字符串函数总结
-
求字符串长度:strlen()函数
size_t my_strlen(const char* str) //因为这个函数不会修改str,const增加鲁棒性 { assert(str != NULL) //防范野指针 int count; while (*str++ != '\0') { count++; } return count; }
- 字符串已经以’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’)
- 注意函数的返回值为size_t,是无符号的,要用%zu格式打印
if (strlen("abc")) - strlen("qwerty") > 0) printf(">\n); else printf("<=\n");
结果将会打印>,因为strlen返回无符号整型size_t,结果虽然是负数,但会被强制转换为大于0的无符号整型。如果要使用的话可以进行强制类型转换
if (int)(strlen("abc")) - (int)strlen("qwerty") > 0)
-
长度不受限制的字符串函数
-
strcpy()函数
char* my_strcpy(char* dest, const char* src) { assert(dest && src) char* ret = dest //保存起始位置 while(*dest++ = *src++) { ; } return ret }
- src必须以’\0’结束,strcpy()中的’\0’拷贝到dest
- src只用于读取不修改,因此设为const src以增强其鲁棒性
- dest必须足够大,以确保能存放字符串
-
strcat()函数 将src追加到dest后面
char* my_strcat(char* dest, const char* src) { assert(dest && src); char* ret = dest; //1.用strlen找到dest中开始拷贝的位置,即第一个'\0'的位置 while (*dest) { dest++; //如果判断条件里写*dest++,则当dest已经为'\0'时,dest还会再++一次 //此时dest就会将'\0'包括进去,那么即使之后的代码有效,在打印时由于打印到'\0'就不再打印了,所以打印无效 } //2.用strcpy将src从头开始将dest拷贝到目标位置,src的'\0'被覆盖 while (*dest++ = *src++) { ; } return ret; } //第一步的另一种写法 while (*dest++) { ; } dest--; int main() { char arr1[20] = "hello "; char arr2[] = "bit"; my_strcat(arr1, arr2); printf("%s\n", arr1); return 0; }
- dest从’\0’开始被src首字符开始覆盖,src的’\0’也被一同拷贝
- 设计思路:先用strlen找到dest’\0’的位置(即开始拷贝的位置),然后用strcpy将src拷贝到dest之前找到的位置
- 用my_strcat函数,字符串自己给自己追加的时候会造成死循环,某些C语言库中的strcat函数解决了这个问题
-
strcmp()函数
int my_strcmp(const char* s1, const char* s2) { assert(s1 && s2); while (*s1 == *s2) { if (*s1 == '\0') { return 0; //相等 } s1++; s2++; } //if (*s1 > *s2) //不相等 //return 1; //else //return -1; return *s1 - *s2; } int main() { char str1[] = "abcq"; char str2[] = "abc"; int ret = my_strcmp(str1, str2); if (ret > 0) { printf(">\n"); } else if (ret == 0) { printf("=\n"); } else { printf("<\n"); } return 0; }
"abc" < "abcdef"
或者arr1 < arr2
这么写是在比较首元素地址的大小- C语言标准规定,若str1>str2,则返回大于0的数字,<则返回小于0的数字,=返回0
- strcmp函数比较的不是字符串的长度,而是比较字符串中对应位置上的字符的ASCII码大小,如果相同,就比较下一对,直到不同或者都遇到’\0’
-
-
长度受限制的字符串函数
-
strncpy
char *strncpy( char *strDest, const char *strSource, size_t count );
- 限制了操作的字符个数
- str2长度小于count时不够的时候其余位置会拿’\0’填充
- src也会将自己末尾的’\0’一同拷贝到dest
-
strncat
- src也会将自己末尾的’\0’一同拷贝到dest
- 可以自己给自己追加了,不会像strcat一样造成死循环
-
strncmp
-
-
字符串查找
-
strstr:找子串,返回str2在str1中第一次出现的位置,若没有找到则返回NULL
char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); //滚动匹配 const char* s1 = str1; //加上const和str1保持一致,否则权限被放大了(指向同一个地址,一个可以被修改,一个不能被修改) const char* s2 = str2; const char* cur = str1; //记录开始匹配的位置 while (*cur) { //匹配失败重置 s1 = cur; //匹配失败s1重置到cur当前位置 s2 = str2; //匹配失败s2重置到str2开始位置 while (*s1 && *s2 && (*s1 == *s2)) //前提条件是*s1,*s2不为零且两者相等 { s1++; s2++; } if (*s2 == '\0') //s2被找完了,也就是说s2匹配成功 { return (char*)cur; //返回这一次匹配开始的起始位置,强转为返回类型char* } cur++; //匹配失败cur前进一位 } return NULL; //找不到 } int main() { char str1[] = "abcdefjlkjjl\0XXXXX"; char str2[] = "cdef"; char* ret = my_strstr(str1, str2); if (NULL == ret) { printf("Cannot find the string.\n"); } else { printf("%s\n", ret); } return 0; }
- 还可以用KMP算法实现
-
strtok:查找自定义分隔符(token)
char *strtok( char *strToken, const char *sep);
- sep参数是个字符串,定义了用作分隔符的字符集合
- strToken为一个字符串,里面包含了0个或者多个被sep分割的字符串段
- strtok函数的第一个参数
- strtok()找到str中的下一个标记,并将其用’\0’结尾,返回一个指向这个标记的指针。strtok()会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改
- strtok函数的第一个参数不为NULL时,函数将找到str中第一标记,strtok函数将保存它在字符串中的位置;strtok函数的第一个参数为NULL时,函数将在同一个字符串中被保存的位置开始,查找下一个标记
printf("%s\n", strtok(arr, sep)); //只找第一个标记 printf("%s\n", strtok(NULL, sep)); //是从上一次保存好的位置开始继续往后找 printf("%s\n", strtok(NULL, sep)); //函数内部有一个静态指针变量保存字符串位置, //函数调用结束后不会被销毁,可以下一次调用时被用到 printf("%s\n", strtok(NULL, sep));
- 不区分分隔符的出现顺序,相同的分隔符只要写一个
- 实际使用不可能手动写n次
printf("%s\n", strtok(NULL, sep))
,要写成循环的形式,具体使用方式如下代码所示
int main() { char arr[] = "wj.feng@tum.de"; char buf[30] = { 0 }; strcpy(buf, arr); //strok会修改原数据,因此使用buf拷贝 const char* sep = "@."; //不区分分隔符的出现顺序,相同的分隔符只要写一个 char* str = NULL; for (str = strtok(buf, sep); str != NULL; str = strtok(NULL, sep)) { printf("%s\n", str); } return 0; }
-
-
错误信息报告: strerror
// strerror 头文件:#include <errno.h> // 全局变量:errno(错误码)比如说404就是一种错误码 int main() { printf("%s\n", strerror(0)); printf("%s\n", strerror(1)); printf("%s\n", strerror(2)); printf("%s\n", strerror(3)); int* p = (int*)malloc(INT_MAX); if (p == NULL) { printf("%s\n", strerror(errno)); //库函数malloc出错时会把错误码放到errno里 //errno是全局变量,会被更新的 perror("malloc"); //与strerror(不打印)使用场景不同 //perror是直接打印错误码对应的字符串,可以加上自定信息(如"malloc") return 1; } return 0; }
-
内存操作函数:str类函数只能用于字符型类型,其他数据如int类数组就不能用
-
memcpy
//void * memcpy ( void * destination, const void * source, size_t num ); void* my_memcpy(void* dest, const void* src, size_t count) //void* 可以用来接收任意类型的指针,但时候时必须要进行强制转换 { assert(dest && src); void* ret = dest; while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; //或者这么写,但这可能有编译器计算路径不确定的问题,还是不要用了 //((char*)dest)++; //++优先级高于强制类型转换 //((char*)src)++; } return ret; }
- 函数从src位置开始往后复制count个字节的数据到dest
- 这个函数在遇到’\0’的时候不会停下来
- 不能用于src和dest有重叠的情况,复制情况未定义,要用memmove
-
memmove
//第一种写法,前->后/后->前/后->前 #include <stdio.h> #include <string.h> #include <limits.h> #include <errno.h> #include <stdlib.h> #include <assert.h> //void * memmove ( void * destination, const void * source, size_t num ); void* my_memmove(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest; //1 if (dest < src) {//前->后 while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; //++(char*)dest; //在某些编译器下可能会有问题 //++(char*)src; } } else {//后->前 while (count--) { *((char*)dest + count) = *((char*)src + count);//以count=20为例,则第一个之间相差19个字节 } } return ret; } int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr1) / sizeof(arr1[0]); //int arr2[10] = { 0 }; //创建一个临时空间不是一个好的写法,因为很难给定到底给多少大小,因此还是用自己的空间 my_memcpy(arr2, arr1, 20); //my_memcpy(arr1 + 2, arr1, 20); //从3开始复制 期望结果: 1 2 1 2 3 4 5 8 9 10 实际结果:1 2 1 2 1 2 1 8 9 10 memcopy不适合重叠内存拷贝(自己拷贝到自己),要用memmove //my_memmove(arr1 + 2, arr1, 20); my_memmove(arr1, arr1 + 2, 20); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr1[i]); } return 0; }
//第二种写法,前->后/后->前/前->后 void* my_memmove(void* dest, const void* src, size_t count) { assert(dest && src); void* ret = dest; if (dest > src && dest < ((char*)src + count)) {//后->前 while (count--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } else {//前->后 while (count--) { *((char*)dest + count) = *((char*)src + count); } } return ret; }
- 既然memmove的功能比memcpy的功能强大,为什么还不废除memcpy?因为memcpy早于memmove出现,出于兼容早期版本C语言等目的是不能随便废除memcpy函数的
- 同时相比于my_memcpy,为了方便使用,VS编译器库函数中的memcpy也实现了重叠拷贝
-
memset:内存设置
//memset 初始化内存单位 //void* memset(void* dest, int c, size_t count); int main() { int arr[] = { 1,2,3,4,5 }; memset(arr, 0xFF, 20); //以字节为单位来初始化内存单元的 return 0; }
-
memcmp
//memcmp //int memcmp(const void* ptr1, const void* ptr2, size_t num); //因为要兼容所有数据类型,所以用了void*,因此这里是一个字节一个字节进行比较的 //形参与实参数据类型不一致时,强制转换为形参的数据类型void* int main() { int arr1[5] = { 1,2,3,4,5 }; int arr2[5] = { 1,2,3,4,0x11223305 }; int ret = memcmp(arr1, arr2, 18); //1,逐字节比较 int ret = memcmp(arr1, arr2, 17); //0 printf("%d\n", ret); return 0; }
-