C语言进阶第三篇【指针进阶】

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 C语言进阶第三篇【指针进阶】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

前言:Hello!我是@每天都要敲代码。上一期我们一起学习了:字符指针的使用、指针数组和数组指针的理解、数组和指针的传参以及补充的一些练习题目<<传送门>>;还没有掌握的一定要去学习在看下面的知识点,因为指针的知识点都是连贯的!这一期我们将继续学指针剩余的内容,一起加油吧!

 

目录

1. 函数指针

1.1 函数指针的理解和写法

1.2 函数指针的调用

1.3 两段有趣的代码

1.3.1 代码1 (*(void (*)())0)()

1.3.2 代码2 void (*signal(int,void(*)(int)))(int)

2. 函数指针数组

2.1 函数指针的理解和写法

2.2 函数指针的实际应用

2.2.1 普通方法实现计算器

2.2.2 函数指针数组实现计算器

3. 指向函数指针数组的指针(了解)

3.1 指向函数指针数组的指针的理解和写法

4. 回调函数

4.1 回调函数的定义和理解

4.1.1 利用回调函数来实现计算器

4.2 利用回调函数模拟实现qsort函数

4.2.1 回顾冒泡排序

4.2.2 回顾快排qsort

4.2.3 qsort函数的模拟

1. 函数指针

1.1 函数指针的理解和写法

我们先根据前面所学梳理清楚函数指针是什么

int* p          整型指针—指向整型的指针—存放的是整型变量的地址
char* pc     字符型指针—指向字符型的指针—存放的是字符变量的地址
int(*p)[10]  数组指针—指向数组的指针—存放的是数组的地址
———-       函数指针—肯定就是指向函数的指针—存放的是函数的地址

我们推导出了函数指针的意义,那么怎么书写呢?

C语言进阶第三篇【指针进阶】

这里我只说两点:

1、我们发现数组指针函数指针书写形式很类似int (*parr)[10] = &arr

                                                                                     int (*pf)(int,int) = &ADD

2、我们打印&ADD和ADD的地址居然也是一样的,也类似于数组&arr和arr;我们来做一下比较:

(1)数组名 != &数组名;两者不等价,前者取出来的是首元素的地址;后者取出来的是整个数组的地址
(2)函数名 == &函数名;两者是等价的,因为对于函数没有什么首元素的地址这一说

注意:()的优先级也要高于*号的,所以必须加上()来保证pf先和*结合

练习:有了上面的理解,我们不妨就拿一个例题来来练练手,看你是否真正理解掌握了函数指针的写法,例如:void test(char* str),函数指针怎么书写呢?void (*pt)(char*) = &test

1.2 函数指针的调用

我们还是先从已学的知识入手,然后剖析函数指针的调用

C语言进阶第三篇【指针进阶】

这里解释一下两点:

1、int (*pf)(int, int) = &ADD   等价于     int (*pf)(int, int) = ADD

2、int sum = (*pf)(2, 3)           等价于    int sum = pf (2, 3);这里的解应用*没有实际意思,是摆设

1.3 两段有趣的代码

1.3.1 代码1 (*(void (*)())0)()

*( void (*)() )0 )() 我们先根据自己的想法通俗理解一下:将0强制类型转换为函数指针,然后解应用,在调用。

解析:

1、从数字0开始着手,想让数字0作为地址,肯定要是一种指针类型才可以;
2、把0强制类型转换为函数指针类型,0作为函数的地址;
3、0放到函数指针类型里是无参的,返回类型是void;
4、然后开始调用,我们解应用括号括起来,()代表我们调用它,参数什么都不传;
总结:

调用0地址处的函数;该函数无参,返回类型是void
1、void(*)()—函数指针类型
2、(void (*)())0—对0进行强制类型转换,被解释为一个函数地址
3、*(void (*)())0—对0地址进行解引用操作
4、(*(void (*)())0)()—调用0地址处的函数

1.3.2 代码2 void (*signal(int,void(*)(int)))(int)

void (* signal(int,void(*)(int)) )(int)我们还是要一步步拆分去理解

1、signal和()先结合,说明signal是函数名;
2、signal函数的第一个参数的类型是int第,二个参数的类型是函数指针;该函数指针,指向一个参数为int,返回类型为void的函数;
3、signal函数的返回类型也是一个函数指针;该函数指针,指向一个参数为int,返回类型为void的函数;所以,signal是一个函数的声明;
4、void (*)(int) signal(int,void(*)(int))我们也可以这样理解,signal函数的返回类型是一个函数指针;但是语法上不能这样写,只能把返回类型写中间;

简化:signal是一个参数函数指针,它的返回类型又是一个函数指针;那我们怎么优化呢?

利用typedef-对类型重定义:typedef void(*) (int) pfun_t;对void(*)(int)的函数指针类型进行重定义为pfun_t;但是这种语法是不支持的,我们要把pfun_t写到中间:

typedef void(*pfun_t) (int);重名之后上面代码就可以拆分成:

void (*)(int) signal(int,void(*)(int)) 和typedef void(*pfun_t) (int)等价于pun_t signal(int,punt)

是不是更加的容易理解了?

2. 函数指针数组

2.1 函数指针的理解和写法

我们先给出对于函数指针数组的理解:存放函数指针的数组;

