小工具知识网

当前位置: 首页 > 行业知识 > 灵魂指针,教给(一)

行业知识

灵魂指针,教给(一)

2026-11-08 00:31:43 来源:互联网转载

欢迎来到白刘的领域   Miracle_86.-CSDN博客

系列专栏  C语言知识

先赞后看,已成习惯

   创作不易,多多支持!

一、内存和地址

1.1 内存

在介���知识之前,先来想一个生活中的小栗子:

假如把你放在一个有100间屋子的酒店里,但是没有门牌号,你想让你的好朋友来找你玩,这种情况好朋友就得一个房间一个房间去找,非常的麻烦。但是如果有了门牌号,我们就可以告诉好朋友,他就可以迅速精准地找到你所在的房间。

 把上述的栗子,映射到计算机上,就成为了我们今天所介绍的内存。

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

事实上,也是通过把内存划分为一个个的小单元格,每个单元格的大小取1个字节。

bit - ⽐特位byte - 字节KBMBGBTBPB
1byte = 8bit1KB = 1024byte1MB = 1024KB1GB = 1024MB1TB = 1024GB1PB = 1024TB
其中,每个内存单元,相当于⼀个学生宿舍,⼀个⼈字节空间⾥⾯能放8个比特位,就好⽐同学们住的⼋⼈间,每个⼈是⼀个⽐特位。 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。

生活中,我们把门牌号叫作地址,计算机中也不例外,我们刚说了内存就像宿舍,就像屋子,那它的编号(门牌号)就是它的地址。而在C语言里,我们给它取了个新的名字——指针。

所以我们可以理解为:

内存单元的编号 == 地址 == 指针

1.2 如何理解编址

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,⽽因为内存中字节很多,所以需要给内存进⾏编址(就如同宿舍很多,需要给宿舍编号⼀样) 。 

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

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

硬件编址亦是如此。 

⾸先,必须理解,计算机内是有很多的硬件单元,⽽硬件单元是要互相协同⼯作的。所谓的协同,⾄少相互之间要能够进⾏数据传递。但是硬件与硬件之间是互相独⽴的,那么如何通信呢?答案很简单,⽤"线"连起来。⽽CPU和内存之间也是有⼤量的数据交互的,所以,两者必须也⽤线连起来。不过,我们今天关⼼⼀组线,叫做地址总线。 

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

二、指针变量和地址

2.1 取地址操作符(&)

我们在操作符那里挖了个坑,详见:

武器大师——操作符详解(下)-CSDN博客

理解了内存与指针的关系,我们再回到C语言,在C语言中创建变量的过程其实本质就是向内存申请空间,eg:

#include int main(){ int a = 10; return 0;}

上述代码,我们创建了整型变量a,我们知道整型是4个字节,所以向内存申请了这四个空间:

0x006FFD700x006FFD710x006FFD720x006FFD73

我们如何得到a的地址呢?这里用到了取地址操作符(&),长得像我们之前学过的按位与。

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

虽然我们整型占用四个字节,但是实际上我们只需要知道第一个字节的地址,就可以顺藤摸瓜获取接下来的地址。

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

2.2.1 指针变量

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

#include 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) 类型的对象。
2.2.3 解引用操作符( * )

我们将地址保存起来,未来是要使⽤的,那怎么使⽤呢? 

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

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

#include int main(){ int a = 100; int* pa = &a; *pa = 0; return 0;}

 上述代码第7行就使用了解引用,*pa其实就是表示的指针指向的对象,也就是变量a,这步操作相当于把a的值改成了0。有人会问道:“那我直接改a=0不是更简单嘛,为什么非要写一个指针呢?”,其实这样对a多了一种途径,会让你的代码更有灵活性和可操作性。

2.3 指针变量的大小

这里先说结论:32位处理器(x86)下是4个字节,64位处理器(x64)下是8个字节。

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

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

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

#include //指针变量的⼤⼩取决于地址的⼤⼩//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;}

x86环境下结果:

x64环境下结果:

注意指针变量的大小跟类型无关!只要是指针类型的变量,在相同的平台下,大小是相同的。

三、指针变量类型的意义

既然我们前面说了,指针变量的大小在相同的平台下是相同的,那不同类型的指针变量有什么意义呢?

3.1 指针的解引用

我们来看下面两段代码:

//代码1#include int main(){ int n = 0x11223344; int *pi = &n;  *pi = 0;  return 0;}
//代码2#include int main(){ int n = 0x11223344; char *pc = (char *)&n; *pc = 0; return 0;}

经过调试我们可以看到,代码1将n的4个字节全改为0,而代码2只改了一个字节。

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。 ⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

3.2 指针+-整数

#include 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*类型的跳过了4个字节。这就是指针类型导致的变化。

结论:指针的类型决定了指针向前或向后走的距离有多大

 3.3 void*指针

这是一种特殊类型的指针,void 译为无效、空的,可以理解为无具体类型的指针或者泛型指针,我们在前面学过void类型的函数。它可以接收任意类型的地址,但是我们说人无完人,它也有局限性的,它不可以进行+-以及解引用的操作。

来举个例子:

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

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

 使用void*接收:

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

一般void*类型的指针是用在函数参数的部分的,我们以后也会讲到(又挖个坑 *^▽^* )。

四、const修饰指针

4.1 const修饰变量

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

 

#include int main(){ int m = 0; m = 20;//m是可以修改的 const int n = 0; n = 20;//n是不能被修改的 return 0;}

上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n。 

但是我们如果绕过n,使用n的地址,去修改n,就可以做到了:

#include int main(){ const int n = 0; printf("n = %d\n", n); int*p = &n; *p = 20; printf("n = %d\n", n); return 0;}

来看运行结果: 

 

我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让p拿到n的地址也不能修改n,那接下来怎么做呢?

4.2 const修饰指针变量

 这里讲个小故事,说有一对小情侣在逛街,女生想吃凉皮,一碗凉皮十块钱,但是男生兜里只有十块钱,女生看了看男生,男生说凉皮一看就不好吃,女生说你要是不想买,我就换男朋友,男生着急了,一狠心说了句,买可以但是你不可以换男朋友,女生点了点头,最后他们幸福的生活在了一起。

这个故事其实就类似于const修饰指针变量。

int *const p;int const* p;

我们把p看成钱,第一行代码就是,const放在右边,修饰限制指针p所指向的值,这个值不可以更改,就像刚开始,男生不想动自己仅剩的钱。

第二行,const放在左边,就可以看成,男生不想让女生换对象,const修饰限制p指向的对象不可以变。

const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。 const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

五、指针运算

指针运算一共分为三种:指针+-运算、指针-指针、指针的关系运算。

5.1 指针+-整数

由于数组在内存中的存放是连续的,我们根据这一特性,只要知道第一个元素的地址,就可以顺藤摸瓜得到任意想要的元素,这就是指针的灵活性所在。

#include //指针+- 整数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

上一篇:揭秘诛仙单机副本轻松通关秘籍,攻略大解密!

下一篇:宝塔面板APK下载与设置详解