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 字符串处理函数的指针实现

很多库函数,比如 strcpystrlenstrcmp,底层都是用指针操作的。理解它们的指针实现,能帮你更深刻地理解字符串。

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 时,先检查 destsrc 是否重叠。如果重叠,用 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 知识体系图

下面这张图,把本章的核心逻辑串起来了。你可以对照着看,理解字符串、指针、存储区之间的关系。

指针与字符串知识体系 字符串存储方式 静态存储区(只读) 栈/堆(可读写) 字符指针 vs 字符数组 指针可指向别处 数组名是常量地址 字符串处理函数 strlen:指针遍历 strcpy:指针赋值 strcmp:指针比较 字符串常量与指针 字符串常量是只读的,不能修改;字符数组可读写

这张图把本章的四个核心主题串在了一起。你从左上角开始看:字符串存在哪里?决定了你能不能改。字符指针和字符数组,本质区别是什么?处理函数怎么用指针实现?最后,字符串常量为什么不能改?把这些搞明白,指针与字符串这块就算通了。

我的建议:平时写代码时,多想想字符串存在哪里。是只读区还是栈?用指针还是数组?想清楚了,很多bug自然就避免了。


公众号:蓝海资料掘金营,微信deep3321