C语言:指针的变量运算及数组指针

1、指针的变量运算

指针变量保存的是地址,二地址本质上是一个整数,所以指针变量可以进行部分运算,列如加法减法、比较等,请看下面的代码:

1. #include <stdio.h>
2.
3. int main(){
4. int a = 10, *pa = &a, *paa = &a;
5. double b = 99.9, *pb = &b;
6. char c = '@', *pc = &c;
7. //最初的值
8. printf("&a=%#X, &b=%#X, &c=%#X\\n", &a, &b, &c);
9. printf("pa=%#X, pb=%#X, pc=%#X\\n", pa, pb, pc);
10. //加法运算
11. pa++; pb++; pc++;
12. printf("pa=%#X, pb=%#X, pc=%#X\\n", pa, pb, pc);
13. //减法运算
14. pa -= 2; pb -= 2; pc -= 2;
15. printf("pa=%#X, pb=%#X, pc=%#X\\n", pa, pb, pc);
16. //比较运算
17. if(pa == paa){
18. printf("%d\\n", *paa);
19. }else{
20. printf("%d\\n", *pa);
21. }
22. return 0;
23. }

运行结果:

&a=0X28FF44, &b=0X28FF30, &c=0X28FF2B
pa=0X28FF44, pb=0X28FF30, pc=0X28FF2B
pa=0X28FF48, pb=0X28FF38, pc=0X28FF2C
pa=0X28FF40, pb=0X28FF28, pc=0X28FF2A
2686784

从运算结果可以看出:pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。

这很奇怪,指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1,这是为什么呢?

以 a 和 pa 为例,a 的类型为 int,占用 4 个字节,pa 是指向 a 的指针,如下图所示:

刚开始的时候,pa指向a的开头,通过*pa读取数据是,从pa指向的位置向后移动四个字节,吧这是个字节的内容作为要获取的数据,这四个字节也正好是变量a占用的内存。

如果pa++,是的地址加1的话,就会变成如下图所示的指向关系:

这个时候pa指向整数a的中间,*pa使用的是橙色虚线画出的四个字节,其中前三个是变量a的,后面一个是其他数据的,把他们搅合在一起,显然是没有实际的意义,取得的数据也会非常怪异。

如果pa++,使得地址加4的话,正好能够完全跳过整数a,指向它后面的内存,如图所示:

数组中的所有元素在内存中是连续排列的,如果一个指针指向了数组中的某个元素,那么加1就表示指向下一个元素,减1就表示指向上一个元素,这样指针的加减运算就有了现实的意义。

反面教槽,警告不要尝试通过指针获取下一个变量的地址;

1. #include <stdio.h>
2.
3. int main(){
4. int a = 1, b = 2, c = 3;
5. int *p = &c;
6. int i;
7. for(i=0; i<8; i++){
8. printf("%d, ", *(p+i) );
9. }
10. return 0;
11. }

在 VS2010 Debug 模式下的运行结果为:

3, -858993460, -858993460, 2, -858993460, -858993460, 1, -858993460,

可以发现,变量a、b、c并不挨着,他们中间还掺杂了别的辅助数据。

指针变量除了可以参加加减运算,还可以参与比较运算,当对指针变量进行比较运算时,比较的是变量本身的值,也就是数据的地址。如果地址相等,那么两个指针就指向同一份数据,否则就指向不同的数据。

上面的代码(第一个例子)在比较 pa 和 paa 的值时,pa 已经指向了 a 的上一份数据,所以它们不相等。而 a的上一份数据又不知道是什么,所以会导致 printf() 输出一个没有意义的数,这正好印证了上面的观点,不要对指向普通变量的指针进行加减运算。

不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际含义。

2、数组指针

 

数组(array)是一系列具有相同属性的数据集合,每一份数据叫做一个数组元素(element)数组中所有元素在内存中是连续排列的,整个数组占用的是一块内存,以int arr【】={99,15,100,888,252}为例,该数组在内存中的分布如下图:

