字符数组(字符串)
背景:C语言没有"字符串"类型
很多语言有专门的 string 类型,但 C 没有。那C里怎么表示一串文字?
答案:用字符数组。 最末尾加一个特殊的 \0(空字符)表示"到这里结束"。
一、字符数组的两种写法
写法1:老老实实一个一个写
写法2:用双引号(更常用)
两种写法完全等价,写法2更方便。
验证一下长度
#include <stdio.h>
int main() {
char str[] = "Hello";
printf("数组长度: %zu\n", sizeof(str)); // 6(包含\0)
printf("字符串长度: %zu\n", strlen(str)); // 5(不包含\0)
// sizeof 数的是所有字符(含\0)
// strlen 只数到\0为止(不含\0)
return 0;
}
%zu是 sizeof 和 strlen 返回值的格式符(size_t 类型)。
二、内存里的样子
内存中:
┌────┬────┬────┬────┬────┬────┐
│ H │ e │ l │ l │ o │ \0 │
└─┬──┴─┬──┴─┬──┴─┬──┴─┬──┴─┬──┘
0 1 2 3 4 5 ← 下标
⚠️ \0 不是字母,是结束标志! 告诉程序:"字符串到这里就完了"。
三、strlen 和 sizeof 的区别
| 表达式 | 结果 | 解释 |
|---|---|---|
sizeof("Hello") |
6 | 整个数组的字节数(含\0) |
strlen("Hello") |
5 | 有效字符个数(不含\0) |
strlen("") |
0 | 空字符串,长度0 |
sizeof("") |
1 | 空字符串也有一个\0 |
⚠️ strlen 需要引入 <string.h> 头文件!
四、遍历字符数组
和普通数组一样,用 for 循环:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Friday";
int len = strlen(str);
for (int i = 0; i < len; i++) {
printf("str[%d] = '%c'\n", i, str[i]);
}
return 0;
}
输出:
也可以直接用 %s 一口气打印整个字符串:
%s 会从数组开头一直输出到遇到 \0 为止。
五、用户输入字符串
scanf 读取
⚠️ 为什么没有 &?
普通变量用 scanf("%d", &x) 要加 &,但数组名 name 本身就是地址,所以不用加。
⚠️ scanf 的坑:遇到空格就停了! 输入 "Friday Hello" → 只读到 "Friday"。
fgets 读取(推荐)
fgets 的三个参数:
1. 存到哪里
2. 最多读多少个字符(含\0)
3. 从哪里读(stdin = 键盘)
六、常用字符串操作函数()
| 函数 | 作用 | 示例 |
|---|---|---|
strlen(s) |
获取字符串长度 | strlen("Hi") → 2 |
strcpy(dest, src) |
复制字符串 | strcpy(copy, str); |
strcat(dest, src) |
拼接字符串 | strcat(greeting, name); |
strcmp(a, b) |
比较两个字符串 | strcmp(s1, s2) == 0 表示相等 |
strncpy(dest, src, n) |
安全复制(限n个字符) | 防溢出 |
strncat(dest, src, n) |
安全拼接 | 防溢出 |
strcmp 的返回值
char a[] = "apple";
char b[] = "banana";
char c[] = "apple";
strcmp(a, b); // < 0 (a 比 b 小)
strcmp(b, a); // > 0 (b 比 a 大)
strcmp(a, c); // == 0 (相等!)
⚠️ 千万不要用 == 比较字符串!
七、完整示例:姓名问候程序
#include <stdio.h>
#include <string.h>
int main() {
char name[50];
char greeting[100] = "你好, "; // 先存好前半句
printf("请输入你的名字: ");
fgets(name, sizeof(name), stdin);
// fgets 会把换行符也读进去,把它去掉
name[strcspn(name, "\n")] = '\0'; // strcspn 找到\n的位置,改成\0
// 拼接
strcat(greeting, name);
strcat(greeting, "!");
printf("%s\n", greeting);
printf("你的名字有 %zu 个字符\n", strlen(name));
return 0;
}
strcspn(str, "\n")返回字符串中第一个\n的位置。用这个技巧去掉 fgets 带进来的换行符。
八、数组越界——字符串的隐患
改了也不够位:
安全做法: 预留足够的空间,用 strncpy 代替 strcpy:
char dest[10];
strncpy(dest, "Hello World", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 确保以\0结尾
九、逐字符操作(字符数组的真正威力)
因为字符串本质就是数组,你可以像操作普通数组那样逐个改字符:
char str[] = "hello";
str[0] = 'H'; // 首字母大写
str[4] = 'Y'; // 改成 hellY
printf("%s\n", str); // hellY
// 全部转大写
for (int i = 0; i < strlen(str); i++) {
if (str[i] >= 'a' && str[i] <= 'z') {
str[i] = str[i] - 32; // ASCII码:'a'-'A' = 32
}
}
printf("%s\n", str); // HELLY
小写字母转大写:减32。大写转小写:加32。 因为 ASCII 表中
'A'=65,'a'=97,相差32。
总结
| 概念 | 要点 |
|---|---|
| 字符串本质 | 字符数组 + \0 结束符 |
| 声明 | char str[] = "Hello"; |
| 长度 | sizeof 含\0,strlen 不含\0 |
| 打印 | %s 一口气输出,或用 for 逐个输出 |
| 输入 | fgets 比 scanf 安全 |
| 比较 | 用 strcmp,不能用 == |
| 复制/拼接 | strcpy / strcat,注意空间够不够 |
| 逐字符操作 | 直接改 str[i],跟普通数组一样 |