我们在一起回顾一下整型指针数组:整型指针 int*,整型指针数组 int* arr[10];那么函数指针数组怎么定义和使用呢?我们通过一段代码的形式去理解。

C语言进阶第三篇【指针进阶】

2.2 函数指针的实际应用

那么函数指针在实际写代码中有什么应用呢?我们就通过一个典型的计算器来对比学习;比如:我们要实现加、减、乘、除。

2.2.1 普通方法实现计算器

C语言进阶第三篇【指针进阶】

我们从switch中就可以看出,代码很冗余。那我们思考一下

1、如果把 printf(“请输入两个操作数:>”);scanf_s(“%d %d”, &x, &y);这两句代码全部提炼出去放到switch最前。printf(“ret=%d\n”, ret);这句代码提炼出区放到后面不久可以避免代码冗余了?

2、但是这样会出现我们问题,当我们选择5错误或者选择0退出时,它还是要让我们输入两个操作数才能退出,这就很怪异;包括打印也是,我们只有正确调用了函数,才能打印;所以这种方法并不可取;怎么办呢?下面我们就用函数指针数组来优化这个代码!

2.2.2 函数指针数组实现计算器

C语言进阶第三篇【指针进阶】

我们利用函数指针数组来实现计算器,即解决了代码冗余的问题,而且还让代码更加的简练了,是不是很妙?这里函数指针数组parr相当于一个跳板的作用,我们经常把这样的数组叫做转移表! 

函数指针数组的用途:转移表

3. 指向函数指针数组的指针(了解)

3.1 指向函数指针数组的指针的理解和写法

指向函数指针数组的指针:是一个指针,指针指向一个数组 ,数组的元素都是函数指针 。

函数指针数组                                                 

取出函数指针数组的地址

整型数组

int arr[5];

int (*p1)[5] = &arr

整型指针数组

int* arr[5]

int* (*p2)[5] = &arr;p2是指向【整型指针数组】的指针

函数指针数组

int (*p)(int, int);函数指针

int  (*p2[4])(int, int);函数指针的数组

int* (* (*p3)[4])(int, int) = &p2;取出的是函数指针数组的地址

p3就是一个指向【函数指针的数组】的指针

这部分很难,也比较绕,我们根据下面这个例题进行了解就好:

C语言进阶第三篇【指针进阶】

补充:区分数组元素类型和数组的类型

例如:int arr[10];数组元素类型—int,arr数组类型是—int [10]

4. 回调函数

4.1 回调函数的定义和理解

回调函数:就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

理解: A函数—–》B函数(A函数的地址、指针);A函数不是直接调用,把A函数的地址传递给B函数;B函数通过A函数的指针反过来调用A函数。要借用参数为函数指针!

4.1.1 利用回调函数来实现计算器

我们前面已经有两种方法了实现计算器,现在我们继续用回调函数来优化计算器,简直妙不可言!

C语言进阶第三篇【指针进阶】

4.2 利用回调函数模拟实现qsort函数

在利用回调函数模拟实现qsort函数之前,我们先回顾一下以前学过的冒泡排序和qsort快速排序,对比一下两者的区别!

4.2.1 回顾冒泡排序

C语言进阶第三篇【指针进阶】

上面我们是按照升序进行排序;冒泡排序很好理解,所以代码也很容易实现;但是有一个弊端:只能排整形数据;所以这就限制了它的使用!

C语言进阶第三篇【指针进阶】

4.2.2 回顾快排qsort

对于库里面的qsort函数,它就比较通用;什么样的数据都能进行排序 !

1、排整形数据

C语言进阶第三篇【指针进阶】

C语言进阶第三篇【指针进阶】

2、排结构体数据

C语言进阶第三篇【指针进阶】

 C语言进阶第三篇【指针进阶】

4.2.3 qsort函数的模拟

我们在模拟实现前,不妨先看一下qsort函数的参数

void qsort(void* base,  base存放的是待排序数据中第一个对象的地址
                size_t num,  排序数据元素的个数
                size_t width, 排序数据中一个元素的大小,单位是字节
                int(* compare)(const void* e1, const void* e2) 函数指针—是用来比较待排序数据中的2个元素的函数。

对于qsort函数的模拟:我们可以在qsort使用的基础上进行修改,原来qsort是内部函数,拿来就能用,现在我们改成my_qsort去模拟实现my_qsort这个函数就可以!

最终:e1-e2就是升序排,e2-e1就是降序排!

C语言进阶第三篇【指针进阶】

代码的解析都已经写到注释里,如果还不明白的小伙伴,不如自己去调试运行一下,看看他们运行的逻辑。另外我们实际上知识利用my_qsort模拟实现了qsort函数,其它使用的方式还是和qsort函数一样的。对于排结构体数据也是没问题的,这就留给读者自己去尝试一下;相信你肯定有所收获! 

 总结:

    这一章节,我们把指针剩余的部分就讲完了。主要讲解了:函数指针、函数指针数组的使用;指向函数指针数组的指针的了解;以及利用普通法、函数指针数组法、回调函数法实现计算器;回调函数的理解;利用回调函数模拟实现qsort;内容虽然不多,但是满满的都是干活!希望大家可以慢慢吸收;下一篇我们就开始补充关于指针的例题,相信会让你很有收获!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/128529.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!