3. 字符串+内存函数
3.1 字符串函数
头文件:string.h
3.1.1 strlen
-
原型
// 功能:返回字符串的长度 size_t strlen ( const char * str ); // 形参:字符指针 // 返回值:C字符串的长度
-
原理
size_t strlen ( const char * str ) { const char *eos = str; while( *eos++ ) ; return( eos - str - 1 ); }
-
说明
-
字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )
-
参数指向的字符串必须要以 '\0' 结束
-
函数的返回值为size_t,无符号整形
#include <stdio.h> #include <string.h> int main() { if (strlen("abc") - strlen("abcdef") > 0) { printf(">\n"); } else { printf("<=\n"); } } // 输出结果: <= // "abc"的长度为3; "abcdef"的长度为6 // 而输出结果表示"abc"的长度 小于 "abcdef"的长度 // 原因:strlen("abc") - strlen("abcdef") // (无符号整形)3 - (无符号整形)6 = (无符号整形)-3 // -3为 unsigned int 应表示为 一个正数 (FFFFFFFF-3)(十六进制)
-
-
实例
#include <stdio.h> #include <string.h> int main() { char* s = "hello world!"; printf("%u\n", strlen(s)); // 12 return 0; } // 输出结果:12
3.1.2 strcpy
-
原型
// 功能:拷贝字符串 char * strcpy ( char * destination, const char * source ); // 形参:destination 将要拷贝到的地址空间的指针 // source 被拷贝的字符串 // 返回值:目标空间地址destination
-
原理
char * strcpy(char * dst, const char * src) { char * cp = dst; while((*cp++ = *src++) != '\0') ; /* Copy src over dst */ return( dst ); }
-
说明
-
源字符串必须以 '\0' 结束
-
会将源字符串中的 '\0' 拷贝到目标空间
-
目标空间必须足够大,以确保能存放源字符串
// strcpy并不会检查目标空间是否大于要拷贝的字符串 // 而是直接强行拷贝 所以strcpy不安全 // 源文件最前面加上一行代码 #define _CRT_SECURE_NO_WARNINGS
-
目标空间必须可变
// C语言中数组是可变的 char arr[20] = { 0 }; // 字符串是常量,不可变的 char* s = " "; // 所以目标空间可以是arr,而不能是s // C语言中,字符串的底层就是字符串首字符地址
-
-
实例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main() { char arr[20] = { 0 }; // arr = "hello"; // arr表示的是数组首元素的地址,是数组的标识 // 而字符串 "hello" 应放到arr所指的空间中 // 所以需要使用库函数strcpy将字符串拷贝放到arr中 // 而不能直接赋值 arr = "hello"; strcpy(arr, "hello"); // 字符串使用本质上是字符串首字符的地址 printf("%s\n", arr); // hello }
3.1.3 strcat
-
原型
// 功能:字符串追加 char * strcat ( char * destination, const char * source ); // 形参:source 被追加的字符串 // destination 目标字符串 // 返回值:目标字符串 destination 地址
-
原理
char * strcat ( char * dst, const char * src ) { char * cp = dst; while( *cp ) cp++; /* find end of dst */ while((*cp++ = *src++) != '\0') ; /* Copy src to end of dst */ return( dst ); /* return dst */ }
-
说明
- 源字符串必须以 '\0' 结束
- 目标空间必须有足够的大,能容纳下源字符串的内容
- 目标空间必须可修改
-
模拟实现
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <assert.h> char* my_strcat(char* dst, const char* src) { assert(dst && src); char* cp = dst; while (*cp) { cp++; } // 找到dst中的'\0' //while (*cp++); 问题:为什么上一条while循环不能简写成这样 while (*cp++ = *src++); // 将src复制到dst末尾 return dst; }
-
实例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main() { char arr1[20] = "Hello \0#########"; char arr2[] = "world"; strcat(arr1, arr2); printf("%s\n", arr1); // Hello world return 0; }
-
追加字符串自己
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main() { char arr[20] = "abcd"; strcat(arr, arr); printf("%s\n", arr); // abcdabcd return 0; } // 输出结果:abcdabcd // 而自己实现的strcat函数会出现死循环 // dst和src指向同一块区域 追加时 '\0' 被覆盖
3.1.4 strcmp
-
原型
// 比较字符串1和字符串2 int strcmp ( const char * str1, const char * str2 ); // 形参:str1 str2 分别是要比较的两个字符串 // 返回值:整形 标准规定: // <0 str1<str2 // >0 str1>str2 // =0 str1=str2 // VS中返回值为-1 0 1 // C语言中不能直接使用使用关系运算符确定两个字符串的大小 // 字符串比较,实际上是比较字符的ASCII码
-
原理
int strcmp ( const char * src, const char * dst ) { int ret = 0 ; while((ret = *(unsigned char *)src - *(unsigned char *)dst) == 0 && *dst) { ++src, ++dst; } return ((-ret) < 0) - (ret < 0); // (if positive) - (if negative) generates branchless code }
-
模拟实现
#include <assert.h> int my_strcmp(const char* s1, const char* s2) { assert(s1 && s2); while (*s1 == *s2) { if (*s1 == '\0') { return 0; } s1++; s2++; } return *s1 - *s2; }
-
实例
#include <stdio.h> #include <string.h> int main() { printf("%d\n", strcmp("abc", "abcd")); // -1 return 0; } // 输出结果:-1 // 'a' 比较 'a' 结果 = // 'b' 比较 'b' 结果 = // 'c' 比较 'c' 结果 = // '\0' 比较 'd' 结果 < // 返回 -1
3.1.5 strncpy
-
原型
// 将n个字符拷贝到另一个字符串中 char * strncpy ( char * destination, const char * source, size_t num ); // 形参: destination 目标字符串 // source 被拷贝字符串 // num 拷贝字符个数 // 返回值:destination
-
原理
char * strncpy ( char * dest, const char * source, size_t count ) { char *start = dest; while (count && (*dest++ = *source++) != '\0') /* copy string */ count--; if (count) /* pad out with zeroes */ while (--count) *dest++ = '\0'; return(start); }
-
模拟实现
#include <assert.h> char* my_strncpy(char* destination, const char* source, size_t num) { // 当 num 大于 source 长度时 // 当 num 大于 destination 长度时 // 当 num 等于 0 assert(destination && source); char* ret = destination; char* src = source; while (num && (*destination++ = *source++)) { num--; } if (num) { return ret; } while (num--) { *destination++ = '\0'; } return ret; }
-
实例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main() { char dest[20] = { 0 }; char src[] = "i like c."; strncpy(dest, src, 6); printf("%s\n", dest); // i like return 0; } // 输出结果: // i like
3.1.6 strncat
-
原型
// 功能:将n个字符追加到另一个字符串后 char * strncat ( char * destination, const char * source, size_t num ); // 形参: destination 目标字符串 // source 被追加字符串 // num 追加字符个数 // 返回值:destination
-
原理
char * strncat ( char * front, const char * back, size_t count ) { char *start = front; while (*front++) ; front--; while (count--) if ((*front++ = *back++) == 0) return(start); *front = '\0'; return(start); }
-
模拟实现
#include <assert.h> char* my_strncat( char* front, const char* back, size_t count ) { // count 大于 back 的字符数(包括'\0'在内) // count 等于 0 assert(front && back); // 断言 空指针 char* ret = front; while (*front++); // 找到被追加字符串的结束位置 '\0' front--; // 回退到 '\0' 位置 while (count--) // 循环 count 次 if (!(*front++ = *back++)) // 将 back 复制到 被追加字符串末尾 return ret; // 遇到 '\0' 返回 ret (表示 已经将back的所有字符追加到front后面) *front = '\0'; // 字符串末尾补 '\0' return ret; }
-
实例
#include <stdio.h> #include <string.h> int main() { char dest[20] = "I like "; char src[] = "clang and golang"; char* ret = strncat(dest, src, 5); printf("%s\n", ret); // I like clang return 0; } // 输出结果: // I like clang
3.1.7 strncmp
-
原型
// 功能:比较两字符串前n个字符 int strncmp ( const char * str1, const char * str2, size_t num ); // 形参:str1 str2 分别是要比较的两个字符串 // num 比较的字符个数 // 返回值:整形(规定与strcmp相同) // 相对于 strcmp函数 更安全
-
原理
int strncmp ( const char *first, const char *last, size_t count ) { size_t x = 0; if (!count) { return 0; } /* * This explicit guard needed to deal correctly with boundary * cases: strings shorter than 4 bytes and strings longer than * UINT_MAX-4 bytes . */ if( count >= 4 ) { /* unroll by four */ for (; x < count-4; x+=4) { first+=4; last +=4; if (*(first-4) == 0 || *(first-4) != *(last-4)) { return(*(unsigned char *)(first-4) - *(unsigned char *)(last-4)); } if (*(first-3) == 0 || *(first-3) != *(last-3)) { return(*(unsigned char *)(first-3) - *(unsigned char *)(last-3)); } if (*(first-2) == 0 || *(first-2) != *(last-2)) { return(*(unsigned char *)(first-2) - *(unsigned char *)(last-2)); } if (*(first-1) == 0 || *(first-1) != *(last-1)) { return(*(unsigned char *)(first-1) - *(unsigned char *)(last-1)); } } } /* residual loop */ for (; x < count; x++) { if (*first == 0 || *first != *last) { return(*(unsigned char *)first - *(unsigned char *)last); } first+=1; last+=1; } return 0; }
-
实例
int main() { char str1[] = "I like"; char str2[] = "I like c"; int ret = 0; ret = strncmp(str1, str2, 5); printf("%d\n", ret); // 0 ret = strncmp(str1, str2, 7); printf("%d\n", ret); // -1 return 0; } // 输出结果: // 0 // -1
3.1.8 strstr
-
原型
// 功能:在字符串 str1 中查找第一次出现字符串 str2 的位置 不包含终止符 '\0'。 const char * strstr ( const char * str1, const char * str2 ); char * strstr ( char * str1, const char * str2 ); // 参数:str1 str2 字符串 // 返回值:str2 在 str1 中第一次出现的位置--指针
-
模拟实现
-
查找原理
假设在字符串 "acdeef" 中查找 "ef" 查找步骤如下图:
-
代码
#include <assert.h> #include <stdio.h> char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); const char* s = str1; const char* s1 = NULL; const char* s2 = NULL; // C语言规定 如果str2为空字符串,返回str1 if (*str2 == '\0') { return (char*)str1; } while (*s) { s1 = s; s2 = str2; while (*s1 && *s2 && (*s1 == *s2)) // 或者 while ((*s2 != '\0') && (*s1 == *s2)) // *s2 != '\0' 主要是防止越界访问 { s1++; s2++; } if (*s2 == '\0') { return (char*)s; } s++; } return NULL; } // 测试函数 void test() { char str1[] = "acdeef"; char str2[] = "ef"; char* ret; ret = my_strstr(str1, str2); printf("%s\n", ret); // ef } // main主函数 int main() { test(); return 0; }
-
-
实例
#include <stdio.h> #include <string.h> int main() { char str1[] = "i like c"; char str2[] = "like"; char* ret = strstr(str1, str2); printf("%s\n", ret); return 0; } // 输出结果:like c
3.1.9 strtok
-
原型
// 功能:分割字符串 char * strtok ( char * str, const char * delimiters ); // 参数: // str2 字符串 定义了用作分隔符的字符集合 // str1 字符串 包含0个或多个由str2中一个或多个字符分割的标记
-
说明
- strtok函数找到str中的下一个标记,并将其用 '\0' 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
- strtok函数第一个参数不为NULL时,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置(基于static)
- strtok函数第一个参数为NULL时,函数将在同一个字符串中保存的位置开始,查找下一个标记
- 如果字符串中不存在更多的标记,则返回NULL指针
-
实例
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main() { char str[] = "he#ll*o wo#rl*d"; char delimiters[] = "*#"; // strtok会修改被分割字符串,所以创建临时变量 char tmp[20] = { 0 }; strcpy(tmp, str); for ( char* s = strtok(tmp, delimiters); // 找到str中第一个标记 strtok函数将保存它在字符串中的位置 s != NULL; // 当s=NULL时剩余的字符串中没有更多的标记 s = strtok(NULL, delimiters) // strtok 函数将在同一个字符串中保存的位置开始 查找下一个标记 ) { printf("%s\n", s); } return 0; } // 输出结果: // he // ll // o wo // rl // d
3.1.10 strerror
-
原型
// 功能:将错误码转换为错误信息 char * strerror ( int errnum ); // 参数:errnum 整形 由C语言提供的全部变量 存在于头文件 <errno.h> 中 调用库函数失败时 都会设置全局错误码 // 返回值:转换的错误信息字符串第一个字符地址
-
实例1
#include <stdio.h> #include <string.h> int main() { char* str0 = strerror(0); printf("%s\n", str0); char* str1 = strerror(1); printf("%s\n", str1); char* str2 = strerror(2); printf("%s\n", str2); char* str3 = strerror(3); printf("%s\n", str3); char* str4 = strerror(4); printf("%s\n", str4); return 0; } // 输出结果: // No error // Operation not permitted // No such file or directory // No such process // Interrupted function call
-
实例2
#include <stdio.h> #include <string.h> #include <errno.h> int main() { FILE* pFile; pFile = fopen("unexist.ent", "r"); if (pFile == NULL) printf("Error: %s\n", strerror(errno)); //... 文件处理 fclose(pf); pf = NULL; return 0; } // 输出结果: // Error: No such file or directory
3.1.11 perror
-
原型
// 功能: // 1. 将错误码转换为错误信息 strerror // 2. 将错误信息打印 printf void perror ( const char * str ); // 参数:提示信息 会自动与错误信息拼接打印 // 头文件 stdio.h 中
-
实例
#include <stdio.h> int main() { FILE* pFile; pFile = fopen("unexist.ent", "r"); if (pFile == NULL) perror("Error"); //... 文件处理 fclose(pf); pf = NULL; return 0; } // 输出结果: // Error: No such file or directory
3.2 字符函数
头文件:ctype.h
3.2.1 字符分类
函数 | 如果他的参数符合下列要求 则返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格' ',换页 '\f',换行 '\n',回车 '\r',制表符 '\t',垂直制表符 '\v' |
isxdigit | 十进制数字0~9 |
islower | 十六进制数字,包括所有十进制数字,小写字母af,大写字母AF |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母az或AZ |
isalnum | 字母或者数字,a~z, A~Z, 0~9 |
ispunct | 标点符号,任何不属于数组或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
3.2.2 字符转换
-
原型
// tolower:将大写字符转换为小写字符 int tolower ( int c ); // toupper:将小写字符转换为大写字符 int toupper ( int c );
-
实例
#include <ctype.h> #include <stdio.h> int main() { char str[] = "hello"; int size = sizeof(str) / sizeof(str[0]); for (int i = 0; i < size; i++) { str[i] = toupper(str[i]); } printf("%s\n", str); // HELLO } // 输出结果: // HELLO
3.3 内存函数
头文件:string.h
3.3.1 memcpy
-
原型
// 功能拷贝不重叠内存(C语言规定实现拷贝不重叠内存即可,但是VS编译器中该函数也实现了拷贝重叠内存功能) void * memcpy ( void * destination, const void * source, size_t num ); // 参数:destination 拷贝目标地址 // source 被拷贝数据地址 // num 拷贝的大小 单位:字节 // 返回值:destination
-
模拟实现
#include <assert.h> void* my_memcpy(void* dest, const void* src, size_t num) { assert(dest && src); void* ret = dest; while (num--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; // 不能 (char*)src++ 因为(type)类型转换优先级高于后置自增++操作符 // 或者说 单目运算符 从右向左 先自增再类型转换 而类型转换前 src时无类型指针 不能自增 // 可以这样改写 ((char*)src)++ } return ret; }
-
实例
#include <stdio.h> #include <string.h> int main() { char arr[] = "abcdefg"; memcpy(arr + 4, arr, 2); printf("%s\n", arr); // abcdabg return 0; } // 输出结果: // abcdabg
3.3.2 memmove
-
原型
// 功能:可拷贝重叠内存 void * memmove ( void * destination, const void * source, size_t num ); // 参数:destination 拷贝目标地址 // source 被拷贝数据地址 // num 拷贝的大小 单位:字节 // 返回值:destination
-
模拟实现
-
分析
// 对于一个数组: int arr[] = { 1,2,3,4,5,6,7 }; // 想要将数组索引为1~4的数据复制到索引为3~6的位置 // 使用memcpy函数(C标准规定)得到的结果是 { 1,2,3,2,3,2,3 } // 而预期结果是{ 1,2,3,2,3,4,5 } 显然结果不同 // 原因是 memcpy在拷贝时 如果拷贝内容重叠 会发生覆盖
拷贝有两种情况,分别为从前向后和从后向前
-
情况一
如图,将红框中内容拷贝到篮框中,如果从前往后拷贝则不会发生覆盖,是预期结果;而从后往前拷贝则会发生覆盖,非预期结果。
-
情况二
如图,将红框中内容拷贝到篮框中,如果从前往后拷贝则会发生覆盖,非预期结果;而从后往前拷贝则不会发生覆盖,达到预期结果。
-
情况三
如图,将红框中内容拷贝到篮框中,无论是从前向后拷贝还是从后向前拷贝,并不会发生覆盖,都是预期结果。
-
总结
当目标地址和被拷贝地址重合时,如果目标地址在被拷贝地址之前,则从前向后拷贝;如果目标地址在被拷贝地址之后,则从后向前拷贝。
当目标地址和被拷贝地址不重合时,拷贝顺序并不影响结果。 显然:选择适当的拷贝顺序,就可以避免发生覆盖。 方便起见,和重合统一划分为,如果目标地址在被拷贝地址之前,则从前向后拷贝;如果目标地址在被拷贝地址之后,则从后向前拷贝。
- 代码
#include <assert.h> void* my_memmove(void* dest, const void* src, size_t num) { assert(dest && src); void* ret = dest; if (dest>src) { // 从前向后拷贝 while (num--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } else { // 从后向前拷贝 while (num--) { *((char*)dest + num) = *((char*)src + num); } } return ret; } // 测试函数 void test() { char arr[] = "abcdefg"; my_memmove(arr + 2, arr, 4); printf("%s\n", arr); // ababcdg } // main主函数 int main() { test(); return 0; } // 输出结果: // ababcdg
- 代码
-
-
-
实例
#include <stdio.h> #include <string.h> int main() { char arr[] = "abcdefg"; memmove(arr + 2, arr, 4); printf("%s\n", arr); // ababcdg return 0; } // 输出结果: // ababcdg
3.3.3 memcmp
-
原型
// 功能:内存比较 int memcmp ( const void * ptr1, const void * ptr2, size_t num ); // 参数:ptr1 ptr2 地址 // num 比较小大 单位:字节 // 返回值:整形 标准规定: // <0 str1<str2 // >0 str1>str2 // =0 str1=str2 // VS中返回值为-1 0 1
-
实例
#include <stdio.h> #include <string.h> int main() { int arr1[] = { 1,2,3 }; int arr2[] = { 1,3,5 }; int ret = 0; // 比较arr1和arr2内存空间中的前4字节 ret = memcmp(arr1, arr2, 4); printf("%d\n", ret); // 0 // 比较arr1和arr2内存空间中的前8字节 ret = memcmp(arr1, arr2, 8); printf("%d\n", ret); // -1 return 0; } // 输出结果: // 0 // -1
3.3.4 memset
-
原型
// 功能:内存设置 void * memset ( void * ptr, int value, size_t num ); // 参数:ptr 地址 // value 值 // num 大小 单位:字节 // 返回值:ptr
-
实例
#include <stdio.h> #include <string.h> int main() { char s[10] = { 0 }; memset(s, 'a', 10); for (int i = 0; i < 10; i++) { printf("%c ", s[i]); // a a a a a a a a a a } return 0; } // 输出结果: // a a a a a a a a a a