指针与字符串:字符串指针,字符指针数组,const修饰指针
指针和字符串,这两个东西放在一起,是C语言里最常用、也最容易出bug的地方。我个人习惯把这一块叫做「C语言的灵魂地带」——搞懂了它,你才算真正入了C的门。
今天咱们就聊聊三个核心概念:字符串指针、字符指针数组、还有const修饰指针。嗯,别被名字吓到,说白了就是「怎么用指针去玩字符串」这件事。
一、字符串指针:其实字符串就是字符数组
先问个问题:C语言里有字符串类型吗?没有。那字符串是什么?说白了,就是一个以'\0'结尾的字符数组。
所以你可以这样定义一个字符串:
char str[] = "Hello";
这没问题。但更常见的写法是:
char *p = "Hello";
这两者有什么区别?我刚开始学的时候也搞混过。后来在项目里踩了一次坑才彻底明白——str[]是在栈上分配了一块内存,你可以修改里面的字符;而p指向的是一个字符串常量,存在只读区,你敢改它,程序就敢崩。
看个例子:
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 *。这样既安全,又让代码意图更清晰。
四、知识体系总览
下面这张图把今天讲的内容串起来了,你可以对照着看:
五、避坑指南
最后分享几个我踩过的坑:
- 不要用
scanf直接读字符串指针——指针没有分配内存,读进去的数据没地方放。先分配数组或者用malloc。 - 字符串比较用
strcmp,不要用==——==比较的是指针地址,不是内容。我见过有人写if (p == "Hello"),结果永远不成立。 - const的位置一定要看清楚——写错了编译不过还好,最怕的是编译过了但逻辑不对。
- 字符指针数组的最后一个元素可以置为NULL——这样遍历时就不用记长度了,遇到NULL就停。很多库函数都这么用。
一句话总结:字符串指针指向的是内存地址,字符指针数组存的是多个地址,const修饰的是访问权限。搞懂这三者的关系,C语言的字符串操作你就掌握了八成。
好了,今天就聊到这儿。这些东西看起来简单,但真正用好需要多写多练。下次遇到字符串相关的问题,记得回来翻翻这篇。