C/C++函数指针以及如何解决C++泛型编程中的多样化参数问题

发布时间:2024年01月22日

前言:笔者最近需要在ubuntu平台上写一个类似于TimeSetEvent的计时器,实现按一定频率重复执行回调函数的功能。这就需要运用C/C++函数指针的性质。由于笔者希望计时器预留给回调函数的接口可以适配任何函数,所以需要使用C++泛型编程的性质并解决回调函数参数多样化的问题。这篇文章是笔者解决问题的学习笔记和记录,欢迎交流和指正。

一、函数的地址

函数是有地址的,函数的地址是存储其机器语言代码的内存的开始地址。

#include <stdio.h>

int func(int a,int b)
{
	return a+b;
}

int main(void)
{
	printf("%p",func);
} 

读者可以自行编译上面这一段程序,发现程序可以输出结果。即函数编译后机器语言代码在内存中的起始位置。

二、函数指针——指向函数的指针

int型变量存在地址,所以有int*类型的指针,指向int型变量,储存int型变量的地址;函数也存在地址,所以也应该存在特定类型的指针,指向函数,储存函数的地址。这便是函数指针

函数指针存在很多类型。通过其指向函数的返回值和参数列表来决定其类型。例如,上面的代码段的func函数,其返回值类型为int,参数列表为(int , int)。那么,指向func函数的指针的类型被定义为

int (*)(int,int)

具体的,读者可以按照如下方式定义一个指针型变量p1,用于存储func函数的地址:

int (*p1)(int,int);

注意,p1作为指针的名称,没有出现在最后,而是出现在括号内。

因此,下面这一段代码是可以通过编译的:

int func(int a,int b)
{
	return a+b;
}

int main(void)
{
	int (*p1)(int,int)=func;
} 

以此类推,返回值为void,参数为void的函数指针类型为

void (*)(void)

返回值为float,参数为两个float类型数组的指针类型为

float (*)(float*,float*)

三、利用指针来调用函数

如上,p1是函数func的指针,那么通过以下方法可以调用func函数:

int c=(*p1)(3,5);

考虑到语言设计的包容性,事实上,对p1指针解引用的过程可以被省略,也就是说,下面这个操作也是允许的:

int d=p1(3,5);

?具体示例如下:

#include <iostream> 

int func(int a,int b)
{
	return a+b;
}

int main(void)
{
	int (*p1)(int,int)=func;
	int c=(*p1)(3,5);
	std::cout<<c<<std::endl;
	int d=(p1)(3,5);
	std::cout<<d;
	return 0;
} 

输出结果:

8

8

?

四、函数类型和函数指针类型

按照C/C++规范,其实函数也是有类型的。就比如我们的func函数,其类型为:

int(int,int)

而p1作为指向它的指针,也具有类型:

int (*)(int,int)

两者便有了对应关系。

我们在声明一个函数的时候,就好像声明了一个变量。如下,声明func函数:

int func(int,int);
#include <iostream> 

int func(int,int);

int main(void)
{
	int (*p1)(int,int)=func;
	int c=(*p1)(3,5);
	return 0;
} 

int func(int a,int b)
{
	return a+b;
}

声明了func,就是告诉编译器程序里有一个int(int,int)类型的函数。我们之前觉得古怪的函数声明,是不是逐渐变得合理而自然而然起来了?

五、泛型编程中函数指针的应用

函数指针这一性质的价值可以体现在泛型编程中。这里笔者使用一个简单的例子进行说明。比如说,我要实现一个最简单的定时器,让程序能够在启动定时器的5秒后执行某个回调函数(回调函数,一般指通过函数指针执行的函数)

#include <iostream>
#include <ctime>
#include <thread>

void printnum(int num)
{
	std::cout<<num<<std::endl;
}

//计时器函数使用泛型编程,FuncPtr为函数指针,FuncPam为函数参数 
template <typename FuncPtr,typename FuncPam>
void timer(int period,FuncPtr funcptr,FuncPam funcpam)
{
	clock_t time1=clock();
	clock_t time2;
	clock_t delta;	
	
	while(true)
	{
		time2=clock();
		delta=time2-time1;
		if (delta>=period)
		{
			//通过函数指针调用函数 ,参数为funcpam 
			(*funcptr)(funcpam);
			break;
		}
	}
}

int main(void)
{
	//创建计时器线程,设定为5秒后执行回调函数printnum 
	std::thread timer1(timer<void(*)(int),int>,5000,printnum,2024);
	timer1.join();	
}

通过函数指针和泛型编程,timer定时器可以被设定为任意后执行任意参数数量为1、任意返回类型的回调函数。试想,没有函数指针,完全无法达到这个目的!

六、如何实现多样化参数

想必读者一定注意到,我上面写的计时器所能执行的回调函数类型还是有限的。如果存在一个回调函数,它没有参数,或是它有多个参数,那么就无法用这个计时器触发了!

十几年前,制定C++语法规则的大佬们解决了这个问题。

在C++11标准中,引入了“可变参数模板”,它表示可以接受任意数量的模板参数,并将它们打包为一个模板参数包。在函数模板中,我们可以使用 typename...?来定义一个模板参数包。例如:

template <typename... Args>
void my_function(Args... args)
{
    // ...
}

使用该模板时,也要标明...

根据这个性质,可以改进我们的计时器:

#include <iostream>
#include <ctime>
#include <thread>

void printnum()
{
	std::cout<<2024<<std::endl;
}

//计时器函数使用泛型编程,FuncPtr为函数指针,FuncPam为函数参数列表 
template <typename FuncPtr,typename... FuncPam>
//模板处typename后要加上... 
//使用泛型FuncPam时要加上... 
void timer(int period,FuncPtr funcptr,FuncPam... funcpam)
{
	clock_t time1=clock();
	clock_t time2;
	clock_t delta;	
	
	while(true)
	{
		time2=clock();
		delta=time2-time1;
		if (delta>=period)
		{
			//通过函数指针调用函数,没有参数 
			(*funcptr)(funcpam...);
			//使用泛型funcpam时要加上... 
			break;
		}
	}
}

int main(void)
{
	//创建计时器线程,设定为5秒后执行回调函数printnum 
	std::thread timer1(timer<void(*)(void)>,5000,printnum);
	timer1.join();	
}

文章来源:https://blog.csdn.net/BUAAer_xuyang/article/details/135746890
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。