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

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

  2. const char* 的含义

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

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

  5. sizeof 和 strlen 的区别

  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";

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

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.";

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

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

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

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

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

text

复制代码
str1 and str2 are not same

第二个比较

c

复制代码
str3 == str4

str3 和 str4 是 const 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 相同,也可能不同

关键区别:

  • str1、str2 是数组,各自有独立空间。

  • str3、str4 是指针,指向字面量。

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

  • 内容比较用 strcmp。

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


小结

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

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

  • sizeof("abcdef") == 7,strlen("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 比内容;字符串字面量能不能合并,标准说了不算,编译器说了算,所以别依赖

相关推荐
Escalating_xu7 小时前
【C 语言】数据在内存中的存储:补码、大小端、整型陷阱与 IEEE 754 全解析
java·c语言·网络
神仙别闹14 小时前
基于C语言开发的捕捉流星小游戏
c语言·开发语言
水饺编程15 小时前
第1章,[Win32 章节]:API 及 内存管理模式
c语言·c++·windows·visual studio
Logic10115 小时前
C语言/数据结构位运算题解:异或XOR找出优秀项目组中的“独特编号“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
彦16 小时前
C和C++笔记
c语言·c++·笔记
Code_Solitude17 小时前
反码,补码,原码
c语言
FLJwu17 小时前
运动相机按键与连接器硬件选型实战02:USB‑C接口选型|防水失效、虚接断连、腐蚀烧口量产大坑
c语言·开发语言·经验分享·数码相机·相机·运动相机·oem 量产
Zw-awa19 小时前
陀螺小车传奇之00011000
c语言·stm32
万年咸鱼19 小时前
C语言内功心法篇——变量与数据类型
c语言·jvm·算法
Escalating_xu1 天前
【C 语言】深入理解指针(5):sizeof、strlen、数组名语义与指针笔试题全解析
java·c语言·开发语言