定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第0个元素,在c语言中,我们将第0个元素称为输出的首地址,以上面的数组为例,下图是arr的指向:

示例:以指针的方式遍历数组元素;

1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int len = sizeof(arr) / sizeof(int); //求数组长度
6. int i;
7. for(i=0; i<len; i++){
8. printf("%d ", *(arr+i) ); //*(arr+i)等价于arr[i]
9. }
10. printf("\\n");
11. return 0;
12. }

运行结果: 99 15 100 888 252

第五行代码用来求数组的长度,sizeof(arr)会获得整个数组所占用的字节数,sizeof(int)会获得一个数组元素所占用的字节数,他们相除的结果就是数组包含的元素个数,也就是数组长度。

第8行代码中我们使用了*(arr+i)这个表达式,arr是数组名,指向数组的第0个元素,表示数组首地址,arr+i指像素主的第i个元素,*(arr+i)表示取第i个元素的数据,它等价于arr[i]。

我们可以定义一个指向数组的指针:

1. int arr[] = { 99, 15, 100, 888, 252 };
2. int *p = arr;

arr本身就是一个指针,可以直接赋值给指针变量p,arr是数组第0个元素的地址,所以int *p=arr,也可以写作int *p=&arr[0];。也就是说,arr、p、&arr[0]这三种写法都是等价的,他们都指向了数组第0个元素,或者说指向了数组的开头。

如果一个指针指向了数组,我们称它为数组指针(array pointer)

数组指针指向数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关,上面的例子中,p指向数组元素是int类型,所以p的类型必须也是int*。

反过来想,p并不知道它指向的是一个数组,p只知道他只想的是一个整数,究竟如何使用p取决于程序员的编码。

更改上面的代码,是数组指针来遍历数组元素;

1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int i, *p = arr, len = sizeof(arr) / sizeof(int);
6.
7. for(i=0; i<len; i++){
8. printf("%d ", *(p+i) );
9. }
10. printf("\\n");
11. return 0;
12. }

数组在内存中知识数组元素的简单排列,没有开始和结束标志,在求数组的长度时不能使用sizeof(p)/sizeof(int),因为p只是一个指向int类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以sizeof(p)求得的是p这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。

也就是说,根据数组指针不能逆推出整个数组元素的个数,以及数组从哪里开始、到哪里结束等信息。不像字符串,数组本身也没有特定的结束标志,如果不知道数组的长度,那么就无法遍历整个数组。

上节我们讲到,对指针变量进行加法和减法运算时,是根据数据类型的长度来计算的。如果一个指针变量 p 指向了数组的开头,那么 p+i 就指向数组的第 i 个元素;如果 p 指向了数组的第 n 个元素,那么 p+i 就是指向第 n+i个元素;而不管 p 指向了数组的第几个元素,p+1 总是指向下一个元素,p-1 也总是指向上一个元素。

更改上面的代码,让 p 指向数组中的第二个元素:

1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int *p = &arr[2]; //也可以写作 int *p = arr + 2;
6.
7. printf("%d, %d, %d, %d, %d\\n", *(p-2), *(p-1), *p, *(p+1), *(p+2) );
8. return 0;
9. }

运行结果: 99, 15, 100, 888, 252

引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。

(1)使用下标

也就是采用arr[I]的形式访问数组元素,如果p是指向数组arr的指针,那么也可以使用P[I]来访问数组元素,他等价于arr[I]

(2)使用指针

也就是使用*(p+i)的形式访问数组元素,另外数组名本身也是指针,也可以使用*(arr+i)来访问数组元素,他等价于*(p+i)。

不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素,不同的是,数组名是常量,他的值不能改变,而数组指针时变量,他的值可以任意改变,也就是说,数组名只能指向数组的开头,而数组指针可以指向数组开头,再指向其他元素。

更改上面的代码,借助自增运算符来遍历数组元素:

