和变量一样,函数在内存中有固定的地址,函数的实质也是内存中一块固定的空间。函数的地址存放其机器代码的内存的开始地址。当我们需要调用一个函数并让其使用我们期望的函数进行操作时,函数指针就能发挥作用了。比如pthread_create
函数,需要自定义线程的轨迹。指定线程函数的指针,新线程将从该函数的起始地址开始执行。
简单示例
函数的地址就是函数名print_arg
。这段代码是一个使用模板和函数指针的示例,它打印不同类型参数的值。
声明了两个函数指针 pi
和 ps
,分别指向接受 int*
和 string*
类型参数的 print_arg
函数。通过 (*pi)(&num)
调用函数指针 pi
,将 &num
(num
的地址)作为参数传递给了 print_arg
函数,从而打印了 num
的值。再通过 (*ps)(&str)
调用函数指针 ps
,将 &str
(str
的地址)作为参数传递给了 print_arg
函数,从而打印了 str
的值。
定义函数指针void (*pi)(int *) = print_arg
时,因为括号的优先级比*高,因此(*pi)(int *)
表示一个函数指针,而*pi(int *)
则表示一个返回指针类型的函数。
#include <iostream>
using namespace std;
// 打印参数
template <typename T>
void print_arg(T *arg)
{
// 打印参数
cout << *arg << endl;
}
int main()
{
int num = 10;
string str = "hello";
void (*pi)(int *) = print_arg;
void (*ps)(string *) = print_arg;
(*pi)(&num);
(*ps)(&str);
}
输出结果为
10
hello
事实上,下面这两条语句等价。pi(&num)
:直接使用函数指针 pi
来调用函数,将参数 &num
传递给函数。这种方式更简洁,更符合普通函数调用的语法。(*pi)(&num)
:通过解引用函数指针 pi
,获取指向的函数,并将参数 &num
传递给这个函数。这种方式更明确地显示了对函数指针的解引用操作。
pi(&num);
(*pi)(&num);
传递函数指针参数
我们最开始提到了pthread_create
函数,需要自定义线程的轨迹。指定线程函数的指针,新线程将从该函数的起始地址开始执行。函数指针可以作为参数被传递到另一个函数中。
模板函数 do_what_u_want
。这个函数接受一个函数指针 pf
和一个参数 arg
,并通过调用函数指针对参数进行操作。函数体中通过 (*pf)(&arg)
调用函数指针 pf
,将参数的地址传递给相应的函数,从而对参数进行操作。
通过 do_what_u_want(pi, num)
调用 do_what_u_want
函数,将函数指针 pi
和 num
作为参数传递给该函数,从而调用了 print_arg
函数并输出了 num
的值。再通过 do_what_u_want(ps, str)
调用 do_what_u_want
函数,将函数指针 ps
和 str
作为参数传递给该函数,从而调用了 print_arg
函数并输出了 str
的值。
#include <iostream>
using namespace std;
// 打印参数
template <typename T>
void print_arg(T *arg)
{
// 打印参数
cout << *arg << endl;
}
// 模板函数,传入函数指针和参数,无返回值
template <typename T>
void do_what_u_want(void (*pf)(T *), T &arg)
{
// 调用函数指针,传入num的引用,函数返回值无
(*pf)(&arg);
}
int main()
{
int num = 10;
string str = "hello";
void (*pi)(int *) = print_arg;
void (*ps)(string *) = print_arg;
do_what_u_want(pi, num);
do_what_u_want(ps, str);
}
输出结果为
10
hello
使用auto简化
函数指针的类型表示可能非常麻烦,示例程序的void (*pi)(int *)
可能感觉不到,但是这样呢?
// 函数
const double * func(const int arr[], int size);
// 指向func的指针
const double * (*pf)(const int *, int) = func;
可以使用C++ 11的自动类型推断来简化。使用自动类型推断能够简化代码,并且更加直观,避免了显式指定函数指针类型的繁琐过程。
// 函数
const double * func(const int arr[], int size);
// 指向func的指针,使用auto进行类型推断
auto pf = func;
示例程序
#include <iostream>
using namespace std;
// 打印参数
template <typename T>
void print_arg(T *arg)
{
// 打印参数
cout << *arg << endl;
}
// 函数名:square
// 参数:num
// 功能:将num的值乘以2
void square(int *num)
{
*num *= *num;
}
// 计算阶乘
void fact(int *num)
{
// 定义一个临时变量
int tmp = *num;
// 当临时变量大于1时,执行循环
while (tmp > 1)
{
// 将临时变量乘以临时变量减1的结果
*num *= tmp - 1;
// 临时变量减1
tmp--;
}
}
// 模板函数,传入函数指针和参数,无返回值
template <typename T>
void do_what_u_want(void (*pf)(T *), T &arg)
{
// 调用函数指针,传入num的引用,函数返回值无
(*pf)(&arg);
}
int main()
{
int num = 10;
auto pf = square;
do_what_u_want(pf, num);
auto pi = print_arg<int>;
do_what_u_want(pi, num);
}
输出结果为
100
函数指针数组
pfs[2]
;[]的优先级高于*,因此(*pfs[2])
表示一个包含两个元素的指针,而(*pfs)[2]
则表示一个指向包含两个元素的数组的指针。
三种调用方法:
-
在第一种方式中,直接使用
pfs[1]
作为函数指针参数,传递给do_what_u_want
,调用了fact
函数,计算了num
的阶乘。
-
在第二种方式中,使用
*(pfs)
将函数指针数组名解引用为第一个元素的指针,传递给do_what_u_want
,同样调用了fact
函数。
-
在第三种方式中,将
pfs[1]
赋值给一个自动推断类型的变量func
,然后将func
作为函数指针参数传递给do_what_u_want
,同样调用了fact
函数。
#include <iostream>
using namespace std;
// 打印参数
template <typename T>
void print_arg(T *arg)
{
// 打印参数
cout << *arg << endl;
}
// 函数名:square
// 参数:num
// 功能:将num的值乘以2
void square(int *num)
{
*num *= *num;
}
// 计算阶乘
void fact(int *num)
{
// 定义一个临时变量
int tmp = *num;
// 当临时变量大于1时,执行循环
while (tmp > 1)
{
// 将临时变量乘以临时变量减1的结果
*num *= tmp - 1;
// 临时变量减1
tmp--;
}
}
// 模板函数,传入函数指针和参数,无返回值
template <typename T>
void do_what_u_want(void (*pf)(T *), T &arg)
{
// 调用函数指针,传入num的引用,函数返回值无
(*pf)(&arg);
}
int main()
{
int num = 2;
auto pi = print_arg<int>;
void (*pfs[2])(int *) = {square, fact};
// 三种不同的调用方法
// 法1
do_what_u_want(pfs[1], num);
do_what_u_want(pi, num);
// 法二
do_what_u_want(*(pfs), num);
do_what_u_want(pi, num);
// 法三
auto func = pfs[1];
do_what_u_want(func, num);
do_what_u_want(pi, num);
}
输出结果
2
4
24
使用typedef简化
typedef
是 C/C++ 中的一个关键字,用于为现有的数据类型创建一个新的类型别名。
typedef
的语法形式如下:
typedef <existing_type> <new_type_name>;
其中 <existing_type>
表示现有的数据类型,而 <new_type_name>
表示为该数据类型创建的新的类型名称。
通过使用 关键字,可以创建更具可读性和可维护性的代码,同时提高代码的灵活性和可移植性。它常用于以下几种情况:
-
创建数据类型的别名:可以为现有的数据类型创建一个简短、易于理解的别名,以提高代码的可读性。例如:
typedef int Age; // 创建一个名为 Age 的别名,表示整数类型
-
简化复杂的类型声明:可以使用
typedef
typedef void (*func_ptr)(int *); // 创建一个名为 FuncPtr 的别名,表示一个指向函数的指针类型
-
typedef
,可以在不同的平台或编译器上轻松更改数据类型,而无需修改大量的代码。例如:
typedef unsigned long long ULL; // 创建一个名为 ULL 的别名,表示无符号长长整数类型
使用typedef简化函数指针。使用template的情况
template <typename T>
using func_ptr = void (*)(T *);
不使用template的情况
typedef void (*func_ptr)(int *);
示例程序如下
#include <iostream>
using namespace std;
// // 定义一个函数指针类型,参数为int*
template <typename T>
using func_ptr = void (*)(T *);
// 打印参数
template <typename T>
void print_arg(T *arg)
{
// 打印参数
cout << *arg << endl;
}
// 函数名:square
// 参数:num
// 功能:将num的值乘以2
void square(int *num)
{
*num *= *num;
}
// 计算阶乘
void fact(int *num)
{
// 定义一个临时变量
int tmp = *num;
// 当临时变量大于1时,执行循环
while (tmp > 1)
{
// 将临时变量乘以临时变量减1的结果
*num *= tmp - 1;
// 临时变量减1
tmp--;
}
}
// 模板函数,传入函数指针和参数,无返回值
template <typename T>
void do_what_u_want(void (*pf)(T *), T &arg)
{
// 调用函数指针,传入num的引用,函数返回值无
(*pf)(&arg);
}
int main()
{
// 使用typedef简化
int num = 10;
func_ptr<int> fp = square;
auto pi = print_arg<int>;
do_what_u_want(fp, num);
do_what_u_want(pi, num);
}
运行结果
100