很多人刚接触 C 语言指针时,最容易混淆的就是“字符指针里装的是字符,还是字符串”。这一节可以先把最常见的 `char*`、`const char*` 用法拆开看,再结合字符串常量、字符数组和 `[]` 运算的例子,弄清它们在内存中的差异,以及代码里为什么会出现“内容一样但地址不一样”或“地址反而相等”的情况。
字符指针到底存的是什么
先看最基础的两种写法:
char ch = 'w';
char *pc = &ch;
*pc = 'w';
const char* pstr = "hello world";
printf("%sn", pstr);
这里要分清两件事:
- `char *pc = &ch;`:`pc` 保存的是变量 `ch` 的地址,解引用 `*pc` 后可以访问或修改这个字符。
- `const char* pstr = "hello world";`:`pstr` 里保存的不是整个字符串本身,而是字符串首字符的地址。
也就是说,字符串并不是“被放进了指针变量里”,真正放进指针变量里的,只是这段字符串起始位置的地址。
`const` 在这里也很关键。`const char* pstr` 表示通过 `pstr` 不能修改它指向的字符内容,因此通常用于指向字符串常量。初学时还常会把下面两种写法混在一起:
- `const char * p`:限制的是 `*p`,也就是指向的内容不能改。
- `char * const p`:限制的是 `p`,也就是指针变量本身不能改成别的地址。
这两个声明位置很像,但含义并不一样。
字符指针如何表示字符串
字符指针经常被用来表示一串字符,原因就在于:只要拿到首字符地址,就能顺着内存把后面的字符读出来。
//字符指针是一个变量,用于存放字符串的首地址。
char* ps = "dad";//本质上是把字符串的首字符的地址存储在指针中
char arr[] = "aaa aa";
//字符串中每个字符都有一个内存地址,而字符串的首字符地址就代表了整个字符串的位置。
printf("%sn", ps);//%s打印字符:给出首字符地址,就可以打印整个字符串。
printf("%sn", arr);
printf("%cn", *ps);
这段代码里:
- `ps` 指向字符串常量 `"dad"` 的首字符地址。
- `arr` 是一个字符数组,数组里实实在在存放了 `"aaa aa"` 这串内容。
- `printf("%s", ...)` 这一类输出,本质上就是从首字符地址开始,依次读取字符,直到遇到字符串结束标记。
- `*ps` 取出的则只是首字符,所以 `printf("%c", *ps);` 只会打印一个字符。
这也是为什么字符指针看起来像“保存了一个字符串”,但本质上仍然只是保存地址。
字符数组和字符指针有什么区别
很多入门问题都集中在这里。下面这段代码最值得仔细看:

char str1[] = "hello C";//数组先开辟一块空间,再存放内容
char str2[] = "hello C";
const char* str3 = "hello C";
//*str3 = 'w';//常量字符串的内容不能改变
char* str4 = "hello C";
//*str4 = 'w';
if (str1 == str2)
{
printf("1=2");
}
if (str3 == str4)
{
printf("3=4");
}
结论是:会输出 `3=4`,而不会输出 `1=2`。
原因可以拆成两部分理解。
字符数组会各自拥有独立空间
`str1` 和 `str2` 都是数组。定义数组时,会先各自开辟一块新的内存,再把 `"hello C"` 复制进去。因此,虽然它们保存的内容一样,但数组对应的是两块不同的空间,所以首地址不同,`str1 != str2`。
![C 语言字符指针入门:搞清地址、字符串常量与数组的区别_wishdown.com 展示 p[i]、*(p+i) 与不同指针步长之间的对应关系](https://img.wishdown.com/upload/news/20260824abc3add838c218c8c9ba5b441f85983b.webp)
字符指针可能指向同一份字符串常量
`str3` 和 `str4` 都是指针变量,它们保存的是字符串常量 `"hello C"` 的首地址。对于同一个常量字符串,编译器通常会让它在代码段或常量区中复用同一份内容,因此两个指针可能拿到同一个地址,于是 `str3 == str4`。
这里还要特别注意:虽然代码里写了 `char* str4 = "hello C";`,但它指向的依然是字符串常量,不能通过 `*str4 = 'w';` 这样的方式去修改内容。严格来说,指向字符串常量时,更合适的写法仍然是 `const char*`。
为什么 `[]` 本质上也是指针运算
理解字符指针之后,再看 `[]` 会更顺畅。`[]` 并不只属于数组,它本质上和指针运算直接相关。
可以先记住这几个等价关系:
- `p[0]` 等价于 `*p`
- `p[1]` 等价于 `*(p + 1)`
- 更一般地,`p[i]` 等价于 `*(p + i)`
关键点在于,指针“加一”不是简单地把数值加 `1`,而是按指针类型跨过对应大小的内存:
- `char* pc`,`pc + 1` 往后移动 `1` 个字节,因为 `char` 占 `1` 个字节。
- `int* pi`,`pi + 1` 往后移动 `4` 个字节,因为这里示例按 `int` 占 `4` 个字节来理解。
这也是前面说“指针有类型”的实际意义之一:类型会影响指针运算的步长,也影响解引用时按什么类型解释内存。
从二维访问写法看 `arr[i][j]` 的含义
下面这段代码展示了 `[]` 和指针运算是怎样配合工作的:
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[] = { 0,1,2,3,4,5 };
int c[] = { 3,44,5,6,7 };
int* arr[3] = { a,b,c };//指针数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//printf("%d ", *(arr[i] + j));
printf("%d ", arr[i][j]);// []表示指针运算符,用来计算指针加上或减去一个整数后所指向的地址。例如,p[0]等价于*p,p[1]等价于*(p+1)
}
printf("n");
}
return 0;
}
`arr` 是一个指针数组,里面放了 `a`、`b`、`c` 三个整型数组的首地址。
因此:
- `arr[i]` 取出的是某一个整型数组的首地址。
- `arr[i][j]` 就是在这个地址基础上继续取第 `j` 个元素。
- 它与注释中的 `*(arr[i] + j)` 完全等价。
这个例子虽然是 `int*`,但理解方式和字符指针完全一致。无论是访问字符串中的第几个字符,还是访问整型数组中的第几个元素,本质上都是“首地址 + 偏移量 + 解引用”的过程。
这一节需要记住的几个判断点
- 字符指针本身是变量,保存的是地址,不是把整段字符串直接塞进指针里。
- `const char*` 常用于接收字符串常量的首地址,通过它不能修改所指向的内容。
- 字符数组会创建自己的存储空间;字符指针通常只是指向某段已有字符数据。
- 两个内容相同的字符数组,地址未必相同;两个指向同一字符串常量的指针,地址可能相同。
- `p[i]` 本质上就是 `*(p + i)`,`[]` 背后仍然是指针运算。
把这些关系理顺后,后面再学习指针数组、数组指针以及函数指针时,就不容易在“地址”和“内容”之间混淆了。







