你以为指针真的像你以为的那样简单吗?

  • 1.字符指针
  • 2.指针数组
  • 3.数组指针
    • 3.1数组指针的定义
    • 3.2再盘&数组名and数组名
    • 3.3数组指针的使用
  • 4.数组传参、指针传参
    • 4.1一维数组传参
    • 4.2二维数组传参
    • 4.3一维指针传参
    • 4.4二维指针传参
  • 5.函数指针
  • 6.函数指针数组
  • 7.指向函数指针数组的指针

哇塞,我们可以看到这一篇博客的目录就好长好长了,本来呢阿涛也想的是把这一篇大博客分成三段,再来上个上中下,但是我转念一想,我好像从来没有尝试过更新一篇超长博客,全是中不溜的长度,那么今天,我想挑战一下我的软肋,希望兄弟们吃水不要忘记挖井人,也希望我这一篇博客可以破百赞,然后我就成为大博主啦!!!

1.字符指针

我们之前给兄弟们讲过的类型里面就有一个字符类型:char。

想要打印出字符就要使用 %c;同时我们之前也讲过了指针的用法:创建指针变量就是类型+ *就构成了指针类型:我们这里的 pc的类型就是char*,想要打印出地址就要使用%p。
注意看这里的‘a’,想必兄弟们一定都注意到了这里的a使用单引号括起来的,不要问为什么,这就是语法规定,字符要用‘ ’括起来,但是有另一个问题兄弟们是可以问的,也是我必须要讲的:我们都知道C语言是有字符串的,那请问C语言中有没有字符串类型啊?
答案是:没有没有坚决没有!!!!

 char ch[] = "asdfgf";

如果你想要把字符串储存起来就必须要创建一个数组,那兄弟们就要犯嘀咕了,为什么非要创建一个数组呢,原因很简单啊,刚才我还特地强调一下,在C语言中是没有字符串类型的,所以我们只能把字符串当中每一个元素当作一个字符存入数组当中,为了服众,阿涛这里给兄弟们演示一下:

这就是没有创建数组的写法,想要打印字符人家直接把一个大大的?甩到你脸上!
某些兄弟:那有没有可能这里是要用%s才能打印出来呀?

有图为证,首先我描述一下编译器的反应吧:这么先进的一台计算机,面对我兄弟写出来的代码都要先停顿两秒,闪了两下,然后表示实在是无能为力,实在猜不到坐在它屏幕前的程序员想要表达什么,然后这个程序就崩溃了!
但是我们从下面的错误信息中是不是也可以得到一些启发呢?
这里显示“ printf的调用必须是字符串地址”结合我们之前所用的案例,我们不难猜出,我们想要编译器给我们打印出字符串,必须给他我们字符串的首字符地址,然后可能printf函数的机制就是一直打印知道找到\0,那我们是不是可以给他一个地址呢?

首先我们可以看到,我们圆满完成了任务,成功打印出了预期的字符串,那是不是我们给自己的猜想发了银水?接着我们再看,我还打印了一个地址以及一个字符。这是用意何在啊?
我们之前说的,我们创建的类型是char *类型,但是我们要存放的确实一串字符串啊,难道我们是把这些字符的所有地址都存到了ch里面吗?显然不是的,程序已经告诉了你答案,我们只存了一份地址,至于是谁的地址呢?我们使用解引用操作,找出了金主‘a’!
那么我们就知道了,在程序中不创建数组就存放字符串,存放的就是字符串首字符的地址。我们再来看一题,加深一下印象:

 char a[] = "asdfg";char b[] = "asdfg";char* c = "asdfg";char* d = "asdfg";if (a == b){printf("a==b\n");}else{printf("a!=b\n");}if (c == d){printf("c==d\n");}else{printf("c!=d\n");}

请问,结果会输出什么?
这就是结果,这就是结局!
我们来分析一下吧,我们先是创建了两个数组来接收字符串,之前我们讲某块内存知识的时候是不是说过,只要是在程序里创建了变量,数组或者函数,程序都会申请一块空间供他们使用?(函数只有在调用的时候才会开辟空间),那既然只要创建了数组我们就要申请内存空间的话,我们这边创建了两个数组,是不是内存就要申请两块不同的空间来给我们的数组使用?也就是说不管字符串的内容相同与否,我们都会申请两块不同的空间,那数组名就是首元素的地址,这点相信兄弟们已经是根深蒂固了,都是两块不同的内存空间了,你让他们的首元素地址怎么可能相同呢?

 char* c = "asdfg";char* d = "asdfg";

