5、指针与字符串:字符串的存储方式、字符指针与字符数组的区别、字符串处理函数的指针实现、字符串常量与指针
字符串,说白了就是字符的序列,末尾带个'\0'。很多C语言初学者觉得字符串很简单,但一碰到指针就懵了。我当年也是这样,直到在项目中踩了几个坑,才真正搞明白。
5.1 字符串的两种存储方式
字符串在内存里,主要有两种待的地方:
- 静态存储区:存字符串常量,比如
"hello"。程序启动就分配好了,只读,不能改。 - 栈或堆:存字符数组,比如
char s[] = "hello"。可以读写,用完就释放。
这两种方式,决定了你能对字符串做什么操作。我见过有人直接修改字符串常量,结果程序崩溃——嗯,这就是没搞清楚存储位置。
核心区别:字符串常量存在只读区,字符数组存在可读写区。前者不能改,后者可以。
5.2 字符指针 vs 字符数组
这两个东西,长得像,但本质不同。你想想看:
char *p = "hello"; // 指针指向字符串常量
char arr[] = "hello"; // 数组,在栈上分配了6个字节
有什么区别?
p是一个指针,指向只读区的"hello"。你不能通过p[0] = 'H'去改它。arr是一个数组,它在栈上拷贝了一份"hello"。你可以随便改。p可以指向别处,arr不行——数组名是常量地址。
我在项目中遇到过一个问题:有人用 char *p = "hello",然后试图用 strcpy 往里面写数据。结果呢?段错误。因为 p 指向的是只读区,根本写不进去。
注意:字符指针指向字符串常量时,千万不要尝试修改内容。这是未定义行为,编译器不报错,但运行时可能崩溃。
5.3 字符串处理函数的指针实现
很多库函数,比如 strcpy、strlen、strcmp,底层都是用指针操作的。理解它们的指针实现,能帮你更深刻地理解字符串。
5.3.1 strlen 的指针实现
计算字符串长度,说白了就是数到'\0'为止。
size_t my_strlen(const char *s) {
const char *p = s;
while (*p != '\0') {
p++;
}
return p - s;
}
这里用了一个技巧:两个指针相减,得到的是它们之间的元素个数。我习惯用这种方式,比用计数器更简洁。
5.3.2 strcpy 的指针实现
复制字符串,把源串的每个字符拷到目标串,包括最后的'\0'。
char *my_strcpy(char *dest, const char *src) {
char *ret = dest;
while ((*dest++ = *src++) != '\0') {
// 空循环体
}
return ret;
}
这段代码很经典。赋值表达式 *dest++ = *src++ 的值就是被赋的字符,当它是'\0'时循环结束。嗯,这里要注意:dest 必须有足够的空间,否则会越界。
小技巧:写 strcpy 时,先检查 dest 和 src 是否重叠。如果重叠,用 memmove 更安全。
5.3.3 strcmp 的指针实现
比较两个字符串,按字典序。
int my_strcmp(const char *s1, const char *s2) {
while (*s1 != '\0' && *s1 == *s2) {
s1++;
s2++;
}
return (unsigned char)*s1 - (unsigned char)*s2;
}
为什么用 unsigned char?因为 char 可能是有符号的,直接相减可能得到负数。我曾经在这个细节上吃过亏——比较两个字符串,结果返回值不对,查了半天才发现是符号扩展的问题。
5.4 字符串常量与指针
字符串常量,比如 "hello",在C语言中是一个 char * 类型的表达式。它的值是指向该字符串首字符的指针。
printf("%s\n", "hello"); // 输出 hello
printf("%c\n", *"hello"); // 输出 h
printf("%c\n", "hello"[1]); // 输出 e
你看,字符串常量可以直接用下标访问。这是因为编译器把它当作一个数组,只不过存在只读区。
但要注意:
char *p = "hello";
p[0] = 'H'; // 错误!字符串常量不可修改
为什么?因为 "hello" 在只读区,你试图写它,操作系统会给你一个段错误。我刚开始学C时,就犯过这个错——想当然地以为字符串常量可以改,结果程序崩了。
总结一下:
- 字符串常量是只读的,用字符指针指向它时,不能修改内容。
- 字符数组是可读写的,用字符指针指向它时,可以修改内容。
- 字符串处理函数的指针实现,核心就是遍历字符直到'\0'。
- 指针相减得到元素个数,这个技巧很实用。
5.5 知识体系图
下面这张图,把本章的核心逻辑串起来了。你可以对照着看,理解字符串、指针、存储区之间的关系。
这张图把本章的四个核心主题串在了一起。你从左上角开始看:字符串存在哪里?决定了你能不能改。字符指针和字符数组,本质区别是什么?处理函数怎么用指针实现?最后,字符串常量为什么不能改?把这些搞明白,指针与字符串这块就算通了。
我的建议:平时写代码时,多想想字符串存在哪里。是只读区还是栈?用指针还是数组?想清楚了,很多bug自然就避免了。
公众号:蓝海资料掘金营,微信deep3321