思维导图


一、 字符与转义字符
1.1 字符的本质是整数
在计算机眼里,'A' 只是一个数字 65。
char 类型通常占用 1 个字节(8 bits),范围是 -128 到 127。
代码示例:
c
char c = 'A';
printf("字符: %c\n", c); // 输出 A
printf("ASCII: %d\n", c); // 输出 65
// 字符运算
char next = c + 1;
printf("下一个: %c\n", next); // 输出 B
1.2 转义字符
有些字符看不见(如换行),或者有特殊含义(如双引号),需要用反斜杠 \ 来转义。
| 转义符 | 含义 |
|---|---|
\n |
换行 |
\t |
制表符,用于对齐 |
\\ |
反斜杠本身 |
\' |
单引号(在字符常量中用) |
\" |
双引号(在字符串中用) |
\0 |
空字符,字符串结束标志 |
二、 C 字符串的存储规则
2.1 核心规则:以 \0 结尾
C 语言没有记录字符串的长度。它怎么知道字符串在哪里结束?
答案: 只要遇到 ASCII 值为 0 的字符
\0,就认为结束了。
声明与初始化:
c
// 写法 1:逐个字符,必须手动加 \0,否则打印乱码
char s1[] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 写法 2:字符串字面量(编译器自动加 \0)
char s2[] = "Hello";
// 内存视角:
// s2 占用了 6 个字节:'H' 'e' 'l' 'l' 'o' '\0'
2.2 数组 vs 指针
代码对比:
c
// 1. 字符数组:内容存放在栈上,可以随意修改
char str[] = "Hello";
str[0] = 'X'; // 合法,变成 "Xello"
// 2. 字符串指针:指向只读数据区(常量区)
char *ptr = "Hello";
// ptr[0] = 'X'; // 崩溃!Segfault (非法写入)
三、 常用字符串函数
3.1 strlen:计算长度
功能: 计算字符串的有效长度,不包括 \0。
注意: 它需要从头走到尾数数,时间复杂度 O(N)。不要在循环条件里调用它!
手写 strlen:
c
size_t my_strlen(const char *s) {
size_t len = 0;
while (s[len] != '\0') { // 只要没遇到结束符
len++;
}
return len;
}
3.2 strcpy / strncpy:复制
strcpy(dst, src):把 src 的内容(含 \0)拷给 dst。
风险: 如果 dst 空间不够,会发生 缓冲区溢出。
推荐: strncpy(dst, src, n),限制拷贝 n 个字符。但要注意 strncpy 可能不补 \0,需要手动补。
3.3 strcmp:比较
错误写法: if (s1 == s2) ------ 这比的是 地址,不是内容!
正确写法: if (strcmp(s1, s2) == 0) ------ 相等返回 0。
3.4 strcat:拼接
strcat(dst, src):把 src 接到 dst 后面。
前提: dst 必须有 足够 的剩余空间容纳 src。
四、 字符串输入的坑与解决
4.1 scanf("%s") 的缺陷
- 遇空格即停: 输入 "Hello World",只能读到 "Hello"。 2. 不检查边界: 极易导致 溢出。
危险代码:
c
char buf[10];
scanf("%s", buf); // 输入 "ThisIsTooLong",程序崩溃或被黑客利用
4.2 fgets:更安全的替代者
语法: fgets(buf, size, stdin);
优点:
1.限制读取大小(最多 size-1 个),绝对安全。
2.可以读取 空格。
缺点: 会把 \n 也读进去(如果空间够的话)。
最佳实践模板:
c
char buf[100];
printf("请输入:");
if (fgets(buf, sizeof(buf), stdin)) {
// 处理末尾的换行符
size_t len = strlen(buf);
if (len > 0 && buf[len-1] == '\n') {
buf[len-1] = '\0'; // 替换为结束符
}
printf("你输入了: %s\n", buf);
}
五、 练习题
题目 1: char s[10] = "abc";,sizeof(s) 和 strlen(s) 分别是多少?
题目 2: char s[] = "abc"; 这个数组实际占用了几个字节?
题目 3: 为什么不能用 if (str == "hello") 来判断字符串内容?
题目 4: 下面代码输出什么?
c
char s[] = "A\0B";
printf("%d", strlen(s));
题目 5: strcpy 和 memcpy 最大的区别是什么?
题目 6: 使用 fgets 读取输入时,如果用户输入的字符数少于缓冲区大小,字符串末尾通常会有什么字符?
题目 7: 字符 '0' 和整数 0 有什么区别?
题目 8: 什么是 缓冲区溢出(Buffer Overflow)?为什么它很危险?
题目 9: 编写一个函数 my_strcpy,实现字符串复制。
题目 10: char *p = "Hello"; p[0] = 'h'; 这行代码会发生什么?
题目 11: 如何将字符串 "123" 转换为整数 123?
题目 12: 下面代码有没有问题?
c
char s[5];
strcpy(s, "Hello");
题目 13: 在 C 语言中,字符串常量(如 "Hello")通常存储在哪个内存区域?
题目 14: 为什么说在循环条件中写 i < strlen(s) 是低效的?
题目 15: 给定 char a[] = "Hi"; char b[] = "Hi";,表达式 a == b 的结果是真还是假?
六、 解析
题 1 解析
答案: sizeof 是 10,strlen 是 3。
详解:
sizeof看的是开辟的 总空间;strlen看的是\0前面有多少个字符。
题 2 解析
答案: 4 字节。
详解:
'a', 'b', 'c', '\0'。编译器自动加结束符。
题 3 解析
答案: 比较的是 地址。
详解:
str是数组首地址,"hello"是常量区地址。这两个地址 永远不可能相等。必须用strcmp。
题 4 解析
答案: 1。
详解:
strlen遇到第一个\0就停止计数。后面的 'B' 被忽略了。
题 5 解析
答案: 停止条件不同。
详解:
strcpy遇到 \0 停止;memcpy严格按照指定的字节数拷贝,不管内容是什么。
题 6 解析
答案: 换行符 \n。
详解:
fgets会把用户按下的回车键也存入缓冲区(只要空间足够)。
题 7 解析
答案:
'0'的 ASCII 码是 48;整数0的值是 0(也就是\0)。
题 8 解析
答案:
向缓冲区写入了超过其容量的数据,覆盖了相邻的内存(如返回地址)。黑客可利用此漏洞执行恶意代码。
题 9 解析
答案:
c
char *my_strcpy(char *dest, const char *src) {
char *ret = dest;
while ((*dest++ = *src++));
return ret;
}
题 10 解析
答案: 运行时错误 (Segfault)。
详解:
"Hello"存储在 只读常量区,试图修改会导致硬件保护异常。
题 11 解析
答案: atoi("123") 或 sscanf。
题 12 解析
答案: 有问题(越界)。
详解:
"Hello" 需要 6 个字节(含 \0),而
s只有 5 个。\0会写到s的外面。
题 13 解析
答案: 只读数据段 (Text Segment / .rodata)。
题 14 解析
答案:
strlen需要遍历字符串。如果放在循环条件里,每次循环都要遍历一次,时间复杂度从 O(N) 变成 O(N^2)。
题 15 解析
答案: 假。
详解:
a和b是两个独立的数组,它们在栈上的地址不同。

日期:2025年2月11日
专栏:C语言