而这两行代码又有不同之处,我们说过了这边是没有创建数组就直接想存放字符串的,那么我们存放的也就是首元素的地址,也就是‘a’的地址,那我请问兄弟们,在计算机里面有多少a啊?是不是有且仅有这一个a?不光是a,我们每一个字符在内存中都是只有一个他自己的地址,放在可读区里面,不可更改只能读取使用!
话说回来,既然只有一个字符,是不是a就没有必要拥有多份地址啊?
所以我们说的c和d是相等的,存放的就是字符a的地址!

2.指针数组

 int a[] = { 1,2,3,4 };int* b[] = {1,2,3,4,5,6};for (int i = 0;i < 6;i++){printf("%p\n",b[i]);}

存放整型的数组叫做整形数组,存放指针的数组就叫做指针数组喽,之前我们还是说过,指针数组重点在于数组!
我们正好来看一下,b【】数组里面存放的是什么:

看看看看,是不是每一个字符在内存中都有自己特别的地址?(这里后面我又往里面放了字母字符,效果也是一样的!)

3.数组指针

指针数组,表示的存放指针的数组,那指针不高兴了呀,怎么我凭什么只能当修饰词,我也要做名词,我也要被人家追捧!
那么兄弟们要不要猜一猜数组指针是什么意思?
我相信大家一定都是脱口而出:数组指针啊,是指针,它存放的就是数组的地址呗!
恭喜你们,你们的悟性强过我当年!

3.1数组指针的定义

 int* p[1];

通过这个式子,我们应该就能感受得到【】的优先级是在*之上的,不然的话这个就不叫做指针数组了,那我们能不能够想一个办法,让*的优先级高起来呢?
有的!

 int* p[1];int(*pp)【1】 = &p;

是不是我们在这里加一个(),*的优先级就瞬间高了许多,就瞬间翻身做主人了?
但是我们上面举得这个例子不太应景,阿涛来换一个例子,方便我们的讲解哈!

 int a[5] = { 1,2,3,4,5 };int(*pa)[5] = &a;

我们来一步一步看哈,首先这是一个赋值,把右边的赋值给左边,右边是&a,我们取出了a数组的地址,这跟我们之前说的数组名就是首元素地址就不是一个道理了,就真的是数组的地址了!然后根据优先级,我们可以发现pa首先跟*结合,表示pa就是一个指针变量了!!!然后我们把*拿掉,留下的就是int [5],【5】这个数组有五个元素,int表示这五个元素就是int类型。

3.2再盘&数组名and数组名

呀,又到了讲解数组名和&数组名的环节了,我没有记错的话,这应该已经是我第三次给兄弟们讲解这部分知识了,不过没关系,我在学习的时候我的老师也一直在跟我反复讲这些,虽然我一般都不太想听,但是人家还是一直在讲。

好了我们来看一下,我们一直在讲数组名就是首元素地址,但是有两个例外我们要记住:
1.sizeof(数组名)计算的是一整个数组所占的内存大小!
2.&数组名取出的是整个数组的地址!
通过上面的这一张图,兄弟们是不是感觉阿涛有些地方讲错了?不是说好了a和&a只有上面两种情况是特例吗,怎么到了我这里a和&a打印出来相同呢?阿涛,你是不是怕教会徒弟饿死师傅?是不是藏私了?兄弟们要相信我,我真的没有藏东西啊!
我举个例子吧,一个小康家庭住在601,然后他们下面住着一家超级富豪,从地下车库到501都是他家的,那这个富豪在跟别人介绍自己的住处的时候会不会一口气全给你报菜名一样报出来?那肯定是报最底一层的号码啊!
这不是一个道理吗,难道非要程序把你这个数组里面每一个元素的地址都给你打印出来你才满意?
虽然我们看到了a和&a好像打印出来是一样的值,但这并不代表两者是完全等价的,为了证明这一点,我还特地让他们分别加一给你们看,事实就是这个世界从来没有让有心人太过失望,我们可以明显感觉到两者的步长差的不是一点点,这一点在后面的应用中尤其重要!!

3.3数组指针的使用

