字符数组与字符串:别把数组和字面量搞混了
说实话,我刚学C语言那会儿,最头疼的就是字符数组和字符串字面量。明明看起来差不多,用起来却总出幺蛾子。有一次我写了个简单的登录程序,用==比较两个字符串,结果死活不相等——嗯,后来才知道,那是在比地址,不是在比内容。
今天咱们就把这块彻底捋清楚。
字符数组 vs 字符串字面量
先看个例子:
char str1[] = "Hello"; // 字符数组
char *str2 = "Hello"; // 字符串字面量
这两行代码,表面上看都是存了个"Hello",但底层完全不一样。
| 对比项 | 字符数组 | 字符串字面量 |
|---|---|---|
| 存储位置 | 栈区(可修改) | 常量区(只读) |
| 能否修改内容 | 可以 | 不行!会崩溃 |
| 变量类型 | 数组名,是地址常量 | 指针,指向常量区 |
| sizeof结果 | 整个数组大小(含'\0') | 指针本身大小(4或8字节) |
⚠️ 我曾经踩过的坑: 用
char *p = "abc"; p[0] = 'x'; 试图修改字符串字面量。程序直接段错误。记住,字符串字面量是只读的,想修改就用字符数组。
字符串的输入:gets 和 fgets
输入字符串,最原始的办法是 scanf("%s", str)。但有个致命问题——它遇到空格就停了。你输入"Hello World",它只读到"Hello"。
所以大家更常用 gets 和 fgets。
gets —— 简单但危险
char buf[10];
gets(buf); // 如果输入超过9个字符,直接溢出
gets 不检查缓冲区大小。我见过一个老项目,就因为 gets 导致缓冲区溢出,被黑客利用执行了恶意代码。所以,永远不要在生产代码里用 gets。C11标准已经把它删掉了。
fgets —— 我推荐你用这个
char buf[10];
if (fgets(buf, sizeof(buf), stdin)) {
// 安全!最多读 sizeof(buf)-1 个字符
}
fgets 有三个参数:目标缓冲区、最大读取长度、文件流。它会在末尾自动加 '\0',而且不会溢出。
💡 我个人习惯: 用 fgets 时,第二个参数直接写 sizeof(buf),别写死数字。这样以后改缓冲区大小,代码也不用改。
不过 fgets 有个小特点——它会保留换行符。比如你输入"abc\n",buf 里存的是 "abc\n\0"。如果你不想要这个换行,可以手动去掉:
size_t len = strlen(buf);
if (len > 0 && buf[len-1] == '\n') {
buf[len-1] = '\0';
}
字符串的输出:puts
输出字符串,printf("%s", str) 当然可以。但如果你只是单纯想输出一行字符串,puts 更简洁:
char str[] = "Hello";
puts(str); // 自动加换行
puts 会自动在末尾加一个 '\n'。它内部其实调用了 fputs(str, stdout) 再加换行。
📌 核心区别:
- puts(str) 等价于 printf("%s\n", str)
- fputs(str, stdout) 等价于 printf("%s", str) —— 不加换行
知识体系图
下面这张图,帮你把本章的核心逻辑串起来:
完整示例:综合运用
#include <stdio.h>
#include <string.h>
int main() {
char name[20];
char greeting[50];
printf("请输入你的名字:");
fgets(name, sizeof(name), stdin);
// 去掉换行符
size_t len = strlen(name);
if (len > 0 && name[len-1] == '\n') {
name[len-1] = '\0';
}
// 拼接字符串
strcpy(greeting, "你好,");
strcat(greeting, name);
strcat(greeting, "!欢迎学习C语言。");
puts(greeting); // 输出:你好,XXX!欢迎学习C语言。
return 0;
}
💡 我建议: 实际项目中,字符串操作尽量用
strncpy、strncat 这类带长度限制的函数,别用 strcpy、strcat。安全第一。
好了,字符数组和字符串的基本操作就这些。你想想看,其实核心就两点:能不能改,以及安不安全。记住这两个维度,以后遇到字符串相关的问题,心里就有底了。