第14章:字符数组与字符串——C语言中最常用的“文本容器”
说实话,字符数组和字符串这块,是我当年刚学C语言时踩坑最多的地方。那时候我总搞不清:明明都是字符,为什么有的能当字符串用,有的就不行?后来在嵌入式项目里调试串口数据,被一个缺失的'\0'折腾了整整两天……嗯,从那以后,我再也不敢小看这个小小的结束符了。
这一章,咱们就把字符数组和字符串的关系彻底理清楚。你想想看,C语言里没有专门的“字符串类型”,所有字符串操作都得靠字符数组来完成。说白了,字符数组是容器,字符串是内容——但前提是,这个容器里必须有一个'\0'作为结束标志。
14.1 字符数组与字符串的关系
字符数组就是用来存放字符的数组。每个元素是一个char类型,占用1个字节。而字符串,本质上就是一个以'\0'(空字符,ASCII码为0)结尾的字符数组。
核心区别:
- 字符数组:可以存放任意字符,不一定以'\0'结尾
- 字符串:必须以'\0'结尾,否则C语言的字符串处理函数无法正常工作
我在项目中遇到过这样一个问题:用printf打印一个字符数组,结果后面跟了一堆乱码。原因就是数组里没有'\0',printf一直读到内存里下一个'\0'才停。从那以后,我每次定义字符数组都会多留一个位置给'\0'。
// 字符数组 vs 字符串
char arr1[] = {'H', 'e', 'l', 'l', 'o'}; // 字符数组,长度5,没有'\0'
char arr2[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 字符串,长度6,以'\0'结尾
char arr3[] = "Hello"; // 字符串,自动添加'\0',长度6
printf("arr1: %s\n", arr1); // 可能输出乱码
printf("arr2: %s\n", arr2); // 正常输出 Hello
printf("arr3: %s\n", arr3); // 正常输出 Hello
注意:用双引号括起来的字符串常量,编译器会自动在末尾添加'\0'。但用花括号逐个初始化字符时,不会自动添加'\0',需要手动加。
14.2 字符串的输入输出
字符串的输入输出,常用的方式有几种。我个人习惯用printf和scanf,但在实际项目中,我更推荐用fgets——为什么呢?因为scanf遇到空格就停了,而fgets可以读取一整行。
14.2.1 使用printf和scanf
char name[20];
printf("请输入你的名字:");
scanf("%s", name); // 注意:name本身就是地址,不需要加&
printf("你好,%s!\n", name);
这里有个坑:scanf("%s")不会检查输入长度。如果你输入了20个以上的字符,数组越界就发生了。我曾经在调试一个串口命令解析程序时,就因为用户输入超长导致栈被破坏,程序跑飞了……
14.2.2 使用fgets(推荐)
char buffer[50];
printf("请输入一行文本:");
fgets(buffer, sizeof(buffer), stdin); // 安全!最多读取49个字符+'\0'
printf("你输入的是:%s", buffer);
fgets会读取换行符并存入数组,所以输出时可能会多一个换行。如果你想去掉它,可以这样:
buffer[strcspn(buffer, "\n")] = '\0'; // 去掉换行符
小技巧:在嵌入式开发中,我经常用fgets配合sscanf来解析用户输入。先整行读入,再按格式解析,比直接用scanf安全得多。
14.3 字符串处理函数
C语言标准库提供了几个非常常用的字符串处理函数,都在strlen是查长度,strcpy是复制,strcat是拼接,strcmp是比较。
14.3.1 strlen——求字符串长度
strlen返回字符串中字符的个数,不包括'\0'。
char str[] = "Hello";
int len = strlen(str); // len = 5
printf("字符串长度:%d\n", len);
注意:strlen不会检查传入的指针是否为NULL。如果传入NULL,程序会崩溃。我在项目中习惯先判断一下:
if (str != NULL) {
len = strlen(str);
}
14.3.2 strcpy——字符串复制
strcpy将源字符串复制到目标数组,包括'\0'。
char src[] = "Hello";
char dst[20];
strcpy(dst, src); // dst 的内容变为 "Hello"
printf("dst: %s\n", dst);
重要警告:strcpy不会检查目标数组的大小。如果源字符串比目标数组长,就会发生缓冲区溢出。这是很多安全漏洞的根源。我建议用strncpy代替:
char dst[10];
strncpy(dst, src, sizeof(dst) - 1); // 最多复制9个字符
dst[sizeof(dst) - 1] = '\0'; // 手动添加结束符
14.3.3 strcat——字符串拼接
strcat将源字符串追加到目标字符串的末尾。
char str1[20] = "Hello";
char str2[] = " World";
strcat(str1, str2); // str1 变为 "Hello World"
printf("str1: %s\n", str1);
同样的问题:目标数组必须足够大,能容纳拼接后的结果。我一般会这样估算:
if (strlen(str1) + strlen(str2) + 1 <= sizeof(str1)) {
strcat(str1, str2);
} else {
printf("空间不足,无法拼接!\n");
}
14.3.4 strcmp——字符串比较
strcmp比较两个字符串是否相等。它逐字符比较ASCII码,直到遇到不同字符或'\0'。
char str1[] = "Hello";
char str2[] = "Hello";
char str3[] = "World";
if (strcmp(str1, str2) == 0) {
printf("str1 和 str2 相等\n");
}
if (strcmp(str1, str3) != 0) {
printf("str1 和 str3 不相等\n");
}
// 返回值:
// 相等返回0
// str1 < str2 返回负数
// str1 > str2 返回正数
个人经验:比较字符串时,千万别用==!==比较的是地址,不是内容。我见过不少新手犯这个错,包括当年的我自己。
14.4 知识体系结构图
下面这张图帮你理清本章的核心知识点:
14.5 常见陷阱与避坑指南
做嵌入式这些年,我在字符串上栽过的跟头不少。下面这几个坑,你一定要注意:
- 数组越界:用
strcpy和strcat时,目标数组必须足够大。我习惯用strncpy和strncat,并手动添加'\0'。 - 忘记'\0':自己构造字符数组时,别忘了在末尾加'\0'。否则
strlen会一直数到内存里碰到的第一个0。 - 用==比较字符串:这是新手最容易犯的错误。
==比较的是指针地址,不是内容。一定要用strcmp。 - scanf不检查长度:用
scanf("%s")输入字符串时,如果输入过长,会覆盖数组后面的内存。推荐用fgets。
总结一下:
字符数组是C语言中处理文本的基础。记住三点:
- 字符串必须以'\0'结尾
- 使用字符串处理函数时,确保目标数组足够大
- 比较字符串用
strcmp,不要用==
把这些记住了,字符串操作就不会出大问题。
公众号:蓝海资料掘金营,微信deep3321