C语言字符指针与字符串字面量——从一道“剑指 Offer”笔试题说起

  1. 字符指针 char* 与字符串字面量

  2. const char* 的含义

  3. 字符串末尾隐藏的 '\0'

  4. "abcdef"[i] 为什么合法

  5. sizeofstrlen 的区别

  6. 字符数组 vs 字符指针

  7. 数组名退化与地址比较

  8. == 比较地址,strcmp 比较内容

  9. 字符串字面量是否合并:标准未指定

  10. 修改字符串字面量是未定义行为

  11. 栈区、只读数据区/常量区的概念

  12. const 的三种位置

  13. C 和 C++ 对字符串字面量类型的差异

  14. 未使用变量警告

  15. 笔试题输出分析


1. 字符指针基础

你的代码:

c

复制代码
char c = 'a';
const char* pc = "abcdef";

for (i = 0; i < 6; i++)
{
    printf("%c ", "abcdef"[i]);
}

这里 "abcdef" 是一个字符串字面量。它本质上是一个字符数组,内容为:

text

复制代码
'a' 'b' 'c' 'd' 'e' 'f' '\0'

长度是 6,占用 7 个字节。

在 C 语言中,字符串字面量的类型是 char[N],但是修改它是未定义行为。所以更推荐用:

c

复制代码
const char* pc = "abcdef";

表示:pc 指向的字符内容不打算通过它修改。

pc 指向首字符 'a'

c

复制代码
pc     -> 'a'
pc + 1 -> 'b'
pc + 2 -> 'c'
...
pc[0] == 'a'
pc[1] == 'b'
*(pc + 1) == 'b'

注意:

c

复制代码
sizeof(pc);      // 指针大小,64 位通常是 8
strlen(pc);      // 6
sizeof("abcdef");// 7,包括 '\0'
strlen("abcdef");// 6,不包括 '\0'

pc 本身是一个指针变量,它可以改变指向:

c

复制代码
pc = "xyz"; // 可以,改变 pc 的指向

但不能通过它修改指向的内容:

c

复制代码
pc[0] = 'x'; // 编译错误,因为 pc 是 const char*

2. "abcdef"[i] 为什么合法?

因为字符串字面量在表达式中会退化为指向首字符的指针,所以:

c

复制代码
"abcdef"[i]

等价于:

c

复制代码
*("abcdef" + i)

也等价于:

c

复制代码
*(pc + i)
pc[i]

所以你的循环:

c

复制代码
for (i = 0; i < 6; i++)
{
    printf("%c ", "abcdef"[i]);
}

会输出:

text

复制代码
a b c d e f

如果写成 i <= 6,第 7 次会访问 '\0''\0' 是空字符,终端通常看不见,但它是字符串结束标志。

另外,你的代码里定义了:

c

复制代码
char c = 'a';
const char* pc = "abcdef";

但循环里没有使用 cpc,有些编译器会警告"未使用变量"。更直观的写法是:

c

复制代码
for (i = 0; i < 6; i++)
{
    printf("%c ", pc[i]);
}

3. 字符数组 vs 字符指针

这是字符指针笔试题的核心。

字符数组

c

复制代码
char str1[] = "hello bit.";
char str2[] = "hello bit.";

这两个是数组。编译器会在栈上给它们各自分配空间,并把字符串内容复制进去。它们的内容相同,但地址不同。

可以修改:

c

复制代码
str1[0] = 'H'; // 可以

因为这是数组,数组里的内容属于你自己。

字符指针

c

复制代码
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";

这两个是指针变量。它们指向字符串字面量。字符串字面量通常放在只读数据区或常量区。相同内容的字面量是否合并,由编译器实现决定。

常见编译器可能会让 str3str4 指向同一块地址:

text

复制代码
str3 ---> "hello bit."
str4 ---> "hello bit."

也可能不合并:

text

复制代码
str3 ---> "hello bit."  (地址 A)
str4 ---> "hello bit."  (地址 B)

标准没有规定必须相同,也没有规定必须不同。


4. 笔试题分析

你的笔试题:

c

复制代码
char str1[] = "hello bit.";
char str2[] = "hello bit.";

const char* str3 = "hello bit.";
const char* str4 = "hello bit.";

if (str1 == str2)
    printf("str1 and str2 are same\n");
else
    printf("str1 and str2 are not same\n");

if (str3 == str4)
    printf("str3 and str4 are same\n");
else
    printf("str3 and str4 are not same\n");

第一个比较

c

复制代码
str1 == str2

str1str2 是数组名。在 == 中,数组名退化为指向首元素的指针。所以这里比较的是两个数组首元素的地址。

因为 str1str2 是两个独立的数组,地址不同,所以:

text

复制代码
str1 and str2 are not same

第二个比较

c

复制代码
str3 == str4

str3str4const char* 指针,它们保存的是字符串字面量的地址。这里比较的是指针值,也就是地址。

常见编译器会合并相同字符串字面量,所以 str3 == str4 可能为真:

text

复制代码
str3 and str4 are same

但这不是标准保证的。如果编译器不合并,也可能输出:

text

复制代码
str3 and str4 are not same