1. #include <stdio.h>
2.
3. int main(){
4. int arr[] = { 99, 15, 100, 888, 252 };
5. int i, *p = arr, len = sizeof(arr) / sizeof(int);
6.
7. for(i=0; i<len; i++){
8. printf("%d ", *p++ );
9. }
10. printf("\\n");
11. return 0;
12. }

运行结果: 99 15 100 888 252

第 8 行代码中,*p++ 应该理解为 (p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的。

关于数组指针的谜题

假设p是指向数组arr中的第n个元素的指针,那么p++、++p、(*p)++分别是什么意思呢?

  • p++等价于(p++),便是先取得第n个元素的值,在将p指向下一个元素
  • *++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
  • (*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/18085.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【高德地图】基本使用教程(玩转地图)(vue2+vue3)

简介 带大家一步步实现地图显示特定位置&#xff0c;或定位到当前位置。并且拖拽地图界面能够查询出指定位置周边的信息。通过关键字搜索周边信息。 本教程适合初学者。 一、获取使用高德api的key 打开高德开发平台&#xff0c;登录后&#xff0c;鼠标覆盖右上角头像&#…

统信UOS开发接口DTK

DTK(Development ToolKit)是基于 Qt 开发的简单且实用的通用开发框架。提供丰富的开发接口与支持工具,能有效提升开发效率。 文章目录 一、简介DTK 常见模块介绍概述二、框架创建开发环境准备使用 cmake三、常见模块窗口和对话框一、简介 DTK 常见模块介绍 概述 DTK(Dev…

转轮数组(C语言实现)

题目介绍 方法一我们可以先把数字1 2 3 4逆转一下&#xff0c;第二步我们可以逆转一下5 6 7&#xff0c; 最后整体逆置一下就会变成上面的数字。 void reverse(int* nums, int begin, int end) {while (begin < end){int tmp nums[begin];nums[begin] nums[end];nums[en…

云端弹性计算公式有哪些内容?

云端弹性计算公式有哪些内容&#xff1f;云端弹性计算公式涵盖资源分配、性能监控、自动伸缩及积分计算等方面。资源分配依据虚拟机配置和实际需求动态调整&#xff1b;性能监控实时监控CPU、内存等关键指标&#xff1b;自动伸缩根据预设阈值自动调整虚拟机数量&#xff1b;积分…

openGauss常见问题与故障处理(四)

4.数据库故障定位手段&#xff1a; 数据库故障定位手段通常有如下三种类&#xff1a; 提到“种类”&#xff0c;这里给大家举一个模拟场景中肖荏盖反向的小故事 对于初学者入门的学习&#xff0c;一些理论不容易理解或记住&#xff0c;所以本节课程【创新】采用了【正、反对比…

《Structure-from-Motion Revisited》论文解析——COLMAP

一、论文简介 这篇论文的标题是《Structure-from-Motion Revisited》&#xff0c;作者是Johannes L. Schnberger和Jan-Michael Frahm&#xff0c;分别来自北卡罗来纳大学教堂山分校和苏黎世联邦理工学院。这篇论文主要讨论了一种新的增量式结构从运动&#xff08;Structure-fro…

渑池县中药材产业党委莅临河南广宇企业管理集团有限公司参观交流

11月14日&#xff0c;渑池县人大副主任、工商联主席杨航率县中药材产业党委代表团一行13人&#xff0c;莅临河南广宇集团参观交流。河南广宇集团总经理王峰、副总经理王培等领导热情接待并陪同参观、座谈。 代表团一行首先参观了集团旗下郑州美信中医院&#xff08;庚贤堂中医药…

Cherno OpenGL(18 ~ 24)

混合 默认情况下 OpenGL 不执行任何混合&#xff0c;它只需要你们渲染的东西&#xff0c;然后把它渲染成不透明的东西。 之前我们渲染了红色方块&#xff0c;在它上面我们以某种形式渲染了一个半透明的蓝色方块&#xff08;不透明的蓝色方块会直接覆盖红色方块&#xff09;&am…

HashMap源码分析下

HashMap 环境 JDK11 HashMap是用哈希表结构&#xff08;链表散列&#xff1a;数组链表&#xff09;实现&#xff0c;结合数组和链表的优点。扩容时当链表长度超过 6 时&#xff0c;链表转换为红黑树。 public class HashMap<K,V> extends AbstractMap<K,V>impleme…

【Golang】——Gin 框架简介与安装

文章目录 1. Gin 框架概述1.1 什么是 Gin 框架&#xff1f;1.2 为什么选择 Gin&#xff1f;1.3 使用场景 2. 安装 Go 与 Gin 框架2.1 安装 Go 语言环境2.2 初始化 Go 项目2.3 安装 Gin 框架 3. 编写第一个 Gin 应用3.1 Gin 最小化示例代码代码解读3.2 运行程序3.3 测试服务 4. …

南京邮电大学《智能控制技术》期末抢救(上)

一、智能控制的提出 传统控制方法包括经典控制和现代控制——基于被控对象精确模型的控制方式&#xff0c;缺乏灵活性和应变能力&#xff0c;适于解决线性、时不变性等相对简单的控制问题。传统控制方法在实际应用中遇到很多难解决的问题&#xff0c;主要表现以下几点&#xff…

系统设计-系统回调通知设计

系统回调通知设计 消息类型容错机制消息协议负载均衡监控&告警很多公司的架构都存在与外界系统有交互,交互难免会有一些同步请求、回调通知等。且公司一般网络的出入口都是只有一个,而各个业务条线只要存在和外界系统有业务往来,都会存在回调通知,所以可以设计一个公司…

Seatunnel2.3.5的FTP无法读取中文路径的问题

问题原因 Seatunnel的connector-file下的ftp包中关于读取文件的路径没有对路径进行编码导致当有中文的时候会出现乱码 修改源码 我们需要修改两处位置 一处是判断路径是否存在的方法 一处是读取文件的流的地方 修改判断文件是否存在的地方 这个文件的路径是org/apache/sea…

基于java的果蔬种植销售一体化服务平台

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

【云岚到家】-day10-1-状态机增删查

【云岚到家】-day10-1-状态机增删查 1 订单管理1&#xff09;订单管理管什么&#xff1f; 2 基础设计2.1 订单状态流转1&#xff09;订单状态流转图2&#xff09;订单状态3&#xff09;服务单状态 2.2 数据库设计1&#xff09;表设计2&#xff09;分库分表 2.3 状态机设计1&…

ICML24|通用时间序列预测大模型思路

论文标题&#xff1a;Unified Training of Universal Time Series Forecasting Transformers GitHub链接&#xff1a;https://github. com/SalesforceAIResearch/uni2ts 论文链接&#xff1a;https://arxiv.org/pdf/2402.02592 前言 普适预测器是一个能够处理任何时间序列预…

LRU(Least Recently Used,最近未使用)

一、LRU的由来 lru的引入主要是和内存回收有关。 属于内核的大部分page是不能够进行回收的&#xff0c;比如内核栈、内核代码段、内核数据段以及大部分内核使用的page&#xff0c;它们都是不能够进行回收的&#xff1b; 相反&#xff0c;进程使用的page&#xff0c;比如进程…

网盘聚合搜索项目Aipan(爱盼)

本文软件由网友 刘源 推荐&#xff1b; 简介 什么是 Aipan&#xff08;爱盼&#xff09; ? Aipan&#xff08;爱盼&#xff09;是一个基于 Vue 和 Nuxt.js 技术构建的开源网盘搜索项目。其主要目标是为用户提供一个能够自主拥有和管理的网盘搜索网站。该项目持续维护和更新&a…

i春秋-Hash(__wakeup沉默、序列化)

练习平台地址 竞赛中心 题目描述 题目内容 啥也没有就一个标签跳转 点击后的确发生了跳转 观察到url中有key和hash两个值&#xff0c;猜测hash是key的hash 查看源代码发现确实是 $hashmd5($sign.$key);the length of $sign is 8 解密得到$sign应该为kkkkkk01 构造122的hash i…