1. 字符串
C语言中字符串的处理是非常重要的。
可以使用char直接定义一个单个字符,使用单引号('')初始化,用%c可以将单个字符输出:
1 | char c = 't'; |
字符串本质上来说是一个由字符组成的数组,类型是char[],字符串可以储存由多个字符组成的字符序列,用大括号加双引号({""})或直接用双引号("")初始化,用%s可以将字符串输出,直接传入数组名即可:
1 | char str[] = "Hello"; // 实际的字符数组:{'H', 'e', 'l', 'l', 'o', '\0'} |
C语言中,字符串的结尾必定是\0,如果结尾丢失\0,会导致意料之外的问题。
1 | char str1[6] = "Hello"; // 正确,数组长度足以存储字符串和终止符 |
因此定义字符串时一般不会显式指定字符数组的长度,应该使用和数组类似的特性,直接让编译器自行计算长度,这样可以防止手动计算出错。
微软在处理字符串问题时,推荐使用带_s后缀的函数,例如对于字符串读取,微软推荐使用scanf_s而不是scanf,这种新的函数对字符串有特殊处理,可以避免出现字符串的未定义行为。
字符串也可以使用指针定义,与数组的指针类似,字符串的指针可以理解为指向字符数组中第一个字符的指针。
指针定义的字符串同样可以使用%s输出,但输出时与其他类型的指针不同,不需要有解引用符(*),直接将指针传入即可。
1 | char* ptr_str = "Hello!"; // 指向字符串常量的指针 |
需要注意的是,使用指针定义的字符串,其指针指向的是字符串常量,因此不可以修改单个字符,强制修改会导致未定义行为,且编译器不会显式报错,非常危险。
1 | ptr_str[0] = 'h'; // 未定义行为,字符串常量不可修改,可能会导致程序崩溃 |
想要修改指针定义的字符串时,只能直接修改指针,使其直接指向另一个字符串常量:
1 | ptr_str = "world!"; // 指针可以指向另一个字符串常量 |
一般情况下,字符串定义后不会再进行修改,因此字符串会定义为const常量字符数组类型,或者在定义指针字符串的时候显式写上const关键字。
1 | const char str[] = "Hello"; |
这样定义的话,修改字符串中的某个字符时,编译器会显式报错,更为安全。
如果不加
const常量类型,使用字符数组定义的字符串可以直接单独修改其中的字符。
1 str[0] = 'h';此外,如果定义字符指针指向定义好的指针数组,通过指针可以实现字符的修改,这个与其他类型的数组类似。
1
2
3 char str[] = "Hello";
char* ptr_str = str;
ptr_str[0] = 'h'; // 修改指针指向的内容同样也可以使用这个特性结合指针加法遍历字符串数组:
1
2
3
4
5
6
7 char str[] = "Hello";
char* ptr_str = str;
while (*ptr_str != '\0'){
printf("%c\n", *ptr_str);
ptr_str++;
}
因此,如果想要定义一个需要修改的字符串,应该使用字符数组来定义,要么隐式定义,要么定义的数组长度应满足最大长度要求。
当字符数组的长度大于有效值时,字符串的结尾以及其他无效字符均为\0。
如果想要定义一个不需要修改的字符串,可以使用const指针定义,也可以定义为const常量类型的字符数组,在修改时会有明显的报错,可读性更强。
2. strcpy_s的用法
常用的字符串处理函数包含在一个C语言自带的头文件string.h中,使用时需要包含头文件。
strcpy_s用于将一个字符串复制到另一个字符串当中。
传入的参数中,最后面的是源字符串,最前面的是目标字符串,中间需要传入想要复制的字符串的大小。例如:
1 | const char src[] = "Hello"; |
这个函数保证了字符串复制不会出现溢出的情况,如果源字符串长度大于要复制的长度,程序运行时会直接报错。
3. VS编译器捕获字符串异常
通过点击strcpy_s查看源码,可以发现此函数有一个类型为errno_t的返回值,在较为规范的程序中可以通过返回值判断函数运行是否异常,从而进行异常的捕获与处理。
在面向对象的语言中,对于异常捕获可以使用专门的try...catch,C语言中想要捕获异常,需要手动写一个异常处理函数,并在main函数中设置:
1 |
|
需要注意的是,设置无效参数处理程序之后,如果直接运行代码,依然会弹出报错,这是因为Visual Studio在Debug模式下CRT(C Runtime Library)会额外触发断言,导致出现弹窗报错。
需要将程序的生成模式切换到Release模式,或者在Debug模式下在main函数中手动禁用断言弹窗:
1 |
|
设置之后,程序捕获到的错误码就会被打印出来。
4. strlen
strlen函数专门用于计算字符串的长度。
传入想要计算长度的字符串即可,在遇到\0时结束计算,不会将\0包含在内,返回size_t类型的值。
1 | char str[50] = "Hello"; |
但这样写有可能会有警告出现,因为strlen函数并没有检查字符串溢出的功能。
微软建议字符串初始化时统一初始化为0,然后使用strcpy_s函数对字符串安全赋值,赋值后的字符串再使用strlen函数计算长度时就没有警告了。
1 | char str[50] = { 0 }; |
5. strcat_s
strcat_s用于拼接字符串。
如果想要将字符串2拼接到字符串1上,传入的参数中,最前面的是字符串1,会被拼接修改,得到拼接后的字符串,最后面的是字符串2,不会被修改,中间需要传入字符串1的总长度。
1 | char str[13] = "Hello"; |
如果被拼接的字符串的剩余长度不足以容纳需要拼接的字符串,程序会出现运行时弹窗报错。
6. sprintf_s
与printf类似,sprintf_s是专属于字符串的格式化输出。
第一个参数传入字符串,第二个参数传入这个字符串的长度,后面的内容与printf函数类似。
sprintf_s会将格式化输出的内容统一解析并格式化为字符串,并存到传入的字符串中。
sprintf_s会返回一个值,当这个值大于0时,则字符串格式化写入成功,否则写入字符串失败。
1 | char buffer[50] = { 0 }; |
如果需要写入的字符串长度大于第一个参数中的字符串长度,则会出现运行时报错的弹窗。
7. strncpy_s
strncpy_s的作用与strcpy_s类似,都是复制字符,不同的是,strncpy_s需要多传入一个参数,用于限制最大复制的连续字符数(不包括\0和它后面的字符)。
也就是如果设置最大复制的字符数为n,则真正复制的是源字符串的不多于前n个字符,到\0停止复制。
函数返回一个errno_t类型的值,如果这个值等于0,则字符串拷贝成功,否则字符串拷贝失败。
1 | char dest[20]; |
8. strncat_s
与上一节类似,strncat_s比strcat_s多了一个参数,限制函数最多追加多少个字符(不包括\0和其后面的字符)。
1 | char dest[20] = { 0 }; |
9. gets_s
gets_s在功能上与scanf_s类似,都是获取用户的输入,并将其存到一个变量中,但如果用户输入的是纯字符串,应当优先使用gets_s。
gets_s需要传入两个参数,第一个是待存入的字符串,第二个是这个字符串的长度,避免缓冲区溢出。
标准写法是加一个if条件判断,当gets_s的返回值为NULL时,读取输入错误,否则读取用户输入成功。
1 | char buffer[50]; |
当输入的长度大于传入的字符串长度时,同样会出现运行时弹窗报错。
10. strtok_s
strtok_s用于按照要求分解字符串,并返回指向下一个子字符串的指针。
需要传入三个变量,第一个是需要分割的字符串,第二个是分隔符(分割要求),第三个是一个二级指针,用于在连续调用这个函数时保存其状态。
当第一个字符串传入NULL时,strtok_s会认为需要继续之前的拆分工作,拆分前面第一次调用它时传入的字符串。
1 | char str[] = "This is a sample string"; // 可修改的字符串 |
由于第三个参数需要传入一个二级指针,因此定义的时候定义为指针,传入的时候还需要再加一个取地址符。
11. strcmp
前面学过的函数中,只有strlen没有_s的写法,但strlen的功能只是拿到字符串的长度,其他但凡涉及到字符串的修改,都应当使用更为安全的_s版函数。
下面几个要介绍的函数不涉及到字符串的修改,因此没有_s。
strcmp用于比较两个字符串,参数中传入需要比较的两个字符串,返回值是一个int类型的数据,当返回值为0时,两个传入的字符串相同,否则不同。
1 | const char* str1 = "Hello, World!"; |
事实上,strcmp对比两个字符串时,会逐个字符通过减法比较其ASCII值的大小,直到找到ASCII值不同的两个对应字符,当str1中这个字符的ASCII值大于str2,则函数返回值大于0,否则函数的返回值小于0。
12. strncmp
strncmp是在strcmp的基础上限制比较前多少个字符。
第三个参数传入size_t类型的数据,限制比较的字符数。
1 | const char* str1 = "Hello, World!"; |
13. strchr与strrchr
strchr与strrchr用于寻找字符串中的某一个字符,strchr是从前往后找,strrchr是从后往前找,也就是一个字符串从前往后看时,strchr找到的是某一个字符第一次出现的位置,strrchr找到的是某一个字符最后出现的位置。
strrchr多出来的那个r代表reverse,从字符串的末尾开始查找。
第一个参数传入需要寻找字符的字符串,第二个参数传入需要寻找的字符,函数返回一个char*类型的指针,指向找到的那个字符,如果没有找到,返回NULL。
拿到指针后,可以使用指针的加减法输出这个字符在字符串中的位置。
1 | const char* str = "Hello, World!"; |
14. strstr
strstr用于在一个母字符串中查找另一个子字符串。
传入的第一个参数是母字符串,第二个参数是子字符串,返回值是一个指向第一次出现子字符串的第一个字符位置的指针,如果没有找到,返回NULL。
拿到指针后,同样可以计算出子字符串的首字符在字符串中的位置。
1 | const char* text = "This is a simple string example."; |
需要注意的是,当子字符串的长度大于母字符串时,直接返回NULL,表示没有找到。
当子字符串为NULL时,函数会出现警告"sub" 可能是 "0": 这不符合函数 "strstr" 的规范。,程序无法正常运行。
15. strspn与strcspn
strspn用于统计两个字符串从开头开始,相同的字符的个数,直到遇到不同的字符,统计停止。
传入需要进行统计的两个字符串,返回一个size_t类型的长度数据。
1 | const char* str1 = "123456abcdef789"; |
strcspn同样传入两个字符串,用于从头开始连续统计传入的第一个字符串中,没有在第二个字符串中出现的字符数,返回一个size_t类型的长度数据。
1 | const char* str1 = "123456abcdef789"; |
这个示例中,字符串1中字符b之前的所有从头开始的连续字符都没有在字符串2中出现,因此计数返回7。
这个函数可以用来做一个检测文件名中是否有非法字符的案例:
1 | const char* input = "filename.txt"; |
16. 案例:关于string.h
案例:一个综合案例,可能会用到本章学到的各种函数,实现以下功能:
- 将一句话中的特定单词替换为另一个单词;
- 对替换后的字符串统计其中出现某个字符的总数;
- 统计替换后的字符串的单词数;
- 将替换后的字符串中每一个单词提取出来,去重后放在一个数组里。
代码实现:
1 |
|
17. 第十章结束语
本章节主要介绍了字符串的定义与使用,以及很多用于处理字符串的string.h库函数的用法。
字符串的使用与处理涉及到很多指针与数组的相关知识,可以结合起来思考。
多练习,尽量能流畅地使用这些库函数。
本文链接: https://hanqingjiang.com/2026/01/10/20260110_C_string/
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
