-
字符指针
char*与字符串字面量 -
const char*的含义 -
字符串末尾隐藏的
'\0' -
"abcdef"[i]为什么合法 -
sizeof和strlen的区别 -
字符数组 vs 字符指针
-
数组名退化与地址比较
-
==比较地址,strcmp比较内容 -
字符串字面量是否合并:标准未指定
-
修改字符串字面量是未定义行为
-
栈区、只读数据区/常量区的概念
-
const的三种位置 -
C 和 C++ 对字符串字面量类型的差异
-
未使用变量警告
-
笔试题输出分析
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比内容;字符串字面量能不能合并,标准说了不算,编译器说了算,所以别依赖