C语言指针进阶解析:字符指针、指针数组与数组指针的区别
C语言指针是核心难点之一,尤其是涉及字符处理与多维数组时容易混淆。本文从内存分配角度详解字符指针指向常量区的特性,利用“剥去变量名看类型”的技巧辨析指针数组与数组指针,并重点剖析数组指针在二维数组处理中的步长逻辑与函数应用,助你攻克指针进阶关卡。
字符指针与字符串内存分布
在深入复杂结构前,我们先巩固对 char * 的理解。指针本质是存储地址的变量,其大小由平台决定(32位为4字节,64位为8字节),且指针的类型决定了偏移步长和解引用的权限。对于字符指针,主要有两种常见的初始化方式:
第1步:单字符引用
目的:理解普通字符变量的地址存取。
操作:定义一个字符变量并取地址赋给指针。
代码示例:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w'; // 修改所指向的值
return 0;
}
第2步:字符串字面量引用
目的:掌握常量字符串的存储位置及指针的真实含义。
操作:将双引号括起来的字符串直接赋给指针。
const char* pstr = "hello bit.";
这里并不是将整串字符存入指针(指针仅能存地址),而是将字符串首字符 'h' 的地址存入 pstr。由于双引号内的字符串属于字符串常量,存储在静态数据区(常量区),因此它不可被修改。
为了更直观地看清这一差异,我们可以对比数组定义与指针赋值的结果:
#include <stdio.h>
int main()
{
// 两个独立的栈内存数组,内容相同但地址不同
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");
return 0;
}
运行结果显示,str1 和 str2 地址不相等,因为它们在栈上各自开辟了独立的存储空间,类似数组压栈行为,需注意避免栈溢出风险;而 str3 和 str4 指向同一个常量字符串的首地址,故相等。这验证了数组名通常表示首元素地址(非整个数组),而字符串常量在全局/静态区仅保存一份副本的特点。
指针数组 vs 数组指针
这两个名称极易混淆,区分它们的核心法则很简单:去掉变量名,剩下的即为该变量真正代表的类型。
第1步:识别指针数组
操作:观察变量声明。若 [] 先于 * 结合,则是数组。
int *arr1[10]; // 优先级 [] 高于 *,arr1是包含10个 int* 的数组
char *arr2[4]; // 包含4个 char* 的数组
第2步:识别数组指针
操作:观察变量声明。若括号 () 将 * 强制结合到变量名,则是指针。
结果:它是指向数组的指针。
int (*p)[10]; // p先和*结合,说明p是一个指针,指向大小为10的整型数组
判断口诀:先找变量名,看它和谁优先结合。与 * 优先结合就是指针,与 [] 优先结合就是数组。
数组指针的定义与地址步长陷阱
既然明确了 int (*p)[10] 是数组指针,那它在实际地址运算中与普通数组名有何不同?这是指针进阶的关键。
第1步:数组名与取地址的差异
目的:探究 arr 与 &arr 的物理意义。
操作:分别打印一维数组的基地址及加1后的地址。
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr); // 数组首元素地址
printf("%p\n", &arr); // 整个数组的地址
printf("arr+1 = %p\n", arr+1); // 跳过sizeof(int),即4字节
printf("&arr+1= %p\n", &arr+1); // 跳过整个数组,即40字节
return 0;
}
虽然两者输出的初始地址数值相同,但在进行 +1 运算时截然不同:
- arr + 1:跳过 1 个 int 的大小(4字节)。
- &arr + 1:跳过整个数组的大小(10 × 4 = 40字节)。
&arr 的类型实际上是 int (*)[10],这是一种典型的数组指针类型。
数组指针的实际应用场景
理解了数组指针的含义后,最常见的实战场景便是处理二维数组。
第1步:使用下标遍历二维数组
这种方式参数传递时会自动退化为首元素地址,编译器通过行列索引计算位移。
void print_arr1(int arr[3][5], int row, int col) {
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
printf("%d ", arr[i][j]);
}
第2步:使用数组指针高效遍历
目的:在函数参数列表中显式表达“这是一个行指针”,增强代码可读性并明确内存结构。
操作:形参接收类型为 int(*arr)[5]。
#include<stdio.h>
// 用指针来打印二维数组
void print_arr2(int(*arr)[5], int row, int col) {
int i = 0;
int j = 0;
for (i = 0; i < row; i++) {
for (j = 0; j < col; j++) {
// 此时 arr+i 代表第 i 行的地址,解引用 *(arr+i) 得到该行首元素,再偏移 j
printf("%d ", (*(arr + i))[j]); // 或简写为 arr[i][j]
}
printf("\n");
}
}
int main() {
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; // 注意补全所有元素避免未初始化警告
print_arr1(arr, 3, 5);
print_arr2(arr, 3, 5); // 传递的是第一行的地址,符合数组指针接收要求
return 0;
}
在这里,实参 arr 退化为第一行的地址,正好可以用 int(*)[5] 类型的数组指针接住。数组指针在处理多维数据映射时优势明显。
以上就是C语言指针进阶详细内容的解析,更多关于指针与内存管理的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









