当前位置: 首页 > news >正文

C语言:指向数组的指针和指向数组首元素的指针

相关阅读

C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm=1001.2014.3001.5482


在C语言中,指向数组的指针和指向数组首元素的指针经常被混淆,很多时候甚至会被笼统地统称为“数组指针”。但实际上,这两者并不是一回事,它们的类型不同、参与运算时的含义不同、使用方式也不同。本文就围绕这一点展开讨论。

下面先定义一个数组,然后分别定义:指向该数组首元素的指针和指向整个数组的指针。

int arr[] = {0, 1, 2}; int *ptr0 = arr; // 创建一个int型指针,指针值为数组首元素地址 int *ptr1 = &arr[0]; // 创建一个int型指针,指针值为数组首元素地址 int (*ptr2)[3] = &arr; // 创建一个指向整个数组的指针,指针值为整个数组的地址

乍一看,这三个指针对应的地址值似乎是一样的,也就是说,在数值上常常会看到:arr == &arr[0] == &arr。这看起来有点奇怪,因为它们明明不是同一种东西,为什么地址值却一样?原因在于:它们表示的是同一块内存的起始位置,但类型不同,因此编译器对它们的解释方式不同。

  • &arr[0]与arr对于编译器来说没有任何区别,因此ptr0和ptr1两个指针的各种性质都是一样的。
  • &arr这个常量值虽然与其他两者相同,但是对编译器而言,它代表了整个数组的地址,因此当&arr与其他数加减时,会将其他数转化为以数组的字节数为单位的地址值,对于指针ptr2来说也是如此。
//例1 int arr[] = {0,1,2}; //下面两条语句等价 int *ptr0 = arr; //创建了一个int型指针,指针值为数组首元素地址 int *ptr1 = &arr[0]; //创建了一个int型指针,指针值为数组首元素地址 int (*ptr2) [3] = &arr; //创建了一个int型数组指针,指针值为整个数组的地址 printf("arr is %p\n", arr); printf("arr + 1 is %p\n", arr + 1); printf("&arr[0] is %p\n", &arr[0]); printf("&arr[0] + 1 is %p\n", &arr[0] + 1); printf("&arr is %p\n", &arr); printf("&arr + 1 is %p\n", &arr + 1); printf("ptr0 is %p\n", ptr0); printf("ptr0 + 1 is %p\n", ptr0 + 1); printf("ptr1 is %p\n", ptr1); printf("ptr1 + 1 is %p\n", ptr1 + 1); printf("ptr2 is %p\n", ptr2); printf("ptr2 + 1 is %p\n", ptr2 + 1); 输出: arr is 000000000061FDFC arr + 1 is 000000000061FE00 &arr[0] is 000000000061FDFC &arr[0] + 1 is 000000000061FE00 &arr is 000000000061FDFC &arr + 1 is 000000000061FE08 ptr0 is 000000000061FDFC ptr0 + 1 is 000000000061FE00 ptr1 is 000000000061FDFC ptr1 + 1 is 000000000061FE00 ptr2 is 000000000061FDFC ptr2 + 1 is 000000000061FE08

从上面的例1可以看到arr、&arr[0]、&arr、ptr0、ptr1、ptr2的值都是相同的,但是arr、&arr[0]、ptr0、ptr1加1在编译后对地址值加了4,因为int类型占4字节的空间;&arr、ptr2加1在编译后对地址值加了12,因为数组含有三个int类型的数据,因此数组占12字节的空间。

在一维数组中,这种区别还不算特别明显;而在多维数组中,指向数组的指针就会变得非常常见,如例2所示。

//例2 int arr[2][2] = {{0, 1}, {2, 3}}; //创建一个多维数组 int (*ptr0) [2][2] = &arr; //创建了一个int型二维数组指针,指针值为二维数组的地址 //下面两条语句等价 int (*ptr1) [2] = arr; //创建了一个int型一维数组指针,指针值为两个数组中第一个数组的地址 int (*ptr2) [2] = &arr[0]; //创建了一个int型一维数组指针,指针值为两个数组中第一个数组的地址 int *ptr3 = arr[0]; //创建了一个int型指针,指针值为两个数组中第一个数组的首元素地址 int *ptr4 = &arr[0][0]; //创建了一个int型指针,指针值为两个数组中第一个数组的首元素地址 printf("ptr0 is %p\n", ptr0); printf("ptr0 + 1 is %p\n", ptr0 + 1); printf("ptr1 is %p\n", &arr[0]); printf("ptr1 + 1 is %p\n", ptr1 + 1); printf("ptr2 is %p\n", ptr2); printf("ptr2 + 1 is %p\n", ptr2 + 1); printf("ptr3 is %p\n", ptr3); printf("ptr3 + 1 is %p\n", ptr3 + 1); printf("ptr4 is %p\n", ptr4); printf("ptr4+ 1 is %p\n", ptr4 + 1); 输出: ptr0 is 000000000061FDE0 ptr0 + 1 is 000000000061FDF0 ptr1 is 000000000061FDE0 ptr1 + 1 is 000000000061FDE8 ptr2 is 000000000061FDE0 ptr2 + 1 is 000000000061FDE8 ptr3 is 000000000061FDE0 ptr3 + 1 is 000000000061FDE4 ptr4 is 000000000061FDE0 ptr4+ 1 is 000000000061FDE4

可以看到,即使ptr0、ptr1、ptr2、ptr3的值都是一样的,ptr0+1在编译后对地址值加了16(二维数组占4个字节空间),ptr1+1和ptr2+1在编译后对地址值加了8(一维数组占8个字节空间),ptr3+1在编译后对地址值加了4(int类型占4个字节空间)。

下面的例3展示了如何使用指向数组的指针和指向数组首元素的指针来访问数组元素。

例3 int arr[] = {0, 1, 2}; //下面两条语句等价 int *ptr0 = arr; //创建了一个int型指针,指针值为数组首元素地址 int *ptr1 = &arr[0]; //创建了一个int型指针,指针值为数组首元素地址 int (*ptr2) [3] = &arr; //创建了一个int型数组指针,指针值为整个数组的地址 printf("arr[0] is %d\n", ptr0[0]); //使用指向数组首元素的指针和[]打印0 printf("arr[1] is %d\n", ptr0[1]); //使用指向数组首元素的指针和[]打印1 printf("arr[2] is %d\n", ptr0[2]); //使用指向数组首元素的指针和[]打印2 printf("arr[0] is %d\n", arr[0]); //使用数组名和[]打印0 printf("arr[1] is %d\n", arr[1]); //使用数组名和[]打印1 printf("arr[2] is %d\n", arr[2]); //使用数组名和[]打印2 //一个很傻的例子,它是不必要的 printf("arr[0] is %d\n", (&arr[0])[0]); //使用首元素地址和[]打印0 printf("arr[1] is %d\n", (&arr[0])[1]); //使用首元素地址和[]打印1 printf("arr[2] is %d\n", (&arr[0])[2]); //使用首元素地址和[]打印2 //实际上还能更傻,这么做的可读性很差 printf("arr[0] is %d\n", (&((&arr[0])[0]))[0]); //奇怪地打印0 printf("arr[1] is %d\n", (&((&arr[0])[0]))[1]); //奇怪地打印1 printf("arr[2] is %d\n", (&((&arr[0])[0]))[2]); //奇怪地打印2 //尽管ptr2的值和ptr1、ptr0一样,但仍需先用*解引用才能再使用[]访问元素 printf("arr[0] is %d\n", (*ptr2)[0]); //使用指向数组的指针和[]打印0 printf("arr[1] is %d\n", (*ptr2)[1]); //使用指向数组的指针和[]打印1 printf("arr[2] is %d\n", (*ptr2)[2]); //使用指向数组的指针和[]打印2 printf("arr[0] is %d\n", ptr2[0]); //错误的 printf("arr[1] is %d\n", ptr2[1]); //错误的 printf("arr[2] is %d\n", ptr2[2]); //错误的 printf("arr[0] is %d\n", *(ptr0 + 0)); //使用指向数组首元素的指针和*打印0 printf("arr[1] is %d\n", *(ptr0 + 1)); //使用指向数组首元素的指针和*打印1 printf("arr[2] is %d\n", *(ptr0 + 2)); //使用指向数组首元素的指针和*打印2 printf("arr[0] is %d\n", *(arr + 0)); //使用数组名和*打印0 printf("arr[1] is %d\n", *(arr + 1)); //使用数组名和*打印1 printf("arr[2] is %d\n", *(arr + 2)); //使用数组名和*打印2 //尽管ptr2的值和ptr1、ptr0一样,但仍需先用*解引用才能再使用*访问元素 printf("arr[0] is %d\n", *((*ptr2) + 0)); //使用指向数组的指针和*打印0 printf("arr[1] is %d\n", *((*ptr2) + 1)); //使用指向数组的指针和*打印1 printf("arr[2] is %d\n", *((*ptr2) + 2)); //使用指向数组的指针和*打印2 printf("arr[0] is %d\n", *(ptr2 + 0)); //错误的 printf("arr[1] is %d\n", *(ptr2 + 1)); //错误的 printf("arr[2] is %d\n", *(ptr2 + 2)); //错误的

读到这里,其实最容易混淆的点主要有两个。

1、地址值相同,不代表类型相同

arr、&arr[0]、&arr在打印出来时很可能数值一样,但它们的类型并不一样。而在C语言里,类型决定了指针运算的含义。

2、指向整个数组的指针,访问元素时要先解引用

int *可以直接用[]访问元素,因为它指向的是元素本身。而int (*)[3]指向的是整个数组,所以必须先*一次,变成数组,再访问其中的元素。这一点在多维数组里尤其重要,因为多维数组处理中,最常见的其实就是“指向数组的指针”。

下面的博客讨论了C语言中字符串字面量的保存位置,欢迎阅读。

C语言:字符串字面量及其保存位置https://chenzhang.blog.csdn.net/article/details/135142133

http://www.jsqmd.com/news/674694/

相关文章:

  • 程序员追不上机器人干脆开电瓶车跟;小米徐洁云辟谣“雷军被人堵在车里维权”;DeepSeek被曝融资20亿 | 极客头条
  • geogram实战案例:基于几何算法的10个工业应用场景解析
  • 兔抗53BP1抗体亲和纯化,高效富集目标蛋白,低丰度样品稳定输出
  • 终极指南:OCI内容描述符如何保障容器镜像的安全寻址与验证
  • 【万字文档+PPT+源码】基于springboot+vue的学生操行评分系统-计算机专业项目设计分享
  • 如何利用Nuclide键盘宏提升开发效率:完整指南与API解析
  • 如何从零部署Colanode:开源协作平台的完整生产环境搭建指南
  • 终极指南:如何用stacktrace.js构建企业级前端错误监控系统
  • Gemma-3多模态模型应用场景:博物馆文物图片智能导览系统构建
  • Space Cloud架构深度解析:GraphQL API与数据库查询优化终极指南
  • 终极指南:如何使用Eloquent-Sluggable在Laravel中快速创建SEO友好的URL
  • AutoRaise未来展望:macOS窗口管理工具的发展趋势与社区贡献指南
  • ytfzf高级技巧:10个提升终端视频体验的实用方法
  • **发散创新:基于角色权限模型的代码保护机制设计与实现**在现代软件开发中,**模型保护**已成为系统安全的
  • 深圳同袍存储解说DDR内存及SSD价格现状
  • 剪映专业版教程:制作动感照片效果
  • 终极LeetCode2测试驱动开发指南:5个步骤编写可靠算法测试用例
  • Linux挂载硬盘
  • ARM架构安全定时器CNTPS_TVAL_EL1详解与应用
  • 如何参与DictionaryByGPT4开源AI单词学习项目:完整贡献指南
  • Hermes 最强引擎:学习循环——Agent 自己给自己造缰绳
  • 从擦写寿命到掉电保护:深入解析SPI NAND、SD NAND和eMMC的可靠性差异
  • [具身智能-400]:AS5600 PWM时钟与PWM输出与角度的关系详解
  • 如何快速掌握世界最快JSON解析器jsmn:从零开始构建高效数据处理工具
  • 终极指南:Cluster API如何简化Kubernetes集群全生命周期管理
  • 终极Geocoder测试指南:单元测试、集成测试和性能测试的完整方案
  • ssh-audit实战:10个关键命令保护你的SSH服务
  • Handlebars-helpers高级用法:自定义辅助函数与扩展技巧
  • 别再只盯着加密算法了!聊聊GM/T 0054标准里,密钥从‘生’到‘死’的8个关键环节
  • 终极指南:如何在gumbo-parser中扩展自定义标签处理逻辑