1.1.4 strcmp
int strcmp( const char *string1, const char *string2 );
字符串比较
函数细节
- 比较的是字符串的内容,不是字符串的长度
- 比较时内容相同则比较下一对,直到不同或都遇到\0
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
使用方法
int main() { char arr1[] = "abcdef"; char arr2[] = "abcdea"; int ret = strcmp(arr1, arr2); if (ret > 0) { printf(">"); } else if (ret < 0) { printf("<"); } else { printf("="); } return 0; }
运行结果:
模拟实现
int my_strcmp(const char* e1, const char* e2) { assert(e1 && e2); while (*e1 == *e2) { if (*e1 == '\0') return 0;//相等 e1++; e2++; //注意这里不可以调换判断\0的位置,因为这样就在比较第一个元素之前就进行了调整,可能第一个元素就不想相等了,按照这样就错了 } //不相等 /*if (*e1 > *e2) return 1; else return 0;*/ return *e1 - *e2;//更加简洁 } int main() { char arr1[] = "abcdef"; char arr2[] = "abcdea"; int ret = my_strcmp(arr1, arr2); if (ret > 0) { printf(">"); } else if (ret < 0) { printf("<"); } else { printf("="); } return 0; }
运行结果:
1.1.5 strstr
char * strstr ( const char *str1, const char * str2);
查找子串
函数细节
- 查找子串,返回子串第一次出现位置的首地址,找不到返回空指针
使用方法
int main() { char arr1[] = "abcdeqabcdef0"; char arr2[] = "cdef"; char* ret = strstr(arr1, arr2); if (NULL == ret) { printf("找不到子串\n"); } else { printf("%s\n", ret); } return 0; }
运行结果:
模拟实现
char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); const char* s1 = str1;//拷贝地址 const char* s2 = str2; const char* cur = str1;//拷贝str1地址,记录当前位置 //特殊情况 if(*str2 == '\0‘)//!*str2 ok { return str1;//如果字串为空串,直接返回\0 } while (*cur) { s1 = cur;//若一次查找不想等,则重新赋值,cur前的位置一定不可能为字串位置 s2 = str2;//s2始终为str2首地址,用于判断完整子串 while (*s1 && *s2 && (*s1 == *s2))//s1,s2不能为\0,且s1/s2所对应元素相等 //while(*s1 && *s2 && !(*s1 - *s2) //当它们两个都为0,即括号中表达式结果为假时进入循环 { s1++; s2++; } if (*s2 == '\0')//s2查找到了 { return (char*)cur;//直接返回,但是cur类型为const char*,需要强转 } cur++;//cur自增,跳过不可能为字串出现位置的元素 } return NULL;//找不到返回空指针 } int main() { char arr1[] = "abcdeqabcdef0"; char arr2[] = "cdef"; char* ret = strstr(arr1, arr2); if (NULL == ret) { printf("找不到子串\n"); } else { printf("%s\n", ret); } return 0; }
运行结果:
1.1.6 strtok
char * strtok ( char * str, const char * sep );
以分隔字符为分隔线,分隔字符串
函数细节
sep参数是个字符串,定义了用作分隔符的字符集合,第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用\0 结尾,返回一个指向这个标记的指针。
strtok函数的第一个参数不为NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为NULL ,函数将在同一个字符串中被保存的位置(\0)开始查找下一个标记。如果字符串中不存在更多的标记,则返回NULL 指针。
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。
简单来说就是strtok会把分隔符置为\0,并且返回分隔符隔开字段的第一个字符的地址。
使用方法
int main() { char arr[] = "a1249967801@163.com@haha nihao";//需要切割的字符串 char buf[50] = { 0 }; strcpy(buf, arr);//拷贝一份字符串,strtok会分割 const char* sep = "@. ";//分隔符的集合,sep指向的字符串 char* str = NULL; for (str = strtok(buf, sep); str != NULL;str = strtok(NULL, sep))//循环遍历 { printf("%s\n", str); } //三个分隔符,直接穷举 //printf("%s\n", strtok(arr, sep));//只找第一个标记 //printf("%s\n", strtok(NULL, sep));//从保存好\0的位置继续往后找 //printf("%s\n", strtok(NULL, sep)); //如果字符串中不存在标记了,则返回空指针 return 0; }
运行结果:
重复切割问题
若一个字符串被重复切割,且两次切割第一个参数均为待切割字符串,它切割的过程是什么样的?能否详细解释?
案例:
int main() { char arr[] = "hello@anduin@hello"; char buf[200] = { 0 }; strcpy(buf, arr); const char* p = "@"; //char* str = NULL; char* str = strtok(buf, p); for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p)) { printf("%s\n", str); } return 0; }
分析:
这是使用方法部分的错误写法,是我犯的一个错误,为了搞清这个错误,我花了许多时间,接下来就让我阐述一下这段代码干了什么:
这里我们可以看到str在进入循环之前进行了两次切割,这就为字符串重复切割,根本问题就出现在这里。
第一次切割:str首先被strtok切割,此时把分隔符@替换为\0,此时buf为hello\0anduin@hello。
第二次切割:从起始处开始处理,但是字符串处理的是以\0结尾的字符串,也就是说这里处理的是hello\0,而不是hello\0anduin@hello,而buf本身不是NULL,所以当当地一个字符串找不到分隔符时,它返回的就是起始地址。第二次进入循环时将NULL传给strtok函数,由于是从\0处开始的就认为没什么可处理的,于是返回NULL,循环结束。
所以最后打印的结果就为hello。
运行结果
1.1.7 strerror
char * strerror ( int errnum );
返回错误码,所对应的错误信息。
函数细节
全局变量:errno(错误码),为全局整形变量,当库函数调用失败会把相应错误码放到errno中,把错误码传给strerror函数,会返回char* 类型地址,为错误信息的首元素地址,通过printf可以打印出相应错误信息。
函数对应头文件是include <errno.h>
使用方法
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));//Not enough space } return 0; }
分析:
不同的数字对应不同的错误码,当库函数调用失败时就会将对应的错误码记录到errno中。如果将数字或errno传给strerror函数,就会得到错误信息的首元素地址,并且可以通过printf打印错误信息。
malloc向堆区申请内存,将起始地址返回,类型是void,所以需要进行强制类型转换,malloc开辟空间失败返回空指针,malloc是库函数,调用失败会把错误码放到errno中~
且错误码是全局的,会被实时更新!错误码不能一次全部返回!!!
运行结果:
strerror和perror的抉择
C语言中能得到错误信息的函数不止strerror一个,还有一个函数叫做perror。
void perror( const char *string );
打印错误信息
在某种程度上,perror的使用方式比strerror更加方便,如果给出自定义提示信息,它会自动补上:和空格并且直接打印出错误信息;若不给提示信息,则会直接打印错误信息。perror在得到库函数调用失败的错误码后,会根据错误码打印对应的错误信息。
int main() { int* p = (int*)malloc(INT_MAX); if (p == NULL) { perror("Malloc");//Malloc: Not enough space,通过提示信息加上冒号和空格,打印出错误信息 perror("");//不加提示信息 //但是不能什么都不加,函数的参数有规定,起码也得是个空字符串 return 1;//打印结束直接返回,1表示异常返回 } return 0; }
运行结果:
perror是否一定比strerror好?相比于perror,strerror有什么优点?
它俩各有长处,perror一定会打印错误信息(冒号前的为自定义信息),如果只是想打印错误信息,那么perror比较方便;但是如果不想打印,只想拿到错误信息,strerror会根据错误码转化为错误信息,不打印。在这时我们最好的选择就是strerror。
1.1.8 Tip
以上是长度不受限制的字符串函数!在使用时可能会有风险,因为不能规定操作的字符个数,而接下来,我们将要介绍长度受限制的字符串函数~
1.2 长度受限制的字符串函数
长度受限制的字符串函数相比于长度不受限制的字符串函数更加安全,有了这个限制,就在一定范围内约束了越界访问,非法访问内存等错误。
1.2.1 strncpy
char * strncpy ( char * destination, const char * source, size_t num );
拷贝指定个数的字符到目标字符串
函数细节
- 拷贝num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
- \0不算做字符串内容,num为\0之前出现的字符个数。
使用方法
int main() { char arr1[] = "abcdef"; char arr2[] = "and"; strncpy(arr1, arr2, 3);//拷贝三个字符 printf("%s\n", arr1); strncpy(arr1, arr2, 6);//源字符串长度<操作长度,strncpy确实会操作操作长度大小的字符个数,不够的会用\0来填充 printf("%s\n", arr1); return 0; }
图:
运行结果:
模拟实现
char* my_strncpy(char* dest, const char* src, size_t count) { assert(dest && src); char* start = dest; while (count && (*dest++ = *src++) != '\0')//仍然会用\0覆盖 count--; if (count)//处理操作长度大小>源字符串长度的情况 { while (--count)//覆盖次数为count - 1次,因为上方\0已经被覆盖过一次 { *dest++ = '\0'; } } return (start); } int main() { char arr1[] = "abcdef"; char arr2[] = "anduin"; size_t count = 0; scanf("%u", &count); char* ret = my_strncpy(arr1, arr2, count); printf("%s\n", ret); return 0; }
运行结果:
1.2.2 strncat
char * strncat ( char * destination, const char * source, size_t num );
追加指定个数的字符
函数细节
- 追加num个字符从源字符串到目标空间。
- 如果源字符串的长度小于num,只会追加字符串,其余不会操作,并不会自动追加\0。
- 从目标字符串的\0处,开始追加指定字符。
使用方法
int main() { char arr1[20] = "abcdef\0XXXXXXX"; char arr2[] = "and"; strncat(arr1, arr2, 6); //如果操作长度大于字符串长度 //则只会追加字符串,其余不会操作,既不会自动填充\0 //在这种情况下,在追加的末尾加上\0 printf("%s\n", arr1); }
图:
运行结果:
模拟实现
char* my_strncat(char* dest, char* src, size_t count) { char* start = dest; while (*dest)//找目标字符串的\0 { dest++; } while (count--)//count为真 { if ((*dest++ = *src++) == 0)//操作长度大于源字符串,不进行补\0,直接返回 return(start); } *dest = '\0';//追加完毕没有返回,则手动加上\0,此时\0经过++指向追加元素最后元素的后一个位置 return (start); } int main() { char arr1[20] = "hello "; char arr2[] = "worldld"; char* ret = my_strncat(arr1, arr2, 5); printf("%s\n", ret); return 0; }
运行结果:
1.2.3 strncmp
int strncmp( const char *string1, const char *string2, size_t count );
比较指定长度的字符串的大小
函数细节
- 比较指定个数的字符,count为字符个数
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
使用方法
int main() { char arr1[] = "abcdef"; char arr2[] = "abcdeq"; int ret = strncmp(arr1, arr2, 4); printf("%d\n", ret); ret = strncmp(arr1, arr2, 6); printf("%d\n", ret); return 0; }
运行结果:
模拟实现
int my_strncmp(char* str1, char* str2, size_t count) { assert(str1 && str2); while (!((*str1 - *str2)) && *str1 && *str2 && --count) //如果两者相等且不为'\0',并且只操作count个数, //这里需要前置--,如果使用后置--,那么还会多进入一次循环,导致count多执行一次 //前置--,无需进行特殊处理,当前情况直接返回即可 { str1++; str2++; } return *str1 - *str2;//无论是\0还是其他情况都能处理 } int main() { char arr1[] = "abcd"; char arr2[] = "abcq"; int ret = my_strncmp(arr1, arr2, 3); if (ret > 0) { printf("arr1 > arr2"); } else if (ret < 0) { printf("arr1 < arr2"); } else { printf("arr1 = arr2"); } return 0; }
运行结果: