前言
● 从我们第一个C程序——Hello world
的诞生,到字符串的拷贝、比较等各种操作的实现。从中不难发现:我们在处理C语言时对字符
和字符串
的处理很是频繁,因此学习字符及字符串的各种操作函数尤显其必要性。
● C语言本身是没有字符串类型的,字符串通常放在 常量字符串 中或者 字符数组 中。字符串常量适用于那些对它不做修改的字符串函数。
● 补充:本章所讲解的函数均为C语言的库函数,如果想了解更多关于C语言库函数的内容,这里给大家推荐一个官网cplusplus.com大家可以参照官网里的文档学习库函数。
废话少说,上干货。Let’s go!
一、求字符串长度-strlen
🍑1.函数声明
size_t strlen ( const char * str );
注释:
1.字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。最终返回一个无符号的整形,表示字符串的长度。
2.由于strlen 只是计算字符串的长度,不能对原字符串进行更改,所以参数部分采用const修饰。
2.参数指向的字符串必须要以 ‘\0’ 结束,否则返回随机值。
3.注意函数的返回值为size_t 即 unsigned int是无符号的( 易错 )
🍑2.strlen函数使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { if (strlen("abc") - strlen("abcdef") > 0) printf("abc>abcdef\n"); else printf("abc<abcdef\n"); return 0; }
注释:
我们已知strlen返回的是字符串中‘\0’之前出现的字符个数,即strlen(“abc”)=3,strlen(“abcdef”)=6,但是strlen的返回值为size_t类型,即无符号整数(这里是一个坑),因此无符号整数3-6相当于3的补码加上-6的补码,结果为无符号整数,即一个非常大的数字。因此条件满足,输出abc>abcdef。
🍑3.strlen函数的模拟实现
📝思路一:计数器+遍历查找
#include<assert.h> size_t mystrlen(const char* str) { assert(str); int count = 0; while (*str != 0)//找到字符‘\0’的地址 { str++; count++;//找到‘\0’之前计数器每次加1 } //遍历结束,返回字符串长度 return count; }
📝思路二:递归
#include<assert.h> size_t mystrlen(const char* str) { assert(str); if (*str != 0)//递归条件 return 1 + mystrlen(str + 1); else return 0; }
📝思路三:指针-指针
#include<assert.h> size_t mystrlen(const char* str) { assert(str); const char* start = str;//首字符地址 const char* end = str;//尾字符地址 while (*end != 0)//找到字符‘\0’的地址 { end++; } //即‘\0’的地址-首字符地址,指针相减返回之间元素个数 return end - start; }
二、长度不受限制的字符串函数
顾名思义,这一类函数对字符串进行操作的时候总是以’\0’为基准进行的,而不是以字符串的长度为基准。而长度受限制的字符串函数受长度n的限制。
🍑1.字符串拷贝函数-strcpy
🌳(1)函数声明
char* strcpy(char * destination, const char * source );
注释:
1.会将源字符串中的内容拷贝到目标空间中,包括’\0’。最终返回目标字符串的地址。
2.源字符串必须以 ‘\0’ 结束。
3.目标空间必须足够大,以确保能存放源字符串。
4.目标空间必须可变,即不能为常量字符串。
🌳(2)strcpy函数使用
//使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[20] = "abc"; const char arr2[] = "hello world!"; strcpy(arr1, arr2);//将arr2的内容拷贝到arr1中 printf("arr1=%s\n", arr1); printf("arr2=%s\n", arr2); return 0; }
🌳(3)strcpy函数的模拟实现
#include<assert.h> char* my_strcpy(char*dest,const char*src) { assert(dest && src); char* ret = dest;//记录目标空间的起始位置 while (*dest++ = *src++)//拷贝,当*src=0时停止 { ; } //拷贝完毕,返回目标空间起始位置 return ret; }
🍑2.字符串拼接函数-strcat
🌳(1)函数声明
char * strcat ( char * destination, const char * source );
注释:
1.将源字符串的副本附加到目标字符串中。目标字符串中的‘\0’字符将被源字符串的第一个字符覆盖,并且在连接形成的新字符串的末尾包含一个‘\0’字符。最终返回目标字符串的地址。(可以将strcat理解为字符串追加函数,将源字符串追加到目标字符串末尾)
2.源字符串必须以 ‘\0’ 结束。
3.目标空间必须有足够的大,能容纳下源字符串的内容。
4.目标空间必须可修改。
🌳(2)strcat函数的使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[20] = "hello "; const char arr2[] = "world!"; printf("追加前:%s\n", arr1); printf("追加前:%s\n", arr2); strcat(arr1, arr2); printf("追加后: % s\n",arr1 ); return 0; }
🌳(3)strcat函数模拟实现
char* my_strcat(char* dest, const char* src) { assert(dest&&src); char* ret = dest;//记录目标空间首元素地址 while (* dest!='\0')//找到目标空间dest中‘\0’的地址 { dest++; } //从目标字符串的‘\0’地址处开始拷贝字符串,包括结尾的'\0' //此过程类似于strcpy函数 while (*dest++ = *src++) { ; } return ret;//返回目标空间首元素的地址 }
🌳(4)思考:strcat能否实现字符串给自己追加?
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[20] = "hello"; strcat(arr1,arr1); printf("%s\n", arr1); return 0; }
调试结果:
结论:
strcat在追加时,将末尾的‘\0’字符覆盖,因此在此后追加过程中永远找不到’\0’,即不会正常停止,最终报错:写入冲突。所以strcat函数不能实现自己给自己追加。
🍑3.字符串比较函数-strcmp
🌳(1)函数声明
int strcmp ( const char * str1, const char * str2 );
注释:
1.strcmp函数开始比较两个字符串的第一个字符。如果第一个字符相等,则继续向后比较,直到字符不同或达到终止的‘\0’字符,比较停止。
2.strcmp函数,比较对应位置上的字符大小,而非长度。
3.标准规定:
(1)第一个字符串大于第二个字符串,则返回大于0的数字
(2)第一个字符串等于第二个字符串,则返回0
(3)第一个字符串小于第二个字符串,则返回小于0的数字
🌳(2)strcmp函数使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[] = "abcdef"; char arr2[] = "abq"; printf("%d\n",strcmp(arr1, arr2)); //c>q即arr>arr2。vs下返回 -1 char arr3[] = "abcd"; char arr4[] = "abc"; printf("%d\n",strcmp(arr3, arr4)); //d>'\0'即arr3>arr4。vs下返回 1 char arr5[] = "abc"; char arr6[] = "abc"; printf("%d\n",strcmp(arr5, arr6)); //a=a,b=b,c=c,'\0'='\0'即arr5=arr6。vs下返回 0 return 0; }
🌳(3)strcmp函数模拟实现
#include<assert.h> int my_strcmp(const char* s1, const char* s2) { assert(s1 && s2); while (*s1 == *s2)//字符串对应字符相同 { if (*s1 == '\0') { //如果同时为‘\0’,说明两字符串完全相同,返回0 return 0; } //否则同时向后偏移,进行下一对字符的比较 s1++; s2++; } //如果不满足对应字符相同则返回二者差值(巧妙地满足标准规定) return *s1 - *s2; }
三、长度受限制的字符串函数介绍
🍑1.strncpy
🌳(1)函数声明
char * strncpy ( char * destination, const char * source, size_t num );
注释:
1.表示把source所指向的字符串中以source地址开始的前num个字节复制到destination所指的数组中,并返回被复制后的destination的地址。
2.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
3.destination必须具有足够大的空间可以容纳拷贝后的字符串。
🌳(2)strncpy函数的使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[] = "xxxxxxxxx"; char arr2[] = "123456"; //当num小于字符串arr2的长度时,拷贝前num个字符到arr1 strncpy(arr1, arr2, 3); char arr3[] = "xxxxxxxxx"; char arr4[] = "123456"; //当num大于字符串arr4的长度时,超出的部分自动拷贝0,直到达到num个为止 strncpy(arr3, arr4, 7); char arr5[] = "xxxxxxxxx"; char arr6[] = "123\0ddd"; //当arr6中出现\0时,以\0为arr6字符串的结束标志,超出部分会自动补0 strncpy(arr5, arr6, 4); printf("arr1=%s\n",arr1); printf("arr3=%s\n",arr3); printf("arr5=%s\n",arr5); return 0; }
🍑2.strncat
🌳(1)函数声明
char * strncat ( char * destination, const char * source, size_t num );
注释:
1.把source所指字符串的前num个字符添加到destination所指字符串的结尾处,并覆盖destination所指字符串结尾的’\0’,从而实现字符串的连接。最终返回目标字符串的地址。
2.strncat追加后会自动在最后补上’\0’。
3.如果num大于字符串source的长度,那么仅将source指向的字符串内容追加到destination的尾部。
4.目标字符串必须要有足够的空间容纳追加后的新目标字符串。
🌳(2)strncat函数的使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[20] = "xxxxxxxxx"; char arr2[] = "123456"; //当num小于arr2字符串长度时,在arr1后追加前num个字符 strncat(arr1, arr2, 3); printf("arr1 = % s\n", arr1); char arr3[20] = "xxxxxxxxx"; char arr4[] = "123456"; //当num大于arr4字符串长度时,在arr3后追加arr4字符串 strncat(arr3, arr4, 7); printf("arr3 = % s\n", arr3); return 0; }
补充:与strcat不同,由于strncat追加后会自动在最后补上’\0’,所以可以实现自己给自己追加。
#include<stdio.h> #include<string.h> int main() { //注意:arr1需要有足够大的空间可以容纳追加后的字符串 char arr1[50] = "12345"; strncat(arr1, arr1,5 ); printf("arr1 = % s\n", arr1); return 0; }
🍑3.strncmp
🌳(1)函数声明
int strncmp ( const char * str1, const char * str2, size_t num );
注释:
1.比较直到出现对应位置上的字符不一样,或者一个字符串结束,或者num个字符全部比较完。
2.strncmp函数,同样比较对应位置上的字符大小,而非长度。
3.标准规定:
(1)第一个字符串大于第二个字符串,则返回大于0的数字
(2)第一个字符串等于第二个字符串,则返回0
(3)第一个字符串小于第二个字符串,则返回小于0的数字
🌳(2)strncmp函数的使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[20] = "abce"; char arr2[] = "abcdefg"; //比较前三个字符,a=a,b=b,c=c,即前三个字符相同,返回0 printf("%d\n",strncmp(arr1, arr2, 3)); char arr3[20] = "aca"; char arr4[] = "abcdefg"; //比较前2个字符,a=a,a<b,即arr3第2个字符小,vs下返回1 printf("%d\n", strncmp(arr3, arr4, 2)); char arr5[20] = "a"; char arr6[] = "abcdefg"; //由于num>strlen(arr1)+1,比较前2个字符,a=a,'\0'<b,即arr1的第2个字符小,vs下返回-1 printf("%d\n", strncmp(arr5, arr6, 3)); return 0; }
四、字符串查找与分割
🍑1.字符串查找函数-strstr
🌳(1)函数声明
char * strstr ( const char *str1, const char * str2);
注释:
1.判断字符串str2是否是str1的子串。如果是,则该函数返回 str1字符串从 str2第一次出现的位置开始到 str1结尾的字符串;否则,返回
NULL
。2.strstr函数在查找时,大小写会被认为是不同的字符串。
🌳(2)strstr函数的使用
📝用于判断一个字符串中是否含有目标字符子串
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char arr1[] = "abcdef"; char arr2[] = "bcd"; char* p = strstr(arr1,arr2); if (p == NULL) { printf("不存在\n"); } else { printf("%s\n", p); } return 0; }
🌳(3)strstr函数模拟实现
查找流程如图所示:
仿照以上思路,代码实现如下:
#include<assert.h> char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); const char* s1 = str1; const char* s2 = str2; const char* p = str1;//用于记录字符串查找开始的位置 if (*str2 == '\0')//特殊处理 { //如果str2是空串,直接返回str1 return str1; } while (*p)//判断str1是否遍历完 { s1 = p; s2 = str2; while (*s1 != '\0' && *s2 != '\0' && (*s1 == *s2)) { s1++; s2++; } if (*s2 == '\0') { return (char*)p;//找到了,返回在str1中的开始地址 } p++;//上一次循环没找到,查找位置向后偏移 } //str1中不存在str2,找不到子串 return NULL; }
🍑2.字符串分割函数-strtok
🌳(1)函数声明
char * strtok ( char * str, const char * sep );
注释:
1.sep参数是一个字符串,定义了用作分隔符的字符集合。
2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。
3.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
4.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
5.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
6.如果字符串中不存在更多的标记,则返回 NULL 指针。
🌳(2)strtok函数的使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char buf[50] = { 0 }; char arr[] = "www.The calf wants to turn over@.qq.com"; strcpy(buf, arr);//将数据拷贝一份,不改变源数据 char* flag = ".@ ";//分隔符集合 int count = 0; //根据strtok函数特点: //第一次第一个参数不为NULL,向后查找分隔符,找到改为'\0',返回分隔符前的子字符串,并保存这一次strtok走到的位置。 //第二次令第一个参数为NULL,strtok会从上一次保存的位置开始向后查找分隔符,找到改为'\0'并返回第二个子字符串的地址。 //第三次…… for (char* str = strtok(buf, flag); str != NULL; str = strtok(NULL, flag)) { count++; printf("分割%d=%s\n",count,str); } return 0; }
五、错误信息报告
🍑1.错误报告函数-strerror
🌳(1)函数声明
char * strerror ( int errnum );
注释:
1.返回错误码,所对应的错误信息。
2.与错误码变量
errno
搭配使用。(errno 是记录系统的最后一次错误代码)
🌳(2)strerror函数使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> //使用错误码变量需要包含头文件 #include<errno.h> int main() { FILE* pf = fopen("test.txt", "r"); //库函数调用失败之后,会把错误码记录到错误码变量中——errno if (pf == NULL) { printf("%s\n",strerror(errno)); return 1; } fclose(pf); pf = NULL; return 0; }
拓展:与strerror函数类似,使用perror
函数也可显示相应错误信息。perror相当于printf+strerror,同时参数可添加提示词。
代码展示
#include<errno.h> int main() { FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { perror("错误信息"); return 1; } fclose(pf); pf = NULL; return 0; }
六、字符操作函数
🍑1.字符操作函数的分类
字符操作函数 | 如果它的参数符合下列条件就返回真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’ |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母a~z或A~Z |
isalnum | 字母或者数字,a~z,A~Z,0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
🍑2.字符操作函数的简单使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<ctype.h> int main() { char arr[] = "Are you ok?"; char* p = arr; while (*p) { if (islower(*p)) { *p = toupper(*p); } p++; } printf("%s\n", arr); return 0; }
这些字符操作函数针对单个字符进行操作,它们的参数返回值以及功能都非常简单。这里就不再一一介绍了。
七、内存操作函数
🍑1.内存拷贝函数-memcpy
🌳(1)函数声明
void * memcpy ( void * destination, const void * source, size_t num );
注释:
1.函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
2.这个函数在遇到 ‘\0’ 的时候并不会停下来。
3.如果source和destination有任何的重叠,复制的结果都是未定义的。(即:memcpy并不支持内存空间有重叠的复制)
🌳(2)memcpy函数的使用
//拷贝arr1中的前8个字节到arr2中 //注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { float arr1[] = { 1.0f,2.0f,3.0f,4.0f }; float arr2[5] = { 0.0f }; memcpy(arr2, arr1,8); return 0; }
🌳(3)memcpy函数模拟实现
#include<assert.h> void* my_memcpy(void* dest, const void* src, size_t num) { void* ret = dest; assert(dest && src); while (num--)//拷贝字节数 { //每次拷贝1个字节,所以将其转换为(char*)指针 *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } return ret; }
🍑2.内存拷贝函数-memmove(可重叠)
🌳(1)函数声明
void * memmove ( void * destination, const void * source, size_t num );
注释:
1.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
2.如果源空间和目标空间出现重叠,就得使用memmove函数处理。
🌳(2)memmove函数的使用
//从arr中拷贝20个字节到arr+2 //注意:使用库函数需要包含头文件 #include<string.h> int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; memmove(arr+2,arr,20); return 0; }
🌳(3)memmove函数的模拟实现
分析:
📝代码实现
#include<assert.h> void* my_memmove(void* dest, const void* src, size_t num) { void* ret = dest; assert(dest); assert(src); if (dest < src)//src前——>后拷贝 { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } else//src后——>前 { while(num--) { *((char*)dest + num) = *((char*)src + num); } } return ret; }
🍑3.内存比较函数-memcmp
🌳(1)函数声明
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
注释:
1.比较从ptr1和ptr2指针开始的num个字节
2.标准规定:
(1)ptr1对应的字节内容>ptr2对应字节内容,返回大于0的数字
(2)两个内存块内容相同,返回0
(3)ptr1对应的字节内容<ptr2对应字节内容,返回小于0的数字
🌳(2)memcmp函数的使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { int arr1[] = {1,2,3,4,5}; int arr2[] = {1,2,3,0,0}; int ret = memcmp(arr1, arr2, 13); //分析:前12个字节内容相同,第13个字节内容00000100>00000000 printf("%d\n", ret); return 0; }
🍑4.内存设置函数-memset
🌳(1)函数声明
void * memset ( void * ptr, int value, size_t num );
注释:
1.将ptr中当前位置后面的num个字节用 value 替换并返回ptr。
2.注意是以字节(
byte
)为单位。ptr必须具有足够的空间容纳替换后的ptr。
🌳(2)menset函数的使用
//注意:使用库函数需要包含头文件 #include<stdio.h> #include<string.h> int main() { char str[] = "xxxxxxxxxx"; memset(str,'0', 5);//表示将str的前5个字节设置成字符0 printf("%s\n", str); return 0; }
总结
本章主要对字符串以及字符操作函数进行了一个较为全面的介绍,篇幅较长,但干货满满。建议大家反复观看,慢慢食用😊