ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C语言指针痛点--->指针数组与数组指针

C语言指针痛点--->指针数组与数组指针 C语言指针痛点—指针数组与数组指针一、痛点之情景再现我们学习C语言到了指针进阶阶段时虽然知道这两个指针是什么名字但是可能会分不清它们存的是指针地址还是元素或者指向的是什么char*p1[4];// 指针数组char(*p2)[4];// 数组指针我们根据上方代码片段可知指针数组与数组指针这两个代码写法只差一对括号但它们的含义、内存布局天差地别。我们在写代码时很容易将它们混淆。二、破局核心掌握“优先级”这把金钥匙C语言运算符优先级在C语言中[]数组下标的优先级高于*指针解引用。2.记忆口诀“谁离变量名近变量就是什么类型”。带括号的变量先和 * 结合就是指针不带括号的变量先和 [] 结合就是数组。三、 指针数组Array of Pointers—— 本质是数组//代码示例char*p[4];1.拆解分析因为 [] 优先级高所以 p 先和 [4] 结合。结论p 是一个数组。然后看数组里装的是什么是 char *字符指针。2.本质它是一个数组里面存放了 4 个指针。3.内存模型想象一排连续的格子数组每个格子里装的不是字符而是地址指向其他字符串或变量的首地址。它占用的内存是 4 * sizeof(char *)32位系统是16字节64位系统是32字节。图例4.完整可运行示例下面这段代码演示了指针数组的定义、初始化与访问方式。#includestdio.hintmain(){// 定义并初始化一个指针数组数组里存放 3 个 char* 指针char*p[3]{Hello,World,C};// 访问p[i] 取出第 i 个指针再解引用得到字符串for(inti0;i3;i){printf(p[%d] %s\n,i,p[i]);}// 内存布局p 是连续 3 个格子每个格子存一个地址指向字符串首字符printf(p 占用的总内存 %zu 字节\n,sizeof(p));// 3 * sizeof(char*)printf(p[0] 指向的地址 %p\n,(void*)p[0]);printf(p[1] 指向的地址 %p\n,(void*)p[1]);return0;}运行输出示例p[0] Hello p[1] World p[2] C p 占用的总内存 24 字节 p[0] 指向的地址 0x55a1c2d3e010 p[1] 指向的地址 0x55a1c2d3e016说明p本身是数组p[i]取出的是第i个指针sizeof(p)得到的是整个数组占用的字节数3 * sizeof(char *)64 位系统下为 24 字节。地址的具体数值每次运行可能不同但相邻指针指向的地址通常相差 6 个字节对应字符串长度。四、 数组指针Pointer to an Array—— 本质是指针//代码示例char(*p)[4];1.拆解分析因为有 () 强制结合p 先和 * 结合形成(*p)。结论p 是一个指针。然后看指针指向什么指向 char [4] 这个数组类型。2.本质它是一个指针存放的是一个数组的地址即整个数组的起始地址。3.内存模型它不是一排格子它只有一个格子一个变量里面存了一个地址这个地址指向一个长度为4的字符数组。对它进行 p 1它不是移动1个字节而是跨越整整4个字节跳过一个完整的数组。图例4.完整可运行示例下面这段代码演示了数组指针的定义、初始化与访问方式。#includestdio.hintmain(){// 定义一个长度为 4 的整型数组intarr[4]{10,20,30,40};// 定义数组指针p 指向整个 int[4] 数组int(*p)[4]arr;// 访问(*p)[i] 先解引用得到数组再按下标取元素for(inti0;i4;i){printf((*p)[%d] %d\n,i,(*p)[i]);}// 指针运算p 1 会跨越整个数组4 个 int而不是 1 个 intprintf(p 指向的地址 %p\n,(void*)p);printf(p 1 指向的地址 %p\n,(void*)(p1));printf(地址差 %ld 字节即跨越 4 个 int\n,(char*)(p1)-(char*)p);return0;}运行输出示例(*p)[0] 10 (*p)[1] 20 (*p)[2] 30 (*p)[3] 40 p 指向的地址 0x7ffd4a2b3c00 p 1 指向的地址 0x7ffd4a2b3c10 地址差 16 字节即跨越 4 个 int说明p本身是一个指针存放的是整个数组的起始地址(*p)[i]等价于arr[i]。对p做p 1指针会跨越整个int[4]数组64 位系统下int为 4 字节共跨越 16 字节这正是数组指针与普通指针最大的区别。五、常见错误与排查初学者在指针数组与数组指针上最容易踩坑下面列举 3 个典型错误每个都给出错误代码、运行结果、原因分析和正确写法。错误 1混淆声明写法错误代码#includestdio.hintmain(){char*p[4];// 想定义数组指针却写成了指针数组charstr[4]abc;pstr;// 编译报错类型不匹配return0;}运行结果error: assignment to expression with array type原因分析char *p[4]中[]优先级高于*p先和[4]结合本质是数组不能作为指针被赋值。想定义数组指针必须加括号char (*p)[4]。正确写法#includestdio.hintmain(){char(*p)[4];// 数组指针p 指向整个 char[4] 数组charstr[4]abc;pstr;// 正确p 存放数组的起始地址printf(%s\n,(*p));// 输出 abcreturn0;}错误 2错误解引用错误代码#includestdio.hintmain(){intarr[4]{10,20,30,40};int(*p)[4]arr;printf(%d\n,*p);// 错误*p 是数组不是 intprintf(%d\n,p[0]);// 错误p[0] 是数组不是 intreturn0;}运行结果warning: format ‘%d’ expects argument of type ‘int’, but argument has type ‘int *’原因分析数组指针解引用一次得到的是整个数组而不是单个元素。要访问元素必须再按下标取即(*p)[i]或p[0][i]。正确写法#includestdio.hintmain(){intarr[4]{10,20,30,40};int(*p)[4]arr;for(inti0;i4;i){printf(%d ,(*p)[i]);// 正确先解引用得到数组再按下标取元素}printf(\n);return0;}错误 3指针运算越界错误代码#includestdio.hintmain(){intarr[4]{10,20,30,40};int(*p)[4]arr;// 错误p 1 已经越过整个数组再解引用访问越界printf(%d\n,(*(p1))[0]);return0;}运行结果(*(p 1))[0] 0 // 未定义行为可能输出垃圾值或崩溃原因分析数组指针做p 1会跨越整个int[4]数组16 字节此时p 1已指向数组末尾之后的内存解引用属于越界访问结果是未定义行为。正确写法#includestdio.hintmain(){intarr[4]{10,20,30,40};int(*p)[4]arr;// 只在数组范围内访问for(inti0;i4;i){printf(%d ,(*p)[i]);}printf(\n);// 若确实需要遍历多个数组应使用数组指针数组intarr2[4]{50,60,70,80};int(*pa[2])[4]{arr,arr2};// 指针数组每个元素指向一个 int[4]for(intj0;j2;j){for(inti0;i4;i){printf(%d ,(*pa[j])[i]);}printf(\n);}return0;}小结写代码时先按“谁离变量名近”判断类型再决定如何解引用做指针运算前务必确认指针是否仍在数组边界内。六、总结名称定义写法本质占用的内存谁先结合指针数组char *p[4]数组4个指针的大小p 先和 [4] 结合数组指针char (*p)[4]指针1个指针的大小p 先和 * 结合指针数组本质上是数组数组指针本质上是指针做这类题画图还是很有必要的。
返回列表