话不多说,直接上代码理解!

 void print(int(*p)[4], int r, int c)
{for (int i = 0;i < r;i++){for (int j = 0;j < c;j++){printf("%d ",*(*(p+i)+j));}printf("\n");}
}int main(){int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };print(a, 3, 4);return 0;}

按照我们之前的逻辑,数组名是首元素的地址,对于一维数组首元素很好理解,就是第一个嘛,那对于二维数组呢?

这是十六进制的形式,那么首元素与第二个元素之间是差了16字节,正好就是一行的内存大小,包括可以看到就连接收地址我们都是用的是数组指针:

在种种证据面前,我们能够很自然地得出结论:对于二维数组来说,数组名还是首元素地址,而首元素地址就是第一行的地址,所以我们用二维数组数组名传参的时候要用数组指针来接收,是不是这样一捋就都豁然开朗啦?

4.数组传参、指针传参

4.1一维数组传参

int main()
{int a[] = { 1,2,3,4,5 };test1(a);test2(a[5]);return 0;
}

这就是我们之前说的一维数组传参,就形式上来看,我们可以选择传数组名,也可以选择把整个数组传过去,但在这里阿涛可以给你们剧透一下,其实两者是没有区别的:

void test1(int a[2])
{}
void test2(int* a)
{}

我们之前说过的,数组也属于自定义类型,因为数组类型以及元素个数都是需要自己手动输入的,那么在这里我们如果传的是整个数组a【5】的话,程序是不是至少也应该创建一个有五个元素的数组来接收我们的传参呢?但我们可以看到的是test1中,我们只是创建了一个有着两个元素的数组,但是在编译器里面并没有报错,这是为什么呢?
原因很简单:我们数组传参传的是首元素地址,所以从某种意义上来说你创建的数组的【】里面无论填什么值都只是起到了一个装饰的作用,根本不会影响实际上的传参,话说回来虽然本质上数组传参传的是首元素地址,但是就从理解上面来看,数组传参也用数组接收是不是更好理解一点?

4.2二维数组传参

讲完了一维数组传参,我们再来讲一讲二维数组传参吧!

