26.一节课讲明白c语言指针(中④)
hello,小伙伴们,大家好,这里是左左右,这期视频接着给大家讲C语言基础中指针这一部分,在函数那期视频我们写过这样一个Get_R的函数,前面我们讲过给数组名取地址得到的是数组指针,那么如果我们给函数名取地址是不是也可以得到函数的指针呢?通过代码来验证一下,点击运行,这时就将函数指针的地址打印出来了,
#include<stdio.h>
#include<string.h>
int Get_R(int D)
{
return D/2;
}
int main()
{
printf("%p\n",&Get_R);
return 0;
}
那么我们应该如何定义一个函数指针呢?也就是要用一个什么样的容器来存放函数指针呢?我们将之前定义好的函数复制下来,将函数名Get_R改为括号*p,将传参过来的变量名去掉,这里我们就将函数指针定义好了,这时的变量p就是指向函数Get_R的指针,同时函数名取地址和函数名是等效的,调试输出来看一下,是不是输出结果一模一样。
#include<stdio.h>
#include<string.h>
int Get_R(int D)
{
return D/2;
}
int main()
{
int (*p)(int) = &Get_R;
printf("%p\n%p\n%p\n",&Get_R,p,Get_R);
return 0;
}
那么函数指针该怎么使用呢?首先将函数指针解引用得到函数本身,接着将需要的参数传进来,打印输出是不是没有问题,其实指针函数前的*在c语言中是允许被省略的,打印输出是不是也没有问题?
#include<stdio.h>
#include<string.h>
int Get_R(int D)
{
return D/2;
}
int main()
{
int (*p)(int) = &Get_R;
int r1 = (*p)(10);
int r2 = p(10);
// printf("%p\n%p\n%p\n",&Get_R,p,Get_R);
printf("%d\n%d\n",r1,r2);
return 0;
}
按照这样的逻辑,给结构体名取地址是不是得到的就是结构体指针?我们通过代码来验证一下,首先我们定义一个结构体变量并通过大括号的形式给结构体成员赋值,接着打印输出,就将结构体指针的地址打印出来了。
#include<stdio.h>
#include<string.h>
typedef struct{
int id;
int age;
char *name;
}Student;
int main()
{
Student stu1 = {2025001,16,"高启强"};
printf("%p\n",&stu1);
return 0;
}
接着要用一个什么样的容器来存放结构体指针呢?它和int等基础数据类型指针的定义类似,前面放的是结构体名,那么如何通过结构体指针来给结构体成员赋值呢?这里我们要用结构体指针加减号,大于号,加结构体成员名的形式,减号和大于号组成一个箭头,代表指针指向结构体成员,这时打印输出看一下结果,是不是和我们想的一模一样。
#include<stdio.h>
#include<string.h>
typedef struct{
int id;
int age;
char *name;
}Student;
int main()
{
Student stu1 = {2025001,16,"高启强"};
Student *stup = &stu1;
printf("%d %d %s\n",stup->id,stup->age,stup->name);
printf("%d %d %s\n",stu1.id,stu1.age,stu1.name);
return 0;
}
通过这里我们可以得出这样一个结论,通过结构体名访问或修改结构体成员要用“.”的形式,而通过结构体指针访问或修改结构体成员要用“->”的形式,这里假设我们有这样一种需求,通过只调用一个函数实现打印输出“hello vscode”或“hello stm32cubeide”或“hello 左左右”,也就是说hello是不变的,后面的内容是可变的,按我们之前学过的内容,我们只能这样写,这时只能输出一种结果,也就是hello后面的内容是不受控的。
#include<stdio.h>
#include<string.h>
void Print(void)
{
printf("hello vscode");
}
int main()
{
Print();
return 0;
}
这时我们将前面的一个函数拆分为两个,那么我们该如何将这两个函数关联起来呢,我们刚才讲的函数指针就派上用场了,这时我们将Print_vs这个函数以函数指针的形式传参到Print_h中,再在Print_h中以函数指针的形式来调用Print_vs这个函数,接着我们在main函数中将Print_vs以函数指针的形式传参到Print_h中,点击运行,是不是输出没有问题?
#include<stdio.h>
#include<string.h>
void Print(void)
{
printf("hello vscode");
}
void Print_h(void (*p)(void))
{
printf("hello ");
(*p)();
}
void Print_vs(void)
{
printf("vscode");
}
int main()
{
// Print();
Print_h(&Print_vs);
return 0;
}
这段代码我们还可以简化一下,前面我们说过函数名和函数名取地址等效,我们将这里的取地址符可以去掉,我们可以用typedef,用一个简单的名称来替代这里的函数指针,也就是说这时的函数指针和CallbackFunc等效,接着我们将这里的函数指针改为CallbackFunc,同时前面我们说过函数指针前的*是可以省略掉的,所以这里我们可以写成这样,点击运行,是不是输出没有问题?
#include<stdio.h>
#include<string.h>
typedef void (*CallbackFun)(void);
void Print(void)
{
printf("hello vscode");
}
void Print_h(CallbackFun cb)
{
printf("hello ");
cb();
}
void Print_vs(void)
{
printf("vscode");
}
int main()
{
// Print();
Print_h(Print_vs);
return 0;
}
接着我们将剩下几个回调函数也照猫画虎的写一下,点击运行,这时我们是不是就可以随心所欲的输出我们想要的结果了?
#include<stdio.h>
#include<string.h>
typedef void (*CallbackFun)(void);
void Print(void)
{
printf("hello vscode");
}
void Print_h(CallbackFun cb)
{
printf("hello ");
cb();
}
void Print_vs(void)
{
printf("vscode");
}
void Print_stm(void)
{
printf("stm32cubeide");
}
void Print_zzy(void)
{
printf("左左右");
}
int main()
{
// Print();
Print_h(Print_vs);
Print_h(Print_stm);
Print_h(Print_zzy);
return 0;
}
那么回调函数有什么作用呢?如果是我们自己写的回调函数,是不是可以通过不同的回调函数,实现不同的功能,如果我们调用别人的回调函数我们不用考虑Print_h里这一部分有多么复杂,现实中hal库源码中这一部分写的也是十分复杂,我这样简单的写是为了大家能够理解回调函数的实现原理,我们在调用时,不用考虑这部分有多么复杂,我们只需要重写回调函数这一部分就可以了,这就是回调函数的现实意义,我们将之前stm3cubeide中写好的代码再拿出来看一下,当时我们使用这个函数HAL_UART_Receive_IT时,只需与知道他是usart中断接收数据的一个函数,具体他是怎么接收的过程我们不需要知道,我们只需要重写HAL_UART_RxCpltCallback这个回调函数来实现我们想要的功能就可以了。
好的,今天的视频就到这里了,您的关注,点赞和收藏是我持续更新下去的最大动力,我们下期见。