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

深入理解指针(一)

1.内存和地址

2.指针变量和地址

3.指针变量类型的意义

4.指针运算


1. 内存和地址

1.1 内存

在讲内存和地址之前,为了大家更好的理解举了这么个例子:

假如有一栋教学楼,刚好你今天在这栋楼的某一个课室上课,已知这栋楼有50个课室,但是没有编号,你的舍友来找你拿宿舍门钥匙, 如果想找到你,就得挨个教室去找,这样效率很低,但是我们如果根据楼层和楼层的教室的情况,给每个房间编上号,如:

一楼:101,102,103......

二楼:201,202,203......

. . . . . . . . . . . . . . . . . .

有了教室号,你的舍友知道你在那一间教室上课,就可以快速的找到你且拿到宿舍钥匙。生活中,给每个房间编写编号,就能提高效率,能快速地找到你想要找的房间位置。 

 

如果把上面的例子对照到计算机中,又是怎么样呢? 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是 8GB/16GB/32GB 等,那这些内存空间如 何⾼效的管理呢? 其实也是把内存划分为⼀个个的内存单元,每个内存单元的大小取1个字节。 计算机中常见的单位(补充): ⼀个比特位可以存储⼀个2进制的位1或者0

内存单位:

  1. bit - 比特位
  2. Byte - 字节
  3. KB
  4. MB
  5. GB
  6. TB
  7. PB

 

 

  1. 1Byte = 8bit
  2. 1KB = 1024Byte
  3. 1MB = 1024KB
  4. 1GB = 1024MB
  5. 1TB = 1024GB
  6. 1PB = 1024TB

其中,每个内存单元,相当于⼀个教室,⼀个字节空间里面能放 8个比特位,就好比同学们住的八人间,每个人是⼀个比特位。 每个内存单元也都有⼀个编号(这个编号就相当
于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。C语言中给 地址起了新的名字叫: 指针。 所以我们可以理解为: 内存单元的编号 == 地址 == 指针
1.2 究竟该如何理解编址

首先,必须理解,计算机内是有很多的硬件单 元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行数据传递。但是硬件与硬件之间是互相独立的,那么如何通
信呢?答案很简单,用"线"连起来。而CPU和内存之间也是有⼤量的数据交互的,所以,两者必须也用线连起来。不过,我们今天关心一组线,叫做地址总线。CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址(就如同宿舍很 多,需要给宿舍编号⼀样)。 计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。钢琴、吉他 上面没有写上“剁、来、咪、发唆、拉、西”这样的信息,但演奏者照样能够准 确找到每⼀个琴弦的每⼀个位置,这是为何?因为制造商已经在乐器硬件层面上设计好了,并且 所有的演奏者都知道。本质是⼀种约定出来的共识

 


 

2. 指针变量和地址 

2.1 取地址操作符(&)

理解了内存和地址的关系,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间,比如:

#include <stdio.h> 1
int main()
{int a = 10;return 0;
}

比如,上述的代码就是创建了整型变量a,内存中
申请4个字节,⽤于存放整数10,其中每个字节都
有地址,上图中4个字节的地址分别是:
  1. 0x00EEFAE4  0a  
  2. 0x00EEFAE5  00  
  3. 0x00EEFAE6  00  
  4. 0x00EEFAE7  00
那我们如何能得到a的地址呢?
这里就得学习⼀个操作符(&)-取地址操作符

 


#include <stdio.h>
int main()
{int a = 10;&a;//取出a的地址printf("%p\n", &a);return 0;
}

 按照我画图的例子,会打印处理:006FFD70 &a取出的是a所占4个字节中地址较小的字节的地址。虽然整型变量占用4个字节,我们只要知道了第⼀个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。

 2.2 指针变量和解引用操作符(*)

 

2.2.1 指针变量
那我们通过取地址操作符(&)拿到的地址是⼀个数值,比如:0x00EEFAE4 ,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。
比如:
#include <stdio.h>
int main()
{int a = 10;int* pa = &a;//取出a的地址并存储到指针变量pa中return 0;
}
指针变量也是⼀种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
2.2.2 如何拆解指针类型
我们看到pa的类型是 int* ,我们该如何理解指针的类型呢?
int a = 10;
int * pa = &a;
这里pa左边写的是 int* , * 是在说明pa是指针变量,而前面的 int 是在说明pa指向的是整型(int)
类型的对象。
char ch = 'w';
pc = &ch;//pc 的类型怎么写呢?
2.2.3 解引用操作符
我们将地址保存起来,未来是要使用的,那怎么使用呢?
在现实生活中,我们使用地址要找到⼀个房间,在房间里可以拿去或者存放物品。
C语言中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里·必须学习⼀个操作符叫解引用操作符(*)。
#include <stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0;return 0;
}
上面代码中第6行就使用了解引用操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0。有同学肯定在想,这里如果目的就是把a改成0的话,写成 a = 0; 不就完了,为啥非要使用指针呢? 其实这里是把a的修改交给了pa来操作,这样对a的修改,就多了⼀种的途径,写代码就会更加灵活, 后期慢慢就能理解了。

 

