Kodewick
第 14 课,共 30 课目录

字符串函数

第 14 课,共 30 课 · 第 2 部分 · 函数和数组 · 约 15 分钟

上一课我们自己写循环数字符串的长度。这种常用的活儿,C 的标准库早就写好了,放在 <string.h> 里。用之前先 #include 它:

main.c
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 是数组有多大。两个常常不一样:

main.c
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 课),字符串也一样。下面这段编译不过:

main.c
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' 一起:

main.c
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:

main.c
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

两个字符串一样不一样,不能用 == 比:

main.c
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;第一个排在前面返回负数,排在后面返回正数。

main.c
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}

放不下会出事

strcpy 和 strcat 不知道目标数组有多大,只管往里写,写到 '\0' 为止。目标太小,就写到数组外面去了:

main.c
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:

main.c
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 更短,也不用先判断是不是小写字母。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

拼出全名

用 strcpy 和 strcat 把 first、一个空格、last 拼进 full,再显示全名和它的长度:Ada Lovelace(12 个字符)。

要显示
Ada Lovelace(12 个字符)
exercise1.c
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}
输出
点「运行」或按 Ctrl+Enter
练习 2

有几个数字

用 isdigit 数一数 text 里有几个数字字符,显示 5 个数字。

要显示
5 个数字
exercise2.c
1#include <stdio.h>2#include <ctype.h>3 4int main(void) {5    char text[] = "Order 66, room 404";6 7    return 0;8}
输出
点「运行」或按 Ctrl+Enter
练习 3

修好它

这段程序越界了,引擎会停下来。把 msg 改大一点,让它显示 Hi, Sam。

要显示
Hi, Sam
exercise3.c
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}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • <string.h>:strlen 求长度(不算 '\0'),strcpy 复制,strcat 拼接,strcmp 比较。
  • strlen 是文字的长度,sizeof 是数组的大小;strlen 数的是字节。
  • 字符串不能用 = 赋值,要用 strcpy;不能用 == 比较,要用 strcmp(a, b) == 0。
  • strcmp 按编码比:大写字母排在小写字母前面。
  • strcpy 和 strcat 不检查数组够不够大:放不下就是缓冲区溢出。
  • <ctype.h>:toupper、tolower、isdigit、isalpha 处理单个字符。