6. 指针与字符串:字符串指针与字符数组、字符串常量的指针表示、字符串处理函数

字符串在C语言里,其实是个挺有意思的话题。很多初学者会困惑:明明C语言没有专门的字符串类型,那我们平时用的字符串到底是什么?说白了,字符串就是字符数组,只不过末尾多了个'\0'。今天我就带你把这层窗户纸捅破。

6.1 字符数组 vs 字符串指针

先看两个定义:

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

这两行代码,看着差不多,但内存布局完全不同。我当年刚入行时,就在这上面栽过跟头。

字符数组str1 是在栈上分配了6个字节的空间(5个字符 + 1个'\0'),内容可以修改。

字符串指针str2 指向的是常量区的一个字符串字面量。这个区域是只读的,你敢写它,程序就敢崩。

核心区别

  • 字符数组:可读可写,内存分配在栈上
  • 字符串指针:指向常量区,只读不可写

举个例子你就明白了:

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

str1[0] = 'h';  // 没问题,可以改
str2[0] = 'h';  // 运行时崩溃!常量区不可写

嗯,这里要注意。很多教科书上只讲语法,不讲背后的内存模型。我建议你写代码时,如果确定不会修改字符串内容,就用指针形式;如果需要修改,就用数组形式。

6.2 字符串常量的指针表示

字符串常量,比如 "Hello",在C语言中是什么类型?答案是 const char *。它存储在程序的只读数据段(.rodata)里。

你想想看:

const char *p = "Hello";
printf("%p\n", p);      // 打印常量区的地址
printf("%c\n", *p);     // 输出 'H'
printf("%c\n", *(p+1)); // 输出 'e'

指针可以像数组一样用下标访问:

printf("%c\n", p[0]); // 输出 'H'
printf("%c\n", p[1]); // 输出 'e'

为什么会这样?因为 p[i] 本质上就是 *(p+i)。数组和指针在访问元素时,底层是同一套机制。

个人经验:我在做嵌入式协议栈时,经常用字符串指针指向协议头部的固定字符串。比如HTTP的"GET"、"POST"这些关键字,用指针指向常量区,既省内存又安全。

6.3 字符串处理函数

C标准库提供了一组字符串处理函数,都在 <string.h> 里。我挑几个最常用的讲讲。

6.3.1 strlen() — 求长度

char str[] = "Hello";
size_t len = strlen(str);  // len = 5,不包含'\0'

注意,strlen() 返回的是 size_t,不是 int。我见过有人写 int len = strlen(str),在64位系统上可能出问题。

6.3.2 strcpy() 与 strncpy() — 拷贝

char dest[10];
char *src = "Hello";

strcpy(dest, src);   // 危险!不检查目标缓冲区大小
strncpy(dest, src, sizeof(dest) - 1); // 安全,留一个位置给'\0'
dest[sizeof(dest) - 1] = '\0';        // 确保以'\0'结尾

避坑指南:我曾经在一个项目中,因为用了 strcpy() 没检查缓冲区大小,导致堆栈被破坏,查了整整两天才找到原因。从那以后,我写代码只用 strncpy()snprintf()

6.3.3 strcmp() — 比较

if (strcmp(str1, str2) == 0) {
    // 两个字符串相等
}

千万别用 == 比较字符串!那比较的是指针地址,不是内容。我见过新手这样写:

char str1[] = "Hello";
char str2[] = "Hello";
if (str1 == str2) {  // 永远为假!比较的是两个数组的首地址
    // ...
}

6.3.4 strcat() 与 strncat() — 拼接

char buf[20] = "Hello";
strncat(buf, " World", sizeof(buf) - strlen(buf) - 1);

拼接时一定要留够空间,包括最后的 '\0'

6.4 知识体系图

下面这张图,帮你理清指针与字符串的核心关系:

指针与字符串知识体系 字符串在C语言中 字符数组 char str[] 字符串指针 char *str 字符串处理函数 栈上分配,可读可写 内容可修改,末尾有'\0' 指向常量区,只读不可写 适合表示固定字符串 strlen / strcpy / strcmp strcat / strncpy / strncat 核心:理解内存模型,避免缓冲区溢出

6.5 实战建议

说了这么多,我总结几条实战经验:

  • 能用 const 就用 const:如果指针指向的是字符串常量,加上 const 修饰,编译器能帮你检查出很多潜在错误。
  • 优先用 strncpy() 而不是 strcpy():安全第一,别图省事。
  • 字符串比较用 strcmp():别用 ==,那是比较地址。
  • 注意 '\0' 的存在:分配缓冲区时,永远多留一个字节。

我的习惯:在嵌入式项目中,我经常用 snprintf() 替代 strcpy()strcat()。它既能格式化输出,又能指定最大长度,一个函数搞定所有字符串拼接需求。

好了,指针与字符串这块,说白了就是搞清楚内存里数据是怎么放的。数组在栈上,常量在只读区,函数帮你做操作。搞明白这些,字符串这块你就拿下了。


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