字符串函数
上一课我们自己写循环数字符串的长度。这种常用的活儿,C 的标准库早就写好了,放在 <string.h> 里。用之前先 #include 它:
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char word[] = "keyboard";6 printf("%s 有 %zu 个字母\n", word, strlen(word));7 return 0;8}
keyboard 有 8 个字母
这一课讲四个最常用的:
| 函数 | 做什么 |
|---|---|
strlen(s) | s 有几个字符(不算 '\0') |
strcpy(to, from) | 把 from 复制到 to |
strcat(to, from) | 把 from 接到 to 的后面 |
strcmp(a, b) | 比较 a 和 b:一样是 0 |
strlen 和 sizeof
strlen 数到 '\0' 为止,是文字有多长;sizeof 是数组有多大。两个常常不一样:
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char name[20] = "Maria";6 printf("strlen:%zu\n", strlen(name));7 printf("sizeof:%zu\n", sizeof name);8 printf("汉字:%zu\n", strlen("你好"));9 return 0;10}
strlen:5 sizeof:20 汉字:6
name 有 20 个字节,只用了 6 个(5 个字母加 '\0')。strlen 数的是字节:两个汉字是 6 个字节,所以显示 6。
复制:strcpy
数组不能用 = 整个赋值(第 11 课),字符串也一样。下面这段编译不过:
1#include <stdio.h>2 3int main(void) {4 char status[10] = "off";5 status = "on";6 printf("%s\n", status);7 return 0;8}
main.c: In function 'main':
main.c:5:12: error: assignment to expression with array type
5 | status = "on";
| ^assignment to expression with array type 的意思是:不能给数组赋值。要换掉一个字符串的内容,用 strcpy,它把右边的字符一个一个复制过去,连 '\0' 一起:
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char status[10] = "off";6 strcpy(status, "on");7 printf("%s\n", status);8 printf("%zu\n", strlen(status));9 return 0;10}
on 2
注意顺序:先写复制到哪里,再写从哪里复制,和 status = "on" 的方向一样。
拼接:strcat
strcat 找到第一个字符串的 '\0',从那里开始把第二个接上去。常见的做法是先 strcpy 放第一段,再一段一段 strcat:
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char path[30];6 strcpy(path, "photos");7 strcat(path, "/");8 strcat(path, "beach.jpg");9 printf("%s\n", path);10 return 0;11}
photos/beach.jpg
点单步,看 path 的格子一段一段被填上,'\0' 一直往后挪。
比较:strcmp
两个字符串一样不一样,不能用 == 比:
1#include <stdio.h>2 3int main(void) {4 char saved[] = "secret";5 char typed[] = "secret";6 if (typed == saved) {7 printf("一样\n");8 } else {9 printf("不一样\n");10 }11 return 0;12}
不一样
字都一样,却显示「不一样」。== 比的不是里面的字,而是「是不是同一个数组」;这是两个数组,所以永远不相等(第 17 课讲为什么)。要比字,用 strcmp:两个一样就返回 0;第一个排在前面返回负数,排在后面返回正数。
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char saved[] = "secret";6 char typed[] = "secret";7 if (strcmp(typed, saved) == 0) {8 printf("密码正确\n");9 }10 if (strcmp("apple", "banana") < 0) {11 printf("apple 排在 banana 前面\n");12 }13 return 0;14}
密码正确 apple 排在 banana 前面
strcmp 从头一个字符一个字符地比编码,第一个不一样的字符决定先后。
哪个排在前面,显示什么?
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 if (strcmp("Zoo", "apple") < 0) {6 printf("Zoo\n");7 } else {8 printf("apple\n");9 }10 return 0;11}
比的是编码,不是字典顺序。'Z' 是 90,'a' 是 97,所以所有大写字母都排在小写字母前面,"Zoo" 在前。汉字也是按编码比,不是按拼音或笔画。
放不下会出事
strcpy 和 strcat 不知道目标数组有多大,只管往里写,写到 '\0' 为止。目标太小,就写到数组外面去了:
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char name[6];6 strcpy(name, "Alexandra");7 printf("%s\n", name);8 return 0;9}
main.c: In function 'main': main.c:6:5: warning: '__builtin_memcpy' writing 10 bytes into a region of size 6 overflows the destination [-Wstringop-overflow=] 6 | strcpy(name, "Alexandra"); | ^~~~~~~~~~~~~~~~~~~~~~~~~ main.c:5:10: note: destination object 'name' of size 6 5 | char name[6]; | ^~~~ buffer overflow
发生了什么: 停在第 6 行:strcpy:要写 10 个字节,可「name」只有 6 个字节(越界了)。
"Alexandra" 连 '\0' 要 10 个字节,name 只有 6 个。这叫缓冲区溢出,是 C 程序里最常见、也最危险的错之一:多出来的字节会盖掉旁边的数据,坏人还能借它攻击程序。这一次 gcc 在编译时就看出来了,输出最上面那几行就是它的警告;可警告不会拦住程序,它照样编译、运行。我们的引擎在这一行停下来,说清楚要写几个字节、有几个。点「看看是怎么出错的」看一看。
小技巧: 用 strcpy 或 strcat 之前先算一下:结果的 strlen 加 1,不能比数组的 sizeof 大。
字符工具:ctype.h
<ctype.h> 里是处理单个字符的函数。toupper 把小写字母变成大写,别的字符原样返回;tolower 反过来。isdigit 问「是不是数字」,isalpha 问「是不是字母」,是就返回一个不是 0 的数(真),不是就返回 0:
1#include <stdio.h>2#include <ctype.h>3 4int main(void) {5 char code[] = "ab-42c";6 for (int i = 0; code[i] != '\0'; i++) {7 if (isdigit(code[i])) {8 printf("%c 是数字\n", code[i]);9 } else {10 printf("%c 变成 %c\n", code[i], toupper(code[i]));11 }12 }13 return 0;14}
a 变成 A b 变成 B - 变成 - 4 是数字 2 是数字 c 变成 C
上一课我们用 - ('a' - 'A') 自己变大写;用 toupper 更短,也不用先判断是不是小写字母。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。
拼出全名
✓用 strcpy 和 strcat 把 first、一个空格、last 拼进 full,再显示全名和它的长度:Ada Lovelace(12 个字符)。
Ada Lovelace(12 个字符)
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char first[] = "Ada";6 char last[] = "Lovelace";7 char full[30];8 9 return 0;10}
有几个数字
✓用 isdigit 数一数 text 里有几个数字字符,显示 5 个数字。
5 个数字
1#include <stdio.h>2#include <ctype.h>3 4int main(void) {5 char text[] = "Order 66, room 404";6 7 return 0;8}
修好它
✓这段程序越界了,引擎会停下来。把 msg 改大一点,让它显示 Hi, Sam。
Hi, Sam
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 char msg[6];6 strcpy(msg, "Hi, ");7 strcat(msg, "Sam");8 printf("%s\n", msg);9 return 0;10}
这一课学会了
<string.h>:strlen求长度(不算'\0'),strcpy复制,strcat拼接,strcmp比较。strlen是文字的长度,sizeof是数组的大小;strlen数的是字节。- 字符串不能用
=赋值,要用strcpy;不能用==比较,要用strcmp(a, b) == 0。 strcmp按编码比:大写字母排在小写字母前面。strcpy和strcat不检查数组够不够大:放不下就是缓冲区溢出。<ctype.h>:toupper、tolower、isdigit、isalpha处理单个字符。