2.3 指针变量的大小

前面的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产生的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。 如果指针变量是用来存放地址的,那么指针变量的大小8就得是4个字节的空间才可以 同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的大小就是8个字节。

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(double*));return 0;
}

 32位平台下地址是32个bit位(即4个字节):

 

64位平台下地址是64个bit位(即8个字节):

结论:
• 32位平台下地址是32个bit位,指针变量大小是4个字节
• 64位平台下地址是64个bit位,指针变量大小是8个字节
• 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。

 

 

3. 指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在同⼀个平台下,大小都是⼀样的,为什么还要有各种各样的指针类型呢? 其实指针类型是有特殊意义的,我们接下来继续学习。
3.1 指针的解引用
对比,下面2段代码,主要在调试时观察内存的变化。
代码1:
//代码1
#include <stdio.h>
int main()
{int n = 0x11223344;int* pi = &n;*pi = 0;return 0;
}

代码2:

#include <stdio.h>
int main()
{int n = 0x11223344;char* pc = (char*)&n;*pc = 0;return 0;
}
调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。
结论:指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。
比如: char* 的指针解引⽤就只能访问⼀个字节,而int* 的指针的解引用就能访问四个字节。

3.2 指针+-整数

先看⼀段代码,调试观察地址的变化。
#include <stdio.h>
int main()
{int n = 10;char* pc = (char*)&n;int* pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc + 1);printf("%p\n", pi);printf("%p\n", pi + 1);return 0;
}

运行结果: 

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
结论:指针的类型决定了指针向前或者向后走⼀步有大(距离)

 

3.3 void* 指针
在指针类型中有⼀种特殊的类型是 void * 类型的,可以理解为无具体类型的指针(或者叫泛型指
针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,void* 类型的指针不能直接进行指针的+-整数和解引用的运算。

 

例:
#include <stdio.h>
int main()
{int a = 10;int* pa = &a;char* pc = &a;return 0;
}
在上面的代码中,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警告,是因为类型不兼容。而使用void*类型就不会有这样的问题, void* 类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。

 


4. 指针运算

指针的基本运算有三种,分别是:
•   指针+- 整数
•    指针-指针
•   指针的关系运算
4.1 指针+整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。
  1. int arr[10] = {1,2,3,4,5,6,7,8,9,10};

 代码:

#include <stdio.h>
//指针+- 整数
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *(p + i));//p+i 这⾥就是指针+整数}return 0;
}

运行效果:

  

4.2 指针 - 指针

代码 :

#include <stdio.h>
int my_strlen(char* s)
{char* p = s;while (*p != '\0') //判断没有'\0';p++;return p - s;
}
int main()
{printf("%d\n", my_strlen("abcdef"));//存在'\0'有7个字符。return 0;
}

运行效果:

4.3 指针的关系运算 

代码: 

//指针的关系运算
#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz) //指针的⼤⼩⽐较{printf("%d ", *p);p++;}return 0;
}

 运行效果:


本章学习就到这啦!如果内容对你有用 ,请不忘三连支持一波!!!!

完。

 

http://www.xdnf.cn/news/7677.html

相关文章:

  • HarmonyOS实战:3秒实现一个自定义轮播图
  • 纯前端实现 导入/导出/模板下载功能
  • 变频器如何通过Profibus DP主站转Modbus RTU/TCP接入到上位机
  • DeepSeek的走红,会不会带动芯片市场新一轮增长?
  • Java中的ImageIo支持webp解析
  • 小白成长之路-Linux磁盘管理(一)
  • 如何管理和优化内核参数
  • [IMX] 07.LCD 显示
  • 【高斯函数】
  • 驱动相关基础
  • leetcode刷题日记——从前序与中序遍历序列构造二叉树
  • MES管理系统电子看板驱动企业智能制造
  • python Numpy-数组
  • 探索nsupdate:动态DNS更新的终极指南
  • 码钉枪行业2025数据分析报告
  • Java程序员从0学AI(二)
  • 使用F5-tts复刻音色
  • ArrayList源码分析
  • 实现商品列表
  • 建站系统哪个好?
  • 基于CATIA参数化圆锥建模的自动化插件开发实践——NX建模之圆锥体命令的参考与移植(二)
  • 笔记:显示实现接口如何实现,作用是什么
  • ollama部署模型
  • 工单派单应用:5 大核心功能提升协作效率
  • Ai学习之LangChain框架
  • STM32外设应用详解——从基础到高级应用的全面指南
  • 差分数组:原理与应用
  • 文献分享-临床预测模型-基于围手术期时间数据肝切除术后肝衰竭早期检测
  • CSS 背景全解析:从基础属性到视觉魔法
  • MinIO集群故障,其中一块driver-4异常