C:初识指针—学习笔记

目录

前言:

1、内存和地址

[1.1 理解内存和地址](#1.1 理解内存和地址)

[1.2 理解编址](#1.2 理解编址)

2、指针变量和地址

[2.1 取地址操作符:&](#2.1 取地址操作符:&)

[2.2 指针变量](#2.2 指针变量)

[2.3 如何拆解指针类型](#2.3 如何拆解指针类型)

[2.4 解引用操作符(*)](#2.4 解引用操作符(*))

[2.5 指针变量的大小](#2.5 指针变量的大小)

3、指针变量类型的意义

[3.1 指针的解引用](#3.1 指针的解引用)

[3.2 指针+-整数](#3.2 指针+-整数)

[4、void* 指针](#4、void* 指针)

结语:


前言:

谈及指针,大部分人只有一个感觉:太难学了,好抽象啊!但是,请不要着急,今天当你看完这一篇后,相信你肯定能够理解什么是指针了。


1、内存和地址

1.1 理解内存和地址

在介绍指针前,我们需要先了解什么是内存和地址。

关于内存和地址,生活中有一个例子可以很好的解释它们

比如说你住在一栋宿舍楼,大楼内有100个房间,但是房间并没有编号。这时,你的一个朋友来找你玩,如果想找到你,就得一个房间一个房间的寻找,这样效率很低。但是,如果我根据楼层和楼层的房间的情况,给每一个房间都编上号,比如:

1楼:101 102 103......

当有了门牌号,这时候你只需要将门牌号告诉你朋友,他就可以很快速的找到房间,找到房间里的你。

如何将上面的例子抽象到计算机里呢?你可以理解宿舍楼就是内存,房间就是内存中的一个内存单元,房间里的你就是数据,而门牌号就是地址。

所以内存就是存储数据的空间

我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取 的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?

cpu从内存中读取数据,就好比是你朋友要在宿舍楼里找到你,而你朋友找你,也只是在一个个房间寻找。大楼里的一个一个房间的划分,让人们对大楼内的面积能够充分利用,而计算机中也是如此。内存的空间只有8GB/16GB/32GB,因此对于内存的合理运用也变的很重要。

计算机中把内存也划分为一个个的内存单元,每个内存单元的大小取1字节。

补充)计算机中常见的单位:

一个bit可以存储一个2进制位的1和0

|------------------|------------|---|---|---|---|
| 地址(门牌号) | 内存(大楼) | 其中,每个内存单元,相当于⼀个学⽣宿舍,一 个字节空间里面能放8个比特位,就好比同学们住 的八⼈间,每个人是⼀个比特位。 ||||
| 0xFFFFFFFF(16进制) | 1个字节 | 其中,每个内存单元,相当于⼀个学⽣宿舍,一 个字节空间里面能放8个比特位,就好比同学们住 的八⼈间,每个人是⼀个比特位。 ||||
| 0xFFFFFFFE | 1个字节 | 其中,每个内存单元,相当于⼀个学⽣宿舍,一 个字节空间里面能放8个比特位,就好比同学们住 的八⼈间,每个人是⼀个比特位。 ||||
| | 1个字节 | 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的门牌号),有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。 ||||
| | (内存单元) | 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的门牌号),有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。 ||||
| | | 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的门牌号),有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。 ||||
| | | 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的门牌号),有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。 ||||
| | | 生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址, C语言中给地址起了新的名字叫:指针 ||||
| | 1个字节 | 生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址, C语言中给地址起了新的名字叫:指针 ||||
| 0x00000001 | 1个字节 | 生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址, C语言中给地址起了新的名字叫:指针 ||||
| 0x00000000 | 1个字节 | 生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址, C语言中给地址起了新的名字叫:指针 ||||

所以我们可以理解为:
内存单元的编号 == 地址 == 指针

1.2 理解编址

生活中关于我们可以看到通过宿舍门上的门牌号,直接找到我们想去的地方。门牌号是真实存在与宿舍门上的。而内存中的地址我们该怎么理解呢?

计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。

这就像钢琴,吉他上面没有刻上"剁、来、咪、发、 唆、拉、西"这样的信息,但演奏者照样能够准 确找到每⼀个琴弦的每⼀个位置,这是为何?因为制造商已经在乐器硬件层面上设计好了,并且 所有的演奏者都知道。本质是⼀种约定出来的共识!

有点抽象,举个例子:

图书馆里有一排排的书架,每个书架又有一层层的格子,这些格子就好比内存中的存储单元。

给每个格子编上号,也就是编址,就像是要给图书馆里的每个格子都贴上标签。

那这个标签是怎么贴上去的呢?这就得靠图书馆的"硬件设计"了。

比如说,书架的排列方式、格子的划分规则,就像是硬件的设计。

想象一下,书架是固定的,它们的位置和大小决定了格子的位置和数量,这就好比硬件决定了内存有多少个可以存储数据的地方。

然后,有一套专门的标记系统,就像特殊的机器或者装置,按照书架和格子的排列,给每个格子都印上编号,这就是通过硬件实现了编址。

那硬件设计又是怎么实现的呢?

首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协 同,至少相互之间要能够进⾏数据传递。 但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单,用"线"连起来。 ⽽CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。通过地址总线,我们就可以了解什么是硬件编址了。

32位机器有32根地址总线, 每根线只有两态,表示0,1【电脉冲有无】,那么⼀根线,就能表示2种含义,2根线就表示4种含义,依次类推。32根地址线,就能表示2^32种含义,每⼀种含义都代表⼀个地址。 地址信息被下达给内存,在内存上,就可以找到 该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。

内存编址这件事,是靠计算机里面那些实实在在的硬件设备,按照一定的规则和办法来做好的。

简单来说,计算机的编址是通过硬件设计把每一个内存单元的地址都固定好了,不需要把地址额外的存起来。

知识补充:

  1. 32位机器有32根地址总线64位机器有64根地址总线。
  2. 地址总线是实际存在的物理电线

2、指针变量和地址

当我们了解了内存和地址的关系后,就可以开始对指针的学习啦!

在C语言中变量创建的本质是向内存申请空间

比如说:int a = 10;

这串代码就相当于向内存内申请了4个字节,一个整型占4个字节,这串空间我们想存放的数据便是变量10。

2.1 取地址操作符:&

cpp 复制代码
int main()
{
	int a = 0x11223344;//16进制数字
	return 0;
}

16进制0x11223344,一个16进制位可以改写为4个二进制位,因此,11223344可以改写为32个二进制位表示,刚好一个整型可以放下。

我们可以调试来看一下内存:

打开后,输入**&a** 并敲下回车键,列改为一行(记得在x86环境下观察,比较方便)

我们可以看到44,33,22,11都各占一个字节,每一个字节都有一个地址。

我们将列数改为4列再观察

从上面我们可以看到a确实向内存申请了4个空间。

读到这我们可能会有一个新的问题,欸,4个字节都有地址,那我们怎么知道a的地址是哪一个呢?

还记得前面调试的时候我们是怎么观察地址的吗?我们通过输入&a按下回车后出现了0x00E2FEC4,因此0x00E2FEC4便是a的地址,我们也可以发现这个地址和 44 所占字节的地址一样。

总结一下,&a取出的是a所占4个字节中地址较小的字节的地址

虽然整型变量占用4个字节,但是我们只要知道了第一个字节地址,顺藤摸瓜访问到4个字节的数据也是可行的。

代码展示一下,怎么打印地址

cpp 复制代码
int main()
{
	int a = 0x11223344;
	printf("&a=%p\n", &a);
	return 0;	
}

知识补充:

  • &是取地址操作符,想要得到地址,就需要使用这个操作符
  • %p:用来打印地址的占位符

结果展示:

和前面调试的结果不是一样,是因为在打印的时候又申请了新的空间。

2.2 指针变量

在我们之前学习的过程中,我们如果想要将一个整数存储起来,就会创建一个整型变量,比如我们想存储数字10,如下代码:

cpp 复制代码
int main()
{
	int a = 10;
	return 0;	
}

我们通过创建了一个整型变量 a 来存储10。

那么如果我们想要将我们通过&得到的地址存储起来,有没有什么办法呢?我们可以将地址存储在指针变量中。比如我们想要存放 n 的地址

cpp 复制代码
int main()
{
	int a = 10;
	int * pn = &n;
	return 0;	
}

解读:int * pn = &n;

1、pn被称为指针变量

为什么呢?

&n------n的地址------地址就是指针

pn = &n;

pn就是用来存放地址的,也可以说是用来存放指针的

指针变量就是存放指针的变量。

可以通过和整型变量来理解指针变量,

整型变量:a就是用来存放整数的。

2、int * 被称为指针类型

int a = 10;

int * pn = &n;

对比就可以发现,int是整数的类型,int*是指针的类型。

2.3 如何拆解指针类型

上文说到了之指针的类型是(int *),那么我们该如何理解指针类型呢?

pn的类型是int *,我们需要分别理解

  • *:说明pn是指针变量
  • int:说明pn指向的对象是int类型
cpp 复制代码
char ch = 'x';

如果我们想要存放x的地址该怎么写呢?

char * pc = &ch;

这样我们就存放了x的地址

*****告诉我们pc是指针

char则告诉我们指针指向的对象是char类型。

2.4 解引用操作符(*)

当我们将地址保存起来后,是为了后面能够使用,那么我们该怎么使用呢?

在现实生活中,我们使用地址要找到⼀个房间,在房间里可以拿去或者存放物品。

C语言中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里必须学习⼀个操作符叫解引用操作符(*)。

cpp 复制代码
int main()
{
	int n = 10;
	int * pn = &n;
    //解引用操作符(间接访问操作符)
	*pn = 100;
	printf("%d", n);
	return 0;	
}

上述代码中的*pn就使用了解引用操作符, *pn 的意思就是通过pn中存放的地址,找到指向的空间, *pn其实就是n变量了;所以*pn=100,这个操作符是把n改成了100.

从结果上看,n的值的确被改为了100

或许通过这个例子你会觉得指针有是什么用?如果只是想修改n的值,为什么不直接写一个n=100呢?这样不是更方便吗?

其实这里是把n的修改交给了pn来操作,这样对n的修改,就多了⼀种的途径,写代码就会更加灵活, 后期慢慢就能理解了。

其实这里有一个很好的例子能说明指针的作用,生活中,有些事情是不方便自己去做的,因此呢,就需要委托别人来代替你做,比如说一个老板想要喝奶茶,但是他不会自己顶着大太阳出去买,而是会吩咐他的秘书取帮他完成,差不多就是这样,可能有些不恰当,见谅哈!

2.5 指针变量的大小

我们知道我们创建一个整型变量int的大小是4个字节,字符变量char的大小是1个字节,那么指针变量的大小又是多少呢?

思考过程:

指针变量存放的是地址,地址的存放需要多大的空间呢?知道地址存放的空间就是指针变量的大小

也就是说指针变量的大小取决与地址的大小

通过前面的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产生的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。

如果指针变量是用来存放地址的,那么指针变的大小就得是4个字节的空间才可以。

同理64位机器,假设有64根地址线,⼀个地址就是64个二进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变量的大小就是8个字节。

cpp 复制代码
int main()
{
	int n = 10;
	int * pn = &n;
	printf("%zd\n", sizeof(pn));
	return 0;	
}

在32位机器上: 可以看到打印的大小是4个字节

在64位机器上: 可以看到打印的大小是8个字节

那么指针类型是否会影响指针变量的大小呢?我们来测试一下

cpp 复制代码
int main()
{	
	printf("%zd\n", sizeof(int*));//整型
	printf("%zd\n", sizeof(char*));//字符
	printf("%zd\n", sizeof(short*));//短整型
	printf("%zd\n", sizeof(double*));//双精度浮点型
	return 0;	
}

在32位系统上结果:

在64位系统上结果

我们可以看到,不管指针类型是什么,都不会影响指针变量的大小,指针类型的变量大小,在相同平台下,大小都是相同的。

总结:

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

3、指针变量类型的意义

既然指针变量大小与指针类型无关,那么为什么还要搞指针的变量类型呢?

3.1 指针的解引用

cpp 复制代码
int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	return 0;
}

调试过程:(注意观察内存里的值)

运行到292行时内存展示44 33 22 11

当经过*pi = 0 之后

内存里4个字节全部变为了 0

如果我们不用int *的指针类型,改为char *的类型,结果又是如何呢?

我们可以看到只是将n的第⼀个字节改为0。

我们可以看到int类型指针可以访问4个字节,而char类型指针只访问了1个字节。

通过对比,我们可以得到一个结论:

结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。

3.2 指针+-整数

cpp 复制代码
int main()
{
	int n = 0x11223344;
	int* pi = &n;
	char* pc = &n;		
	printf("&n = %p\n", &n);
	printf("pi = %p\n", pi);
	printf("pi+1 = %p\n", pi+1);
	printf("pc = %p\n", pc);
	printf("pc+1= %p\n", pc+1);
	return 0;
}

结果:

我们可以发现, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。

**结论:**指针的类型决定了指针向前或者向后走一步有多大(距离)。

4、void* 指针

void的意思是无,或者空

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

cpp 复制代码
int main()
{
	int n = 10;
	char* pc = &n;
	return 0;
}

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

使用void*类型的指针接收地址就不会出现警告

void* 类型的指针不能直接进行指针的+-整数和解引用的运算

cpp 复制代码
int main()
{
	int n = 10;
	void* pc = &n;
	*pc = 20;
	return 0;
}

当我们想运行的时候,就会报下面这个错误

void* 类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。

这是因为void*是一个无具体类型的指针,当进行解运算的时候,没法确定访问几个字节。

既然如此,那void*有什么作用呢?

专门用来存放别人传送过来的地址,当你不知道别人给你传的是什么类型的指针的时候,就可以使用void*来存放,当需要进行解运算的时候,在使用强制类型转换来实现。

比如:

cpp 复制代码
int main()
{
	int n = 10;
	void* pc = &n;
	*(int*)pc = 20;
	return 0;
}

这样就将类型强制转换成了整型指针,结果就可以打印出来了


结语:

本篇文章主要讲了指针的基本知识,通过本篇文章能够了解什么是指针,指针变量,指针类型是什么。后面会继续更新指针相关知识,希望能够帮助大家攻克指针这一模块。

相关推荐
stm 学习ing28 分钟前
HDLBits训练5
c语言·fpga开发·fpga·eda·hdlbits·pld·hdl语言
虾球xz1 小时前
游戏引擎学习第55天
学习·游戏引擎
就爱学编程2 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
oneouto2 小时前
selenium学习笔记(二)
笔记·学习·selenium
sealaugh322 小时前
aws(学习笔记第十九课) 使用ECS和Fargate进行容器开发
笔记·学习·aws
炭烤玛卡巴卡2 小时前
学习postman工具使用
学习·测试工具·postman
北国无红豆2 小时前
【CAN总线】STM32的CAN外设
c语言·stm32·嵌入式硬件
单片机学习之路3 小时前
【C语言】结构
c语言·开发语言·stm32·单片机·51单片机
thesky1234563 小时前
活着就好20241224
学习·算法
蜗牛hb3 小时前
VMware Workstation虚拟机网络模式
开发语言·学习·php