一、前言
指针是C语言的精髓所在,然而指针却是C语言学习中的难点与重点,不少小伙伴因为过不了这道坎儿而转向学习其他语言甚至放弃学 习……
在这篇博客中,我将把我自己对指针的理解,加以生动的比喻,帮助正在受指针折磨的小伙伴以不同的视角理解指针。
二、正文
-
海王main()和他的小鱼们:
众所周知,C语言的程序只能有一位main(),以下称为海王(手动滑稽),再海王的龙宫内,养着
呆萌可爱型char、
温柔可人型int、
坚强独立型float、
成熟知性型double、
坚强独立型long……等各种类型的小鱼:char xiaoC; int xiaoI; float xiaoF; double xiaoD; long xiaoL; /*类型*/ /*小鱼的变量名*/ //and so on……,unsigned int,const int 啥的就不举例了
当然养鱼是要有代价的,一个char型小鱼需要1字节内存空间,int型需要4字节……可用函数sizeof(type)查看小鱼所需空间:
#include<stdio.h> void main() { printf("size of char is %d\n",sizeof(char)); printf("size of int is %d\n",sizeof(int)); printf("size of float is %d\n",sizeof(float)); printf("size of double is %d\n",sizeof(double)); printf("size of long is %d\n",sizeof(long)); }
运行结果如下:(不同编译器结果可能不一样)
当然有些海王不满足于这些单一类型的小鱼,于是有了结构体和共同体这样的自定义类型小鱼,先来看看结构体:struct LixiangXiaoyu { int a; float b; char c; long l; double d; }
于是就有了一种集合多种性格类型的小鱼类型,当然你也可以自己当海王定义自己想要的类型。然鹅以上只是自己的蓝图,想要养一只这 样的鱼也得先初始化(想不到什么渣男词汇形容这个过程……),于是就可以有一只随时变换性格的小鱼:
#include<stdio.h> struct LixiangXiaoyu { int a; float b; char c; long l; double d; }; void main() { struct LixiangXiaoyu xiaoyu1; /* 理想小鱼类型 */ /*小鱼变量名*/ }
这样复合“性格”型小鱼需要的“供养”也是很高的,我们也可以用sizeof()查看这种小鱼需要的字节数:
#include<stdio.h> struct LixiangXiaoyu { int a; float b; char c; long l; double d; }; void main() { struct LixiangXiaoyu xiaoyu1; /* 理想小鱼类型 */ /*小鱼变量名*/ printf("size of struct LixiangXiaoyu is %d\n",sizeof(struct LixiangXiaoyu)); }
运行结果如下:
可见供养这样的鱼需要的空间是很大的。
有些更渣的海王也想要这样多性格小鱼,又不舍得花费那么多内存供养,于是又有了共同体:union LixiangXiaoyu2 { int a; float b; char c; long l; double d; };
与结构体类似,以上只是海王yy的蓝图,要养共同体小鱼也需要初始化:
#include<stdio.h> union LixiangXiaoyu2 { int a; float b; char c; long l; double d; }; void main() { union LixiangXiaoyu2 xiaoyu2; /* 共同体小鱼类型 */ /*小鱼变量名*/ }
再来看看这样的小鱼需要的空间:
#include<stdio.h> union LixiangXiaoyu2 { int a; float b; char c; long l; double d; }; void main() { union LixiangXiaoyu2 xiaoyu2; /* 共同体小鱼类型 */ /*小鱼变量名*/ printf("size of LixiangXiaoyu2 is %d",sizeof(union LixiangXiaoyu2)); }
运行结果:
于是便可以,周一int小奶猫,周二long性感大姐姐……(哔)还只有一个double的空间(共同体中需要字节最长的类型长度)(手动滑稽)
鱼多了也不太好照顾,于是精明的海王会把相同类型的小鱼放在同一个“房子”数组里,一只小鱼一个房间:int a[10]; char c[10]; struct LixiangXiaoyu yu[10]; union LixiangXiaoyu2 yu2[10]; /*…………
于是翻牌啥的就可以根据数组名,在根据房间号找到想要宠幸的小鱼了( ˃̶̤́ ꒳ ˂̶̤̀ )……
-
指针在海王和小鱼间扮演的角色:
某圣诞节/情人节/养鱼纪念日……,海王要给某位int型小鱼a送礼物1,于是就有以下送法:
①直接送:int a; a=1;
这种送法直达鱼心,将海王对小鱼满满的宠幸送到a手上。
②找个跑腿送:要让人跑腿给某只小鱼送礼物,需要知道1、小鱼的地址;2小鱼的类型(毕竟不能得到海王的亲自宠幸,不按性格哄会 把跑腿轰走(滑稽))。跑腿哪来?找舔狗啊,只要给它一个int型长度的内存字节(指针变量存着其他小鱼的地址,地址为16进制数字所以为int型),就会死心塌地把礼物送达,还给哄好……
这个舔狗就是我们的指针变量int a; float b; char c; //等待宠幸的鱼儿们 int* pts_a = &a; float* pts_b = &b; char* pts_c = &c; /* int*\float*\char* 召唤一只指针舔狗(正经名称:指针变量),告诉ta要要给什么性格类型的小鱼服务。 &:取址运算符 pts_a = &a:把小鱼a的地址告诉pts_a */ /*也可以先召唤指针舔狗,在取地址*/ int *pts_a; pts_a = &a;
接着,指针舔狗们,记着服务对象的地址,带着tm的礼物,去给海王的小鱼们服务了
#include<stdio.h> void main() { int a; float b; char c; //海王的小鱼们 int* pts_a = &a; float* pts_b = &b; char* pts_c = &c; //召唤舔狗指针并告诉舔狗们🐟的地址 *pts_a = 1; *pts_b = 1.5; *pts_c = 'a'; //让舔狗指针把礼物送达 printf("%d %.1f %c\n",a,b,c); //看看舔狗们是否完成了任务 }
于是舔狗们怀着礼物,以及对被海王纳入鱼塘的渴望,奔赴对应的小鱼,运行结果:
礼物全部送达,小鱼们不哭也不闹~~
当然小鱼就在那么近,海王直接把礼物送过去不久好了?还要找个指针间接地送?
这当然不是舔狗(指针)的正确用法,以下将展示舔狗的BudeHouse用法 -
指针的简单用法:
①函数传参:某天,海王要给int型的小鱼a礼物1,给int型小鱼b礼物2int a = 1,b = 2;
可是转念一想,把2给a,1给b貌似更好,于是需要引入一只新的小鱼t把a,b的礼物换一下
t=a; a=b; b=t;
让我们看看交换的结果:
#include<stdio.h> void main() { int a=1,b=2; int t; t=a; a=b; b=t; printf("a=%d b=%d\n",a,b); }
运行结果:
经过这次事件,海王想:如果写个函数,以后再出现这种情况,或者下面还有好多要调换礼物的小鱼,就可以直接调用函数了:void change(int num1,int num2) { int t; t = num1; num1 = num2; num2 = t; }
有了这个函数,海王想:只要调用函数就可以调货了:
#include<stdio.h> void change(int num1,int num2) { int t; t = num1; num1 = num2; num2 = t; } void main() { int a = 1,b = 2; change(a,b); printf("a=%d b=%d\n",a,b); }
运行结果:
海王发现,a,b的礼物并没有被调换,这是为什么?
我们来看change(a,b);这一句,如果把函数拆开,程序是这样执行的:#include<stdio.h> void main() { int a = 1,b = 2; //change(a,b); int num1 = a,num2 = b; //调用函数时,把a的值幅给num1,b的给num2; int t; t = num1; num1 = num2; num2 = t; //然后把num1和num2的值调换了一下,a的还是1,b的还是2 printf("a=%d b=%d\n",a,b); }
可见,函数只是把小鱼a和b以及他们的礼物复制了一份,创造了新的小鱼num1和num2,然后把他们的礼物交换了一下,函数执行完,num1和num2以及 t 也被打入冷宫(内存被释放),a和b还是和原来一样(手动黑人问号)
那要怎么正确地调换礼物呢?这时候就到了舔狗指针出场了:如果把函数形参换成舔狗指针,把a,b的地址告诉他们,他们就会再函数里完成跑腿任务:#include<stdio.h> void change(int* num1,int* num2) { int t; t = *num1; *num1 = *num2; *num2 = t; } void main() { int a = 1,b = 2; change(&a,&b); printf("a=%d b=%d\n",a,b); }
运行结果:
可见,在函数里,舔狗指针成功完成了跑腿任务,在函数完成后指针num1和num2即被释放, 舔狗指针舔到最后BudeHouse
总结:运用指针可以跨函数传参,把参数的值动态地传回主函数……②指针与数组:
在讲下面的之前,先用正经的语言梳理一下:
当我们要间接地取出一个变量的值,或者给变量赋值时,我们需要知道变量的首地址和长度,才能准确、完整地取出它的值,或者把值存进去。指针变量表示的是所指向变量的首地址,指针类型表示所指向变量的类型(用类型可以知道变量的长度)。
如:int a; int* pts_a=&a;
指针变量pts_a存放着a的地址(准确性),int* 则表示指向的变量为int型,取或存时要取或存4个字节的长度(完整性)。
指针也可以加减,比如pts_a+1
,表示pts_a的值加上一个int型变量的长度,最后值为1004。
**正文开始:**以下将帮助大家理解指针与数组的关系
在海王的一栋楼(数组)int a[10];
里,住着a[0],a[1],a[2]……a[9]十只int小鱼,有一位舔狗指针a帮助海王照料10只小鱼,于是当海王要给一栋楼的小鱼送礼物时就可以:#include<stdio.h> void main() { int a[10],i=0; //数组中,数组名可以作为数组第一个元素a[0]的指针,存放数组a[0]的地址 for(i=0;i<10;i++) { *(a+i)=i; } /*a指向a[10]的第一个元素a[0]的地址,a+i表示a[i]的地址, 该循环表示,舔狗指针a知道a[0]的地址,把0送到a[0], 接着a+i表示下一个小鱼房间的地址,循环10次,把0-9分别送 到a[0]-a[9] 指针的加减表示把地址后移(加)或者前移(减)多少个所指向 类型的长度单位*/ for(i=0;i<10,i++) { printf("%d ",a[i]); } /*上面那个for循环也可写成: for(i=0;i<10,i++) { printf("%d ",*(a+i)); } 用*(a+i)表示a[i]*/ printf("\n"); }
运行结果:
将上面的推广到二维数组a[3][3];
,可以表示海王一栋3层的楼,每栋楼有3只小鱼一鱼一个房间
每一层分别由一只舔狗a[0],a[1],a[2]为这一层的小鱼服务,而这三只舔狗又有一只更低级的舔狗a为其服务
在这个数组中,数组名a表示指向数组第一个元素a[0] 这个 一维数组(楼层第一层)的首地址a+1就表示数组下一个元素a[1] (第二层)这个 一维数组的首地址,同理a+i(第i层)就表示数组元素a[i]这个一维数组的首地址
a [0] 就可以理解为一个一维数组第一个元素的指针,表示数组第一个元素a [0][0]
的地址(第一个房间) ,同理 a[0]+i表示a[0][i] (第i个房间)的地址
ps:虽然a[0]的地址与a[0][0]地址数值上一样,但是却又实质上的区别,关于这点将在以后的博客中详讲
三、总结
该博客纯属自己对指针的个人理解。我也是个看完指针一个多月的c语言小白,有不全面或者脱离实际的理解希望各位大佬在评论中指正,还是希望可以通过这篇博文帮助正在学习指针的小伙伴对指针有个大概的理解。感觉在数组与指针那块讲的还是很含糊,我将在后面的博客中再次详细讲解
当然我不是海王也不是舔狗