void test3(int a[3][2])
{}int main()
{int a[3][2] = { 1,2,3,4,5,6 };test3(a);//test4(a);return 0;

首先还和一维数组传参一样,我们传的是二维数组首元素地址,一维数组传参我们用一维数组接收是没有问题的,同样对于二维数组我们也可以用二维数组来接收,只是要注意的是二维数组的列数要与原数组保持一致:

我们看到哪怕是行数不填写都是无伤大雅的,你哪怕就是乱填一个都不碍事!但是列数可不兴乱填!
看完了现象,我们再来看一看本质:

void test4(int(*a)[2])
{for (int i = 0;i < 3;i++){for (int j = 0;j < 2;j++){printf("%d ",*(*(a+i)+j));}printf("\n");}
}
int main()
{int a[3][2] = { 1,2,3,4,5,6 };//test3(a);test4(a);return 0;
}

在这段代码里面比较有意思的有两点:
1.

void test4(int(*a)[2])

这是什么?注意看,我们用的是什么类型来接收二维数组传参的?int (*)【】,这不就是数组指针的类型吗?把变量去掉就是我们的类型!
2.

printf("%d ",*(*(a+i)+j));

*(*(a+i)+j)这里理解出来是不是有一点点难度?我们来细细品味一下哈!大家现在应该已经认同二维数组数组名就是第一行的地址了吧!
那么 a+i 是不是就是第i行的地址?
那么*(a+i) 是不是就是第i行?也就是第i行的数组名,也就是第i行的首元素地址?
那么*(a+i)+j 是不是就是第i行第j个元素的地址?
那么*(*(a+i)+j)是不是就是第i行第j列的元素?
反正我现在的自我感觉是比较好的,我认为我应该把这块讲的够清楚了,希望大家能够理解!
好的那么实践是检验真理的唯一标准,我们来看看我们的分析对不对吧!

但从效果上来看,完全正确!!!!!

4.3一维指针传参

数组传参我们说清楚了,下面我们来说一说指针传参!
指针传参就是把指针作为参数传递过去!
这里怕大家还是模棱两可,再给大家演示一下!

void print(int* p)
{printf("%d\n",*p);
}
int main()
{int a = 0;int* pa = &a;print(pa);return 0;
}

就是如此简单,如此朴实无华!
那我们可以适当拓宽一下思想,什么情况下我们可以用一级指针来接收传参呢?
我觉得至少有两个是水到渠成的答案:
第一个就是我们传的是一个指针,那是不是就应该用指针来接收呢?
第二个就是一维数组数组名,一维数组数组名就是首元素地址,我们应该已经是理解的明明白白了!

4.4二维指针传参

直接上正菜吧,键盘都冒烟了……

void print(int** p)
{printf("%p\n",*p);
}
int main()
{int a = 0;int* pa = &a;int** ppa = &pa;print(ppa);return 0;
}

显示出来的结果呢就是pa了也就是a的地址了。
那请问,我们用什么东西传参需要使用二级指针接收呢?
一.传一个二级指针就用一个二级指针接收。
二.传一个指针数组的数组名,兄弟们想一想本身指针数组里面每一个元素是不是就是一个地址?那我们又知道数组名就是首元素地址也就是地址的地址,这可不就用二级指针嘛?

5.函数指针

有了葫芦画一个瓢很难吗?
阿涛之前给兄弟们讲过一个函数要有函数返回类型,要有参数,那我们把一个函数的地址存放到一个指针里面,怎么可能少了返回类型以及参数呢?

 int a = 0;int* pa = &a;int** ppa = &pa;print(ppa);void (*p)(int**) = &print;

这里我们就是取出了函数printf的地址并且把它放到了p里面,我们可以看到p首先和*结合,代表了p就是一个指针,类型是void(*)(int**)的,是不是把函数返回类型以及参数的类型全部表现了出来?
值得一提的是,在数组里面,数组名还有&数组名是由显著去别的,但是在函数这块,数组名和&数组名的意义是一样的!

6.函数指针数组

好了,基础知识讲完了,下面的两类就是套娃模式了。
函数指针数组,本质上还是一个数组,只是数组里面每一个元素的类型都是函数指针数组,那我们应当如何刻画?

 void(* a[5])(int);

我感觉这样子就不错,首先变量和【】结合,说明这是一个数组,然后我们去掉数组看剩下的就是元素的类型,剩下的就是void*(* )(int)说明这是一个函数指针类型,函数的参数类型是int,函数的返回类型是void*.

7.指向函数指针数组的指针

下面就是终极套娃模式了,指向函数指针数组的函数……有的程序员也不知道一天天瞎琢磨什么东西,尽搞这些花里胡哨的去了。其实在某些特定的场景下面了,这些东西的作用不小,但是那也要等到后面一点我才好跟大家讲讲!
好了,这是一个指针,指向的是数组,数组中每个元素的类型是函数指针,我们这样子慢慢拆分来看,是不是感觉每一块都是我们学过的知识?怎么组合起来你就迷糊呢?
我们来看看吧!

void\*(*(*p)【5】)(int *,int (\*) (float));

就这么一行代码,老手来看一不留神都要在阴沟里面翻船!
首先变量p先和*结合说明这是一个指针类型,指向的是一个有着五个元素的数组【5】,数组中每个元素的类型又是函数指针void(*)( 参数类型 ),返回类型是void*参数有两个,第一个参数类型是int*,第二个参数的类型又是一个函数指针,就这么套娃套娃,着实没有必要为了套娃而套娃,话虽然这么说,但是我还是的任劳任怨地教会你们啊!!!!!!

欢乐时光总是这么短暂,阿涛花了相当长的一段时间为兄弟们连夜高出这一篇博客,绝对是一个程序员呕心沥血之作,希望兄弟们看完后能够有所收获!我相信只要能看一看我的博客一定会有的!!
那么还是那句话:
百年大道,你我共勉!!!!

指针升级版!!!我在这里传授艰深道法,诸位道友可愿与我一同修行?相关推荐

  1. 如果编程语言是女孩子

    试想一下,当Java.C++.Python.Ruby.PHP.C#.JS等编程语言变成了动漫人物会是怎样的一幅场景呢?下面就一起看看在日本作家渡辺将人的笔下,各种编程语言都是哪类可爱的女孩子的吧! 究 ...

  2. 当Java、C++、Python等编程语言都变成妹子。。。

    试想一下,当Java.C++.Python.Ruby.PHP.C#.JS等编程语言变成了动漫人物会是怎样的一幅场景呢?下面就一起看看在日本作家渡辺将人的笔下,各种编程语言都是哪类"美女&qu ...

  3. 水滴石穿C语言之编译器引出的问题

    基本解释 本节主要探讨C编译器下面两方面的特点所引发的一系列常见的编程问题. 对C文件进行分别编译: C程序通常由几个小程序(.c文件)组成,编译器将这几个小程序分别编译,然后通过链接程序将它们组合在 ...

  4. 如果编程语言是女孩,你最喜欢哪一个?

    试想一下,当Java.Python.JS.C++.C.C#.Shell等编程语言变成了动漫人物会是怎样的一幅场景呢?下面就一起看看在日本作家渡辺将人的笔下,各种编程语言都是哪类可爱的女孩子的吧!那你又 ...

  5. LwIP应用笔记(二):无操作系统支持下的RAW API移植

    欢迎来我的个人博客转转:https://www.codinglover.top 写在前面 由于从这篇博客开始要涉及代码编写了,为此笔者自行画板搭建了一个实验平台,以后的所有代码与步骤都会在此实验平台上 ...

  6. 娱乐弹弹弹——程序猿眼中的女人

    程序猿,整天跟操作系统,编程语言,各种打交道,那么程序猿眼中的女人是什么样子的呢? 有程序猿曾经用操作系统形容过各种类型的女人,有程序猿用编程语言描述各种星座的女人. 小编找到了几个版本,请看: 编程 ...

  7. OSChina 娱乐弹弹弹——程序猿眼中的女人

    2019独角兽企业重金招聘Python工程师标准>>> 程序猿,整天跟操作系统,编程语言,各种打交道,那么程序猿眼中的女人是什么样子的呢? 有程序猿曾经用操作系统形容过各种类型的女人 ...

  8. 当编程语言都变成女孩子,你会不会喜欢她们!

    ▼Java 犹如宫泽贤治的<不畏风雨>中出现的.性格木讷的女孩子.从小就由于迟钝和大食量等特征被别人当作笨蛋,从小学入学开始进入田径部.坚持跑步,在中长跑中经常取得好成绩,给人以活泼的印象 ...

  9. 当Java、C++、Python等编程语言都变成软妹子

    试想一下,当Java.C++.Python.Ruby.PHP.C#.JS等编程语言变成了动漫人物会是怎样的一幅场景呢?下面就一起看看在日本作家渡辺将人的笔下,各种编程语言都是哪类"美女&qu ...

最新文章

  1. php输出带的字符串吗,php输出含有“#”字符串的方法
  2. php barcode_php生成条形码
  3. 三相滤波器怎么接线_您知道家用电表如何接线吗?小编来告诉你!
  4. TimerHandler的简单应用
  5. java 注解 payload_spring – 如何使用注释配置PayloadValidatingInterceptor
  6. 软件測试基本方法(一)之软件測试
  7. 05-配置数据库的远程连接 创建hive数据库的时候要选择latin1
  8. linux location root访问文件夹404_如何使网站支持https访问?nginx配置https证书
  9. java泛型程序设计——翻译泛型表达式+翻译泛型方法
  10. javascript 点点滴滴01章 javascript的认知
  11. 化工图纸中LISP_必备干货丨石油化工安装工程质量与成本控制研究
  12. 转载:Pixhawk源码笔记七:姿态控制预览
  13. HDUOJ---老人是真饿了
  14. atitit.避免NullPointerException 总结and 最佳实践 o99
  15. CentOS 7 下挂载新硬盘
  16. 安装应用需要打开未知来源权限_华为盒子安装不了第三方软件?不存在的,简单几步即可搞定...
  17. 非参数统计的Python实现—— Wilcoxon 符号秩检验
  18. 基于pytorch实现线性回归
  19. Windows 11 新功能 Microsoft Teams
  20. 操作系统之进程管理习题

热门文章

  1. 多功能交通一体式综合杆:合杆整治工程是什么意思?
  2. 结构光3D之DLP4500的使用
  3. cvcvtcolor_opencv中函数的一相关说明如:cvtcolor和cvcvtcolor区别
  4. OpenMV使用技巧
  5. 搭建LNMP+DISCUZ论坛
  6. 一个老旧系统的现代化改造
  7. 跟涛哥一起学嵌入式 第07集:GNU/Linux和Linux的区别
  8. java实用程序_java面向对象设计之实用程序类
  9. MySQL高可用之InnoDB Cluster
  10. 为什么PHP项目运行报错502,PHP的502报错