作者简介:一个走在前行路上的人
✨联系方式:2201891280(QQ)
⏳全文大约阅读时间: 80min
每个被C语言劝退的伙伴们是否看到这些概念脑壳疼,那种支配感觉扑面而来0.0
这次我就用一个小故事帮大家打通一下指针相关的任督二脉0.0
先放上一张神图。大名鼎鼎的冯·诺伊曼体系结构。
可以发现运算器能直接读取的程序是在存储器内的。那么运算器怎么拿到这个数据呢?
首先我们先明确,因为变量的读取都是从内存中的,所以一定会有一片空间在内存中。我们把它想象成有一栋公寓有一堆房间,我们想要找到我们的房间是不是得有个房间号?
然后有没有发现102 103 104 105
这四个房间我没画隔断,因为很多地方是按照门来编号的,但是一个大房间可能对应多个门呀。
- 上面的门的编号方式就是编制方式(可以一个门一个号,也可以两个门一个号),地址就每个门的房号,然后数据其实就是门内的内容。
- 我们根据房号找到家,计算机就是根据地址找到数据。
指针
其实就是地址。也就是上面的房号。
我们假如忘了门号就看看
外面门上的标识牌对吧?计算机内如果变量想要直到自己的地址也是需要看看自己的房号,怎么去看呢?用&
,因为是变量自己看所以就是&x
就拿到地址啦0.0
举个例子:#include
int main(){ int a = 0; printf("%d %d\n", &a,a); return 0; } 输出结果可能是:
2686748 1
其中地址是一个unsigned类型的整数(64位是unsigned longlong),至于为啥是无符号的,你见过谁家房号是负数么0.0
指针变量用来存放地址,然后可以看下图,就是指针变量,比较厉害,手里有把钥匙,并且这个钥匙可以开对应的门。
然后由于这是一把钥匙,所以在声明它的时候需要在它前面加个*
,表示它是一把钥匙。也就是int *p;
,一般来说都是把*
放在变量前面。
同时*
也表示拿钥匙开门,*p
就表示拿到对应的数据,所以图上的就是104号房间对应的元素。
刚才知道&
是取地址,就是看看房间号,那么p = &a
就可以把p这把钥匙变成a所对应的地址。此时再去*p
就是a的值。
看下面的程序:#include
int main(){ int a; int *p = &a; *p = 233; printf("%d %d", *p, a); return 0; } 输出结果是
233 233
,因为开房间的优先级很高,在赋值的时候是先打开门,再把数据写入,所以就把值写入到了a之中。最后输出就是两次a的值。
最后说明一下,不同的指针是不同的,比如下面的两把钥匙是不一样的。因为102-105是连着的,所以第一把钥匙钥匙+1实际地址会加4,而右边的指针对应的只有一个门,+1的时候只会加1,
其实对应的就是int *
和char *
类型的指针,其中一个长度为4,另一个为1。
这个指针的类型叫做基类型。
数组就是一片连续的空间,数组名称也作为数组的首地址使用。
根据上面提到的指针加1等于加对应的数据长度,所以a+i
和&a[i]
是完全相同的。
在枚举元素的时候可以这么写:#include
int main(){ int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for(int *p = a; p < a +10;p+) printf("%d ",*p); return 0; }
指针的减法
#include
int main(){ int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *p = a; int *q = &a[5]; printf("p = %d ", p); printf("q = %d ", q); printf("q - p = %d",q - p); } 输出结果是:
p = 2686688 q = 2686708 q - p = 5
会不会感觉震惊?这个q-p竟然不是20
???,其实指针的减法是计算两个指针直接差多少个基类型,因为是int所以返回的就是相差多少个int。
了解完了数组额的指针,那么指针数组是什么呢?就是有一片区域保存着钥匙的区域就是,就是指针数组。同时指针数组的名称也做为这片区域的首地址使用。
举个栗子
我声明了一个指针数组int *pnums[4]
分别对应上图的四把钥匙,那么pnums
的值就是201
,假如我想访问102的值就是*(pnums[0])
就能够访问啦。
注意:int *pnums[10]
和int (*punums)[10]
是完全不同的。后者代表的含义是这是一个指向长度为10的数组的一个指针。
人类的本质就是套娃。套娃可以生出万千花花世界0.0
其实高级指针就是告诉程序这是一个多少级的间接索引。比如二级指针就是需要进行两次查找值才是最终的数据。三级指针就是需要进行三次取值才是最终的数据。
就如上图,那么为啥一定要区分地址和最终的值呢?因为地址的长度和数据长度一般是不一样长的,比如64位机的寻址空间就是64位,而一个int是32位,为了防止出错就会进行区分。
其实高级指针就是多级索引,只有一层一层剥开才能拿到最终的数据啦。
所谓的指针函数,其实就是返回类型是指针的函数。
比如我们需要返回一组数据就需要返回数组的首地址。int *nums(int k,int * returnnums){ ... }
上面的一个函数就返回了一个int类型的指针。一般我们写程序为了知道返回指针的长度会有一个参数
returnnums
传进去来获得相应的数组长度。
其实函数指针也像上面说的那样,是函数保存的地址。之前的都是数据的地址,但是函数本质上也是内存内的一片区域,所以本质上来说对cpu来说没有太大的区别,所以当然可以有地址啦。
举个栗子int cmp(cont void *a, const void *b){ return *((int *)a) > *((int *)b) ? 1 : -1; } int main(){ ... qsort(nums,numssize,sizeof(int),cmp); ... }
上面的cmp当做qsort传参的时候就是对应的函数的指针,同样的,函数指针也有不同的基类。qsort要求的基类就是
int (*fn) (const void *,const void *)
,就是一个返回类型为int且传入参数为两个const void*
的函数。
不同的基类可能会造成解析错误(类比int 和char 长度不同拿到的数据会出错)。
其实我们可以发现:
- 指针放在后面就表示某个东西的地址
- 指针放在前面就表示本身的属性是指针
有没有觉得好记一些呢?
今天就写到这里了,如果大家觉得对你有帮助的话还希望大家动动手指给个三连0.0
你的支持是我前进最大的动力。