20、字符串与指针:字符串指针与字符数组的区别、字符串常量、指针操作字符串

字符串在C语言里,说简单也简单,说复杂也复杂。很多新手甚至工作一两年的朋友,都在这上面栽过跟头。我个人习惯把字符串看作C语言里的一堵墙——翻过去,指针这块基本就通了;翻不过去,后面写代码总是提心吊胆。

今天咱们就把这堵墙拆了。重点聊三个东西:字符串指针字符数组,还有那个让人又爱又恨的字符串常量

字符串指针 vs 字符数组:到底哪里不一样?

先看两行代码:

char str1[] = "Hello";
char *str2 = "Hello";

乍一看,好像差不多。都是存了个"Hello"嘛。但实际在内存里,完全是两码事。

字符数组:在栈上分配了一块连续内存,每个字符挨个放进去。你可以修改里面的内容。

字符串指针:指针本身在栈上,但它指向的是字符串常量区。那块内存是只读的,你敢改,程序就敢崩。

我在项目中遇到过一位同事,写了这样的代码:

char *p = "Hello";
p[0] = 'h';  // 运行时崩溃

他当时一脸懵:明明编译过了啊?嗯,编译能过,但运行时就给你个下马威。这就是典型的字符串常量不可修改的问题。

⚠️ 我曾经... 在嵌入式项目里,因为把字符串常量当数组改,导致整个系统跑飞。从那以后,我只要看到 char *p = "xxx",第一反应就是:这玩意儿只能读,不能写。

咱们用个表格对比一下:

对比项 字符数组 char arr[] 字符串指针 char *p
内存位置 栈(或全局区) 指针在栈,指向常量区
是否可修改 可以 不可以(常量区只读)
大小 sizeof(arr) 得到数组大小 sizeof(p) 得到指针大小(4或8字节)
赋值 不能整体赋值(如 arr = "abc" 不行) 可以重新指向别的字符串
作为函数参数 退化为指针 本身就是指针
💡 小技巧: 如果你不确定一个字符串能不能改,就用 const char * 声明。这样编译器会帮你拦住修改操作,而不是等到运行时才崩溃。

字符串常量:藏在代码里的"钉子户"

字符串常量,比如 "Hello",在程序启动时就被加载到内存的只读段。你写多少次 "Hello",编译器可能只保留一份副本。这叫"字符串常量池"。

你想想看:

char *p1 = "Hello";
char *p2 = "Hello";
if (p1 == p2) {
    printf("指向同一个地址\n");
}

在很多编译器上,这个判断是成立的。p1 和 p2 指向了同一块只读内存。但如果你用字符数组:

char arr1[] = "Hello";
char arr2[] = "Hello";
if (arr1 == arr2) {
    printf("不会执行\n");
}

arr1 和 arr2 是两块不同的栈内存,地址当然不同。这里比较的是数组首地址,不是内容。

🔑 核心要点: 字符串常量是"一次分配,多处共享"。字符数组是"每次定义,独立拷贝"。

指针操作字符串:灵活但容易翻车

用指针操作字符串,说白了就是玩地址。我建议你先把指针的加减搞明白,再上手。

看个例子:

char str[] = "Hello";
char *p = str;

while (*p != '\0') {
    putchar(*p);
    p++;  // 指针后移一个字符
}

这段代码逐个打印字符。p 一开始指向 'H',然后 p++ 指向 'e',直到遇到 '\0' 结束。

为什么会这样?因为字符串在内存里是连续存放的,最后一个字符后面跟着一个 '\0'。这是C语言的约定,也是字符串的"结束标志"。

我再给你看个常用的操作——字符串拷贝:

void my_strcpy(char *dest, const char *src) {
    while (*dest++ = *src++)
        ;  // 空循环体
}

这行代码看着有点绕,但效率很高。它做了三件事:赋值、判断、自增。当 src 指向 '\0' 时,赋值给 dest 后,表达式的值为 0,循环结束。

⚠️ 注意: 用指针操作字符串时,一定要确保目标缓冲区够大。我曾经在写网络协议解析时,因为没检查缓冲区大小,导致内存越界,查了整整两天才找到问题。从那以后,我写字符串操作必加长度检查。

知识体系梳理

下面这张图,把本章的核心逻辑串起来了。建议你多看几遍,把每个节点的关系理清楚。

字符串与指针知识体系 字符串与指针 字符数组 字符串指针 字符串常量 栈上分配,可修改 sizeof 得到数组大小 不能整体赋值 指针在栈,指向常量区 sizeof 得到指针大小 可以重新指向 只读,不可修改 常量池共享 程序启动时加载 指针操作字符串:灵活但需注意边界

避坑指南

  • 不要用 == 比较字符串内容——它比较的是地址,不是字符。用 strcmp()
  • 字符串指针初始化后,别想着改内容——除非你确定它指向的是可写内存。
  • 字符数组作为函数参数时,会退化为指针——所以在函数里用 sizeof 得到的是指针大小,不是数组大小。
  • 字符串常量末尾自动带 '\0'——但如果你自己拼字符数组,别忘了手动加。
💡 我的习惯: 在团队里,我要求所有字符串参数都加 const 修饰,除非明确需要修改。这样一看代码就知道:哦,这个函数不会改我的字符串。代码的可读性和安全性都上去了。

好了,字符串与指针这块,说白了就是搞清楚"谁在栈上,谁在常量区,谁能改,谁不能改"。你把这个理清了,后面再遇到字符串相关的问题,心里就有底了。


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