【C语言】指针详细解读1

1. 内存和地址

1.1 内存

在讲述内存之前,我们先拿生活中的例子类比一下:

假如我们要寻找酒店的一位朋友,首先我得知道以下一些信息:知道他是 ,知道酒店名 ,知道酒店房间号 就表示我们不能去找其他的东西:桌子、凳子等等,只能是是人。酒店名 就表示我们只能在这个酒店去寻找,不能去其他地方寻找。房间号就表示我们去酒店的这个地方就能找到。

我们把上述的例子对照到我们计算机中,就很好理解。
我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何⾼效的管理呢?
其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。

计算机中常⻅的单位(补充):
⼀个⽐特位可以存储⼀个2进制的位1或者0

objectivec 复制代码
bit - ⽐特位       //换算单位
byte - 字节        1byte = 8bit
KB                 1KB = 1024byte
MB                 1MB = 1024KB
GB                 1GB = 1024MB
TB                 1TB = 1024GB
PB                 1PB = 1024TB

其中,每个内存单元,相当于⼀个学⽣宿舍,⼀ 个字节空间⾥⾯能放8个⽐特位,就好⽐同学们住
的⼋⼈间,每个⼈是⼀个⽐特位。
每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。
⽣活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起
了新的名字叫:指针。
所以我们可以理解为: 内存单元的编号 == 地址 == 指针

2. 指针变量和地址

2.1 取地址操作符(&)

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


⽐如,上述的代码就是创建了整型变量a,内存中申请4个字节,⽤于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:
0x00851750
0x00851751
0x00851752
0x00851753
那我们如何能得到a的地址呢?
这⾥就得学习⼀个操作符(&)-取地址操作符

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


虽然整型变量占⽤4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可⾏的。

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

2.2.1 指针变量

那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如: 0x00851750,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。
⽐如:

objectivec 复制代码
#include <stdio.h>
int main()
{
 int a = 10;
 int * p = &a;//取出a的地址并存储到指针变量pa中
 
 return 0;
}

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

2.2.2 如何理解指针类型

我们看到p的类型是 int* ,我们该如何理解指针的类型呢?

复制代码
int a = 10;
int * p = &a;

这⾥p左边写的是 int* , * 是在说明p是指针变量,⽽前⾯的 int 是在说明p指向的是整型(int)类型的对象。
我们对照生活中的例子,假如我要寻找的人叫小明,这样就可以得到三个信息:人、房间号、小明。我们对照一下:

如果char类型的变量ch,ch的地址,要放在什么类型的指针变量中呢?

objectivec 复制代码
char ch = 'a';
char* p = &ch;

2.2.3 解引用操作符

我们将地址保存起来,如果要使⽤的话,那要怎么使⽤呢?
在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。
C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

objectivec 复制代码
#include <stdio.h>

int main()
{
	int a = 10;
	int* p = &a;
	*p = 0;
	printf("&a = %p\n", p);
	printf("a  = %d\n", a);

	return 0;
}

运行结果:

上⾯代码中第6⾏就使⽤了解引⽤操作符, *p 的意思就是通过p中存放的地址,找到指向的空间, *p其实就是a变量了;所以*p = 0,这个操作符是把a改成了0。
有的同学会想,这⾥如果⽬的就是把a改成0的话,写成 a = 0; 不就完了,为啥⾮要使⽤指针呢?
其实这⾥是把a的修改交给了*p来操作,这样对a的修改,就多了⼀种的途径,写代码就会更加灵活, 后期慢慢就能理解了。

2.3 指针变量的大小

如果我们去了解计算机知识,我们可以知道,一台32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。
如果指针变量是⽤来存放地址的,那么指针变的⼤⼩就得是4个字节的空间才可以。
同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列(也就是64个bit位),存储起来就需要8个字节的空间,指针变量的⼤⼩就是8个字节。

x86(32位)环境输出结果:

x64(64位)环境输出结果:


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

3. 指针变量类型的意义

指针变量的⼤⼩和类型⽆关,只要是指针变量,在同⼀个平台下,⼤⼩都是⼀样的,为什么还要有各种各样的指针类型呢?
其实指针类型是有特殊意义的,我们接下来继续学习。

3.1 指针的解引用

对⽐,下⾯2段代码,主要在调试时观察内存的变化。
代码1:

objectivec 复制代码
//代码1
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 int *pi = &n; 
 *pi = 0; 
 return 0;
}

调试结果:

代码2:

objectivec 复制代码
//代码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 指针+-整数

代码:

objectivec 复制代码
#include <stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;

	printf("&n     = %p\n", &n);
	printf("pc     = %p\n", pc);
	printf("pc + 1 = %p\n", pc + 1);
	printf("pi     = %p\n", pi);
	printf("pi + 1 = %p\n", pi + 1);
	return 0;
}

运行结果:


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

3.3 void* 指针

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

objectivec 复制代码
#include <stdio.h>

int main()
{
	int a = 10;
	int* pa = &a;
	char* pc = &a;
	return 0;
}

在上⾯的代码中,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警告(如下图),是因为类型不兼容。⽽使⽤void*类型就不会有这样的问题。

使⽤void*类型的指针接收地址:

objectivec 复制代码
#include <stdio.h>

int main()
{
	int a = 10;
	int* pa = &a;
	void* pc = &a;
	return 0;
}


这⾥我们可以看到, void* 类型的指针可以接收不同类型的地址,但是⽆法直接进⾏指针运算。
那么 void* 类型的指针到底有什么⽤呢?
⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以
实现泛型编程的效果,使得⼀个函数来处理多种类型的数据。

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio