当前位置:网站首页>C语言指针的进阶(下)
C语言指针的进阶(下)
2022-07-01 08:30:00 【清炒莲藕拌饭】
博主: 清炒莲藕拌饭
博客:(10条消息) 清炒莲藕拌饭的博客_CSDN博客-C语言领域博主
专栏:(10条消息) C语言_清炒莲藕拌饭的博客-CSDN博客
专栏介绍:包含从C语言初阶到进阶各各知识点进行讲解,用C语言写的各种小程序,大体围绕C基础为主的专栏;
本篇博客重点:
1.函数指针
2.函数指针数组
3.指向函数指针数组的指针
4.回调函数
1.函数指针
函数指针介绍
首先我们先看一段代码:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出结果(%p是用来打印地址的):
也就是说不管是直接打印还是&打印test函数,它都是相同的地址,居然有地址那肯定就能使用指针指向这个函数,我们称为函数指针;
函数指针的类型定义:
前面我们也了解过来各种样子的指针,例如 int *pa,int (*pb)[5] 等等;
它们的类型除去指针变量名外剩下的就是它们的类型,pa的类型就是int*,pb的类型就是int(*)[5],函数指针也不例外:
int add(int a, int b)
{
return a + b;
}
int main()
{
int a = 0;
int b = 0;
int ret=add(a, b);
return 0;
}
add函数指针:int (*ph)(int,int)= add(&可有可无);
1.(*ph)就说明ph是一个指针
2.(int,int)说明这个指针指向的是一个函数,这个函数有两个参数,类型都是int
3. int说明这个函数的返回值是int类型
int add(int a, int b)
{
return a + b;
}
int main()
{
int a = 5;
int b = 3;
int (*ph)(int, int) = add;
int ret = ph(a, b);//*可以写也可不写,但是要写就一定要加上()
printf("%d", ret); //通过ph我们同样能调用add函数,就像给add函数取了一个小名
return 0;
}
上面函数指针的基本定义我们了解了下面我们来看两段来自《c陷进和缺陷》这本书的代码:
//代码1 ( * ( void ( * ) ( ) ) 0 ) ( );
//代码2 void ( * signal (int , void ( * ) ( int ) ) ) ( int );
代码1解析:
void (*)()我们知道是一个函数指针类型,(void (*)())0是把0强制类型转换成void(*)()这样的指针类型,然后加上 ( * ( void ( * ) ( ) ) 0 ) ( )就是将0解引用然后调用,这个代码本质上其实就是一次函数调用,调用的是0作为地址处的函数;
代码2解析:
首先signal和前面的*没有一起()起来,那么signal肯定就是和后面的()结合,那么sighal一定是一个函数名了,signal(int,void(*)(int))就代表signal这个函数有两个参数,一个是int类型,一个是void(*)(int)函数指针类型,那么剩下的void(*)(int)是什么?
是signal函数的返回类型,它的返回类型也是一个函数指针类型,经过分析这个代码本质上其实就是一次signal函数的声明;
小知识:上面的类型都太过复杂,我们是不是可以用typedef来类型重定义呢?答案是肯定的!
typedef void ( * pfun_t ) ( int );//注意typedef定义函数指针类型需要在(*)里面定义
pfun_t signal ( int , pfun_t );
函数指针的用途:
初学者都会有一个疑惑:为什么要大费周折搞一个函数指针呢?我要调用函数直接用函数名不就行了吗?就像上面的add我就直接用不就行了,为什么要用函数指针ph呢?
按照上面的代码确实不需要函数指针,但只是上面代码过于简单了,对于初学者很少接触到一些高级一点的代码,所以对于函数指针不是很认同,我可以很明确的说函数指针绝可以算得上C语言里面的高级语法了,而且在复杂的代码中经常会用到!
举一个比较简单的栗子,我们要写一个实现计算器的程序(整数加减乘除):
void menu()
{
printf("*****************************\n");
printf("**** 1. add 2. sub *****\n");
printf("**** 3. mul 4. div *****\n");
printf("**** 0. exit *****\n");
printf("*****************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
写这样的代码相信大家已经是轻车熟路了,但我们发现这样的代码是不是过于冗余了,switch语句下的case语句项代码重复度过高,那我们是不是就可以把它单独拿出来封装成一个函数:
主语句部分:
void menu()
{
printf("*****************************\n");
printf("**** 1. add 2. sub *****\n");
printf("**** 3. mul 4. div *****\n");
printf("**** 0. exit *****\n");
printf("*****************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
我们实现一个calc函数,需要进行什么样的计算就把对应的计算函数传过来;
calc函数:
void calc(int (*pf)(int , int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
利用函数指针整段代码是不是简化了许多,而且这还不是最简化的方式,利用数组我们能使代码更加简单(函数指针数组)!
2.函数指针数组
函数指针数组是一个数组,它的所有元素类型都是函数指针类型;
根据我们上面写的计算器的代码,既然函数指针是一种类型,而且上面的加减乘除的函数类型都是一样的,那我们就可以把它们放在一个数组里面,需要哪个函数直接通过下标调用就行了;
void menu()
{
printf("*****************************\n");
printf("**** 1. add 2. sub *****\n");
printf("**** 3. mul 4. div *****\n");
printf("**** 0. exit *****\n");
printf("*****************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
//函数指针的数组
//转移表
int (*pfArr[])(int, int) = {0, Add, Sub, Mul, Div};//[]代表pfArr指向的是一个数组
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
这样写也方便我们下次添加其他的功能,直接在数组的元素初始化地方加上就可以了,下面的代码只需要微调便可;
3.指向函数指针数组的指针
指向函数指针数组的指针 是一个 指针, 指针指向一个 数组 ,数组的元素都是 函数指针 ;
定义方法:
void test(const char* str) { printf("%s\n", str); } int main() { //函数指针pfun void (*pfun)(const char*) = test; //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; return 0; }
这里只是粗略的给大家介绍一下,毕竟这里是可以一直套娃下去的,而且这样的代码也是比较少见的,能够认识这样的代码就足够了;
4.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。
qsort函数:
在C语言的库函数里面有一个qosrt函数,这个函数能够实现各种类型数据的排序,我们平常写的排序方法都是类型写死的,int类型就只能排序int类型,浮点型就只能排序浮点型;
qsort函数一共有4个参数,第一个参数是需要排序数据的起始地址,第二个参数是需要排序的元素个数,第三个参数是每个元素的大小(字节),第四个参数是一个函数指针,这个函数需要使用者自己写一个比较函数;
#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
使用回调函数模拟实现一个qsort函数:
#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);
}
void _swap(void *p1, void * p2, int size)
{
int i = 0;
for (i = 0; i< size; i++)
{
char tmp = *((char *)p1 + i);
*(( char *)p1 + i) = *((char *) p2 + i);
*(( char *)p2 + i) = tmp;
}
}
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{
int i = 0;
int j = 0;
for (i = 0; i< count - 1; i++)
{
for (j = 0; j<count-i-1; j++)
{
if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
{
_swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
边栏推荐
- [Yu Yue education] Shandong Vocational College talking about railway reference materials
- Internet of things technology is widely used to promote intelligent water automation management
- [dynamic planning] p1020 missile interception (variant of the longest increasing subsequence)
- 挖财打新股安全吗
- Learn reptiles for a month and earn 6000 a month? Tell you the truth about the reptile, netizen: I wish I had known it earlier
- 分享2022上半年我读过的7本书
- [untitled]
- Maneuvering target tracking -- current statistical model (CS model) extended Kalman filter / unscented Kalman filter matlab implementation
- 明明设计的是高带宽,差点加工成开路?
- MATLAB【函数求导】
猜你喜欢
毕业论文中word的使用1-代码域标公式
Intelligent constant pressure irrigation system
3、Modbus通讯协议详解
The era of low threshold programmers is gone forever behind the sharp increase in the number of school recruitment for Internet companies
Suivi des cibles de manoeuvre - - mise en oeuvre du modèle statistique actuel (modèle CS) filtre Kalman étendu / filtre Kalman sans trace par MATLAB
What is the material of 15CrMoR, mechanical properties and chemical analysis of 15CrMoR
DID的使用指南,原理
Li Kou 1358 -- number of substrings containing all three characters (double pointer)
《微机原理》——微处理器内部及外部结构
一套十万级TPS的IM综合消息系统的架构实践与思考
随机推荐
How can beginners correctly understand Google's official suggested architectural principles (questions?)
Provincial election + noi part I dynamic planning DP
Anddroid text to speech TTS implementation
【无标题】
Leetcode T29: 两数相除
Embedded-c language-10-enumeration / (function) pointer (function) / multi-level pointer /malloc dynamic allocation / file operation
CPU design practice - Chapter 4 practical tasks - simple CPU reference design and debugging
分享2022上半年我读过的7本书
机动目标跟踪——当前统计模型(CS模型)扩展卡尔曼滤波/无迹卡尔曼滤波 matlab实现
The data analyst will be ruined without project experience. These 8 project resources will not be taken away
栈实现计算器
View drawing process analysis
Data analysis notes 11
Qt的模型与视图
【C】 Summary of wrong questions in winter vacation
MATLAB小技巧(16)矩阵特征向量特征值求解一致性验证--层次分析
Leetcode t31: next spread
MAVROS发送自定义话题消息给PX4
Mavros sends a custom topic message to Px4
The era of low threshold programmers is gone forever behind the sharp increase in the number of school recruitment for Internet companies