指针与字符串:字符串指针,字符指针数组,const修饰指针

指针和字符串,这两个东西放在一起,是C语言里最常用、也最容易出bug的地方。我个人习惯把这一块叫做「C语言的灵魂地带」——搞懂了它,你才算真正入了C的门。

今天咱们就聊聊三个核心概念:字符串指针、字符指针数组、还有const修饰指针。嗯,别被名字吓到,说白了就是「怎么用指针去玩字符串」这件事。

一、字符串指针:其实字符串就是字符数组

先问个问题:C语言里有字符串类型吗?没有。那字符串是什么?说白了,就是一个以'\0'结尾的字符数组。

所以你可以这样定义一个字符串:

char str[] = "Hello";

这没问题。但更常见的写法是:

char *p = "Hello";

这两者有什么区别?我刚开始学的时候也搞混过。后来在项目里踩了一次坑才彻底明白——str[]是在栈上分配了一块内存,你可以修改里面的字符;而p指向的是一个字符串常量,存在只读区,你敢改它,程序就敢崩。

注意: 字符串常量是只读的。不要试图通过指针去修改它。我曾经在嵌入式项目里这么干过,结果直接hard fault,查了半天才发现是这里的问题。

看个例子:

char str[] = "Hello";
str[0] = 'h';  // 合法,可以修改

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

为什么会这样?因为p指向的内存区域是只读的。你想想看,编译器把字符串常量放在了代码段或者只读数据段,你硬要去写它,操作系统当然不答应。

二、字符指针数组:存多个字符串的利器

有时候我们需要存一堆字符串,比如一个菜单的选项列表。你会怎么做?用二维数组?

char menu[3][10] = {"Open", "Save", "Exit"};

这样写没问题,但浪费空间。每个字符串都占10个字节,短的字符串后面全是'\0'填充。我建议用字符指针数组:

char *menu[] = {"Open", "Save", "Exit"};

这样每个指针只占4个字节(32位系统),字符串本身存在只读区,不浪费任何空间。我在做嵌入式菜单系统时,经常用这种结构,省内存效果很明显。

遍历起来也很方便:

for (int i = 0; i < 3; i++) {
    printf("%s\n", menu[i]);
}

嗯,这里要注意一点:menu本身是一个数组,数组名就是首元素的地址。所以menu的类型是char **,指向指针的指针。这个后面讲指针数组时会细说。

三、const修饰指针:保护你的数据

const这个关键字,说白了就是「只读」的意思。但它放在不同位置,含义完全不同。我见过不少同事在这上面栽跟头。

咱们分三种情况来看:

写法 含义 能否修改指针本身 能否修改指向的数据
const char *p 指向常量的指针 可以 不可以
char * const p 常量指针 不可以 可以
const char * const p 指向常量的常量指针 不可以 不可以

怎么记?我有个小技巧:看const和*的位置。const在*左边,修饰的是指向的数据;const在*右边,修饰的是指针本身。

举个例子:

char str[] = "Hello";

const char *p1 = str;   // 不能通过p1修改str的内容
p1 = "World";           // 可以,p1可以指向别处

char * const p2 = str;  // p2只能指向str
*p2 = 'h';              // 可以,通过p2可以修改str

const char * const p3 = str;  // 既不能改指向,也不能改内容

我在项目中经常用const char *作为函数参数,告诉调用者:「你放心,我不会改你的数据」。这是一种很好的接口设计习惯。

小技巧: 写函数接口时,如果参数是字符串且不需要修改,尽量用const char *。这样既安全,又让代码意图更清晰。

四、知识体系总览

下面这张图把今天讲的内容串起来了,你可以对照着看:

指针与字符串知识体系 字符串指针 字符指针数组 const修饰指针 char str[] = "Hello"; char *p = "Hello"; ⚠ 常量区不可修改 char *menu[] = {...}; 省内存,方便遍历 类型:char ** const char *p char * const p const char * const p 核心:理解内存布局,区分可读可写区域 字符串常量只读 | 字符指针数组灵活 | const让接口更安全

五、避坑指南

最后分享几个我踩过的坑:

  • 不要用scanf直接读字符串指针——指针没有分配内存,读进去的数据没地方放。先分配数组或者用malloc
  • 字符串比较用strcmp,不要用==——==比较的是指针地址,不是内容。我见过有人写if (p == "Hello"),结果永远不成立。
  • const的位置一定要看清楚——写错了编译不过还好,最怕的是编译过了但逻辑不对。
  • 字符指针数组的最后一个元素可以置为NULL——这样遍历时就不用记长度了,遇到NULL就停。很多库函数都这么用。

一句话总结:字符串指针指向的是内存地址,字符指针数组存的是多个地址,const修饰的是访问权限。搞懂这三者的关系,C语言的字符串操作你就掌握了八成。

好了,今天就聊到这儿。这些东西看起来简单,但真正用好需要多写多练。下次遇到字符串相关的问题,记得回来翻翻这篇。