6. 指针与字符串:字符串指针与字符数组、字符串常量的指针表示、字符串处理函数
字符串在C语言里,其实是个挺有意思的话题。很多初学者会困惑:明明C语言没有专门的字符串类型,那我们平时用的字符串到底是什么?说白了,字符串就是字符数组,只不过末尾多了个'\0'。今天我就带你把这层窗户纸捅破。
6.1 字符数组 vs 字符串指针
先看两个定义:
char str1[] = "Hello";
char *str2 = "Hello";
这两行代码,看着差不多,但内存布局完全不同。我当年刚入行时,就在这上面栽过跟头。
字符数组:str1 是在栈上分配了6个字节的空间(5个字符 + 1个'\0'),内容可以修改。
字符串指针:str2 指向的是常量区的一个字符串字面量。这个区域是只读的,你敢写它,程序就敢崩。
核心区别:
- 字符数组:可读可写,内存分配在栈上
- 字符串指针:指向常量区,只读不可写
举个例子你就明白了:
char str1[] = "Hello";
char *str2 = "Hello";
str1[0] = 'h'; // 没问题,可以改
str2[0] = 'h'; // 运行时崩溃!常量区不可写
嗯,这里要注意。很多教科书上只讲语法,不讲背后的内存模型。我建议你写代码时,如果确定不会修改字符串内容,就用指针形式;如果需要修改,就用数组形式。
6.2 字符串常量的指针表示
字符串常量,比如 "Hello",在C语言中是什么类型?答案是 const char *。它存储在程序的只读数据段(.rodata)里。
你想想看:
const char *p = "Hello";
printf("%p\n", p); // 打印常量区的地址
printf("%c\n", *p); // 输出 'H'
printf("%c\n", *(p+1)); // 输出 'e'
指针可以像数组一样用下标访问:
printf("%c\n", p[0]); // 输出 'H'
printf("%c\n", p[1]); // 输出 'e'
为什么会这样?因为 p[i] 本质上就是 *(p+i)。数组和指针在访问元素时,底层是同一套机制。
个人经验:我在做嵌入式协议栈时,经常用字符串指针指向协议头部的固定字符串。比如HTTP的"GET"、"POST"这些关键字,用指针指向常量区,既省内存又安全。
6.3 字符串处理函数
C标准库提供了一组字符串处理函数,都在 <string.h> 里。我挑几个最常用的讲讲。
6.3.1 strlen() — 求长度
char str[] = "Hello";
size_t len = strlen(str); // len = 5,不包含'\0'
注意,strlen() 返回的是 size_t,不是 int。我见过有人写 int len = strlen(str),在64位系统上可能出问题。
6.3.2 strcpy() 与 strncpy() — 拷贝
char dest[10];
char *src = "Hello";
strcpy(dest, src); // 危险!不检查目标缓冲区大小
strncpy(dest, src, sizeof(dest) - 1); // 安全,留一个位置给'\0'
dest[sizeof(dest) - 1] = '\0'; // 确保以'\0'结尾
避坑指南:我曾经在一个项目中,因为用了 strcpy() 没检查缓冲区大小,导致堆栈被破坏,查了整整两天才找到原因。从那以后,我写代码只用 strncpy() 或 snprintf()。
6.3.3 strcmp() — 比较
if (strcmp(str1, str2) == 0) {
// 两个字符串相等
}
千万别用 == 比较字符串!那比较的是指针地址,不是内容。我见过新手这样写:
char str1[] = "Hello";
char str2[] = "Hello";
if (str1 == str2) { // 永远为假!比较的是两个数组的首地址
// ...
}
6.3.4 strcat() 与 strncat() — 拼接
char buf[20] = "Hello";
strncat(buf, " World", sizeof(buf) - strlen(buf) - 1);
拼接时一定要留够空间,包括最后的 '\0'。
6.4 知识体系图
下面这张图,帮你理清指针与字符串的核心关系:
6.5 实战建议
说了这么多,我总结几条实战经验:
- 能用
const就用const:如果指针指向的是字符串常量,加上const修饰,编译器能帮你检查出很多潜在错误。 - 优先用
strncpy()而不是strcpy():安全第一,别图省事。 - 字符串比较用
strcmp():别用==,那是比较地址。 - 注意
'\0'的存在:分配缓冲区时,永远多留一个字节。
我的习惯:在嵌入式项目中,我经常用 snprintf() 替代 strcpy() 和 strcat()。它既能格式化输出,又能指定最大长度,一个函数搞定所有字符串拼接需求。
好了,指针与字符串这块,说白了就是搞清楚内存里数据是怎么放的。数组在栈上,常量在只读区,函数帮你做操作。搞明白这些,字符串这块你就拿下了。
公众号:蓝海资料掘金营,微信deep3321