位置:首页 > C > C 语言字符指针入门:搞清地址、字符串常量与数组的区别

C 语言字符指针入门:搞清地址、字符串常量与数组的区别

时间:2026-08-24  |  作者:火苗实验室  |  阅读:0

目录

  1. 字符指针到底存的是什么
  2. 字符指针如何表示字符串
  3. 字符数组和字符指针有什么区别
  4. 为什么 `[]` 本质上也是指针运算
  5. 从二维访问写法看 `arr[i][j]` 的含义
  6. 这一节需要记住的几个判断点

前言

很多人学到 `char*` 时,表面上会写字符串输出,真正卡住的却是“指针里到底装了什么”“数组和指针为什么比较结果不同”。这一节就围绕这些高频疑问展开,把字符指针、字符串常量、字符数组和 `[]` 运算放到同一套内存视角里解释清楚,读完后你能更准确判断一段代码比较的是内容还是地址。

很多人刚接触 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);` 只会打印一个字符。

这也是为什么字符指针看起来像“保存了一个字符串”,但本质上仍然只是保存地址。

字符数组和字符指针有什么区别

很多入门问题都集中在这里。下面这段代码最值得仔细看:

展示字符指针、字符数组与字符串常量在内存表示上的差异
字符数组与字符指针的内存区别用地址与存储位置的对比,帮助读者理解为什么 `str1 != str2` 而 `str3 ==。
  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`。

展示 p[i]、*(p+i) 与不同指针步长之间的对应关系
`[]` 与指针运算的对应关系把 `[]`、解引用和按类型移动的关系放在一张图里,更容易看懂字符指针与整型指针为什么步长不同。

字符指针可能指向同一份字符串常量

`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)`,`[]` 背后仍然是指针运算。

把这些关系理顺后,后面再学习指针数组、数组指针以及函数指针时,就不容易在“地址”和“内容”之间混淆了。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多