指针与字符串:从入门到避坑
大家好,我是你们的老朋友。今天我们来聊聊指针与字符串这个话题。说实话,我在嵌入式这行摸爬滚打十几年,见过太多人在字符串和指针上栽跟头了。我自己刚入行那会儿,也曾经因为一个字符串拷贝的问题,调试了整整一个通宵。嗯,从那以后,我对这块的理解就特别深了。
字符串在C语言里,其实是个挺特殊的存在。它不像int、float那样是原生类型,而是通过字符数组来表达的。说白了,字符串就是字符数组加上一个结束标志'\0'。这个设计,既灵活又容易出问题。
字符串常量与字符数组
先说说字符串常量。你写"hello"的时候,编译器会在内存的只读区放这么一串字符。这个区域,你是不能改的。我有个同事曾经写过这样的代码:
char *p = "hello";
p[0] = 'H'; // 运行时崩溃!
为什么?因为"hello"存在只读区,你试图修改它,操作系统直接给你个段错误。我当时在做一个嵌入式项目,板子上跑着跑着就挂了,查了半天才发现是这个问题。
那字符数组呢?它是在栈上或者全局区分配的内存,你可以随便改:
char arr[] = "hello";
arr[0] = 'H'; // 没问题,arr在栈上
这里有个关键区别:arr是一个数组,它有自己的内存空间,会把"hello"拷贝过来。而p只是一个指针,指向了只读区的那个字符串。
核心区别:
- 字符串常量:只读,不可修改,存储在代码段/只读数据段
- 字符数组:可读写,存储在栈或全局区,是字符串常量的副本
字符指针与字符串
字符指针操作字符串,其实玩的就是地址游戏。你看这个:
char *p = "hello";
char *q = "hello";
你猜p == q是真是假?答案是:可能真,也可能假。这取决于编译器是否做了字符串常量合并。我在GCC上试过,默认是合并的,所以p和q指向同一个地址。但在某些嵌入式编译器上,它们可能指向不同的地址。
我个人习惯是:绝不依赖这种实现细节。要比较字符串内容,老老实实用strcmp。
字符指针的另一个常见用法是遍历字符串:
char *p = "hello";
while (*p) {
putchar(*p);
p++;
}
这里*p就是当前字符,当遇到'\0'时,*p为0,循环结束。简洁高效,对吧?
字符串处理函数的指针实现
很多初学者觉得strcpy、strlen这些函数很神秘。其实说白了,它们就是指针操作的封装。我建议你亲手实现一遍,这样理解会深很多。
先看strlen:
size_t my_strlen(const char *s) {
const char *p = s;
while (*p) p++;
return p - s;
}
这里用了一个技巧:先让p跑到字符串末尾,然后两个指针相减,得到的就是字符个数。指针相减返回的是ptrdiff_t类型,表示两个指针之间的元素个数。
再看strcpy:
char *my_strcpy(char *dest, const char *src) {
char *ret = dest;
while (*dest++ = *src++);
return ret;
}
这段代码很经典。赋值表达式*dest++ = *src++的值就是被赋的那个字符。当拷贝到'\0'时,赋值结果为0,循环结束。注意,这里把'\0'也拷贝过去了。
我曾经踩过的坑:目标缓冲区不够大!strcpy不会检查缓冲区大小,如果源字符串比目标缓冲区长,就会发生缓冲区溢出。这是安全漏洞的常见来源。我建议用strncpy或者自己加长度检查。
最后看strcat:
char *my_strcat(char *dest, const char *src) {
char *ret = dest;
while (*dest) dest++;
while (*dest++ = *src++);
return ret;
}
先找到dest的末尾,然后从那里开始拷贝src。逻辑很直接,但同样有缓冲区溢出的风险。
const char* 与 char* const
这两个东西,我当年也迷糊过一阵子。其实记住一个口诀就行:先看const修饰谁。
| 写法 | 含义 | 示例 |
|---|---|---|
const char *p |
指针指向的内容不可改 | *p = 'A'; 非法 |
char * const p |
指针本身不可改 | p++; 非法 |
const char * const p |
内容和指针都不可改 | 两者都非法 |
怎么记?你看const char *p,const修饰的是char,所以字符不能改。char * const p,const修饰的是p,所以指针不能改。
在实际项目中,我经常用const char *来传递字符串参数,告诉调用者:你放心,我不会改你的数据。这是一种契约,也是一种好习惯。
小技巧:从右往左读声明。比如const char * const p:p是一个const指针,指向const char。这样就不会搞混了。
知识体系图
下面这张图,是我梳理的本章知识脉络。你看一眼,心里就有谱了。
这张图把本章的核心内容串起来了。从字符串常量和字符数组的区别,到字符指针的操作,再到字符串函数的指针实现,最后是const的修饰规则。你顺着这个脉络走一遍,基本就掌握了。
总结
指针与字符串这块,说白了就是两件事:内存在哪里,能不能改。字符串常量在只读区,不能改;字符数组在栈上,能改。字符指针就是个地址,你可以用它遍历、拷贝、拼接字符串。const修饰符帮你把意图说清楚,也让编译器帮你检查错误。
我个人觉得,学C语言指针,最好的方法就是多写、多调试、多踩坑。踩过的坑,记得最牢。希望今天的分享能帮你少走一些弯路。