所以这个笔试题的经典答案是:

text

复制代码
str1 and str2 are not same
str3 and str4 are same   // 常见环境,但不保证

如果要比较内容

应该用 strcmp

c

复制代码
#include <string.h>

if (strcmp(str1, str2) == 0)
    printf("内容相同\n");

if (strcmp(str3, str4) == 0)
    printf("内容相同\n");

strcmp 返回 0 表示两个字符串内容相等。


5. 修改字符串字面量是未定义行为

危险写法:

c

复制代码
char* p = "hello";
p[0] = 'H'; // 未定义行为

在 C 中,char* p = "hello"; 可能只是警告,但修改 p[0] 是未定义行为,可能崩溃。因为字符串字面量通常放在只读区。

推荐写法:

c

复制代码
const char* p = "hello";
// p[0] = 'H'; // 编译错误,安全

如果要修改,用字符数组:

c

复制代码
char s[] = "hello";
s[0] = 'H'; // 正确

6. const 的三种常见位置

c

复制代码
const char* p1;       // 指向常量的指针:不能改 *p1,可以改 p1
char* const p2 = s;   // 常量指针:可以改 *p2,不能改 p2
const char* const p3 = "abc"; // 都不能改

记忆方法:

  • const* 左边:修饰指向的内容。

  • const* 右边:修饰指针本身。

  • 两边都有:都修饰。


7. C 和 C++ 的差异

在 C 中:

c

复制代码
char* p = "hello"; // 语法上允许,但修改是未定义行为

在 C++ 中:

c

复制代码
char* p = "hello"; // 错误,字符串字面量类型是 const char[N]

所以更推荐统一写:

c

复制代码
const char* p = "hello";

这样 C 和 C++ 都更安全。


8. 笔试题背后的内存图

text

复制代码
栈区:
str1:  'h' 'e' 'l' 'l' 'o' ' ' 'b' 'i' 't' '.' '\0'
str2:  'h' 'e' 'l' 'l' 'o' ' ' 'b' 'i' 't' '.' '\0'

只读数据区/常量区:
"hello bit." 可能只有一份,也可能有多份

str3 ---> "hello bit."
str4 ---> "hello bit."   // 可能和 str3 相同,也可能不同

关键区别:

  • str1str2 是数组,各自有独立空间。

  • str3str4 是指针,指向字面量。

  • == 比较地址,不是内容。

  • 内容比较用 strcmp

  • 字面量是否合并由实现决定,不要依赖。


小结

  • "abcdef" 是字符数组,末尾有 '\0'

  • "abcdef"[i] 等价于 *("abcdef" + i)

  • sizeof("abcdef") == 7strlen("abcdef") == 6

  • 字符数组可修改,字符串字面量不可修改。

  • const char* 指向的内容不能通过该指针修改。

  • str1 == str2 比较地址,不是内容。

  • strcmp 比较字符串内容。

  • 相同字符串字面量是否合并,标准未指定,常见编译器会合并。

  • 不要写依赖字面量合并的代码。


最后速查表

表达式 类型/含义
int* arr[3] 指针数组,3 个 int*
arr 在表达式中 退化为 int**
arr[i][j] *(*(arr + i) + j)
int a[3][5] 真正二维数组
a 在表达式中 退化为 int(*)[5]
int** 二级指针,可接收指针数组名
int(*)[5] 数组指针,可接收 int[3][5]
char str[] = "abc" 字符数组,可修改
const char* p = "abc" 指向字符串字面量,不可通过 p 修改内容
str1 == str2 比较地址
strcmp(str1, str2) == 0 比较内容

一句话总结:

数组名大多数时候退化为首元素指针,但 sizeof& 除外;指针数组不是二维数组,int** 也不是 int(*)[N]== 比地址,strcmp 比内容;字符串字面量能不能合并,标准说了不算,编译器说了算,所以别依赖

相关推荐
free-elcmacom6 小时前
C语言指针进阶——指针数组模拟二维数组,以及二级指针到底怎么理解
c语言·visual studio code
山下梅子酒2257 小时前
洛谷-入门-B2049
c语言
剑指offer.9 小时前
嵌入式硬件-ARM芯片的启动
c语言·嵌入式硬件·嵌入式
辰辉创聚10 小时前
重组蛋白聚集的形成机制及其对蛋白活性的影响
opencv·时序数据库·visual studio·重组蛋白·蛋白聚集·蛋白活性·蛋白聚集体
Logic10110 小时前
C语言/数据结构动态规划题解:Kadane算法求最大子数组和——O(n)时间O(1)空间
c语言·数据结构·动态规划·贪心·时间复杂度·算法题·最大子数组和
雷✘11 小时前
C 程序从源文件到运行:预处理、编译、汇编、链接重定位与执行环境
c语言
Logic10111 小时前
C语言/数据结构位运算题解:异或XOR找出数据表中的“独特编号“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
endeavor1212 小时前
BootLoader与OTA学习(三)
c语言·stm32
布莱克60513 小时前
理解内存泄漏:成因、发现方法与解决策略(C++ 举例)
c语言·c++·内存泄漏