深入浅出C/C++函数指针

2023-11-22 13:15:55 浏览数 (1)

和变量一样,函数在内存中有固定的地址,函数的实质也是内存中一块固定的空间。函数的地址存放其机器代码的内存的开始地址。当我们需要调用一个函数并让其使用我们期望的函数进行操作时,函数指针就能发挥作用了。比如pthread_create函数,需要自定义线程的轨迹。指定线程函数的指针,新线程将从该函数的起始地址开始执行。

简单示例

函数的地址就是函数名print_arg。这段代码是一个使用模板和函数指针的示例,它打印不同类型参数的值。

声明了两个函数指针 pips,分别指向接受 int*string* 类型参数的 print_arg 函数。通过 (*pi)(&num) 调用函数指针 pi,将 &numnum 的地址)作为参数传递给了 print_arg 函数,从而打印了 num 的值。再通过 (*ps)(&str) 调用函数指针 ps,将 &strstr 的地址)作为参数传递给了 print_arg 函数,从而打印了 str 的值。

定义函数指针void (*pi)(int *) = print_arg时,因为括号的优先级比*高,因此(*pi)(int *)表示一个函数指针,而*pi(int *)则表示一个返回指针类型的函数。

代码语言:javascript复制
#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);
}

输出结果为

代码语言:javascript复制
10
hello

事实上,下面这两条语句等价。pi(&num):直接使用函数指针 pi 来调用函数,将参数 &num 传递给函数。这种方式更简洁,更符合普通函数调用的语法。(*pi)(&num):通过解引用函数指针 pi,获取指向的函数,并将参数 &num 传递给这个函数。这种方式更明确地显示了对函数指针的解引用操作。

代码语言:javascript复制
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 函数,将函数指针 pinum 作为参数传递给该函数,从而调用了 print_arg 函数并输出了 num 的值。再通过 do_what_u_want(ps, str) 调用 do_what_u_want 函数,将函数指针 psstr 作为参数传递给该函数,从而调用了 print_arg 函数并输出了 str 的值。

代码语言:javascript复制
#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);
}

输出结果为

代码语言:javascript复制
10
hello

使用auto简化

函数指针的类型表示可能非常麻烦,示例程序的void (*pi)(int *)可能感觉不到,但是这样呢?

代码语言:javascript复制
// 函数
const double * func(const int arr[], int size);
// 指向func的指针
const double * (*pf)(const int *, int) = func;

可以使用C 11的自动类型推断来简化。使用自动类型推断能够简化代码,并且更加直观,避免了显式指定函数指针类型的繁琐过程。

代码语言:javascript复制
// 函数
const double * func(const int arr[], int size);
// 指向func的指针,使用auto进行类型推断
auto pf = func;

示例程序

代码语言:javascript复制
#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);
}

输出结果为

代码语言:javascript复制
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 函数。
代码语言:javascript复制
#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);
}

输出结果

代码语言:javascript复制
2
4
24

使用typedef简化

typedef 是 C/C 中的一个关键字,用于为现有的数据类型创建一个新的类型别名。

typedef 的语法形式如下:

代码语言:javascript复制
typedef <existing_type> <new_type_name>;

其中 <existing_type> 表示现有的数据类型,而 <new_type_name> 表示为该数据类型创建的新的类型名称。

通过使用 typedef 关键字,可以创建更具可读性和可维护性的代码,同时提高代码的灵活性和可移植性。它常用于以下几种情况:

  • 创建数据类型的别名:可以为现有的数据类型创建一个简短、易于理解的别名,以提高代码的可读性。例如:
代码语言:javascript复制
typedef int Age;  // 创建一个名为 Age 的别名,表示整数类型
  • 简化复杂的类型声明:可以使用 typedef 来简化复杂的类型声明,使代码更加清晰。例如:
代码语言:javascript复制
typedef void (*func_ptr)(int *);  // 创建一个名为 FuncPtr 的别名,表示一个指向函数的指针类型
  • 提高代码的可移植性:通过使用 typedef,可以在不同的平台或编译器上轻松更改数据类型,而无需修改大量的代码。例如:
代码语言:javascript复制
typedef unsigned long long ULL;  // 创建一个名为 ULL 的别名,表示无符号长长整数类型

使用typedef简化函数指针。使用template的情况

代码语言:javascript复制
template <typename T>
using func_ptr = void (*)(T *);

不使用template的情况

代码语言:javascript复制
typedef void (*func_ptr)(int *);

示例程序如下

代码语言:javascript复制
#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);
}

运行结果

代码语言:javascript复制
100

.post-copyright { box-shadow: 2px 2px 5px; line-height: 2; position: relative; margin: 40px 0 10px; padding: 10px 16px; border: 1px solid var(--light-grey); transition: box-shadow .3s ease-in-out; overflow: hidden; border-radius: 12px!important; background-color: var(--main-bg-color); } .post-copyright:before { position: absolute; right: -26px; top: -120px; content: 'f25e'; font-size: 200px; font-family: 'FontAwesome'; opacity: .2; } .post-copyright__title { font-size: 22px; } .post-copyright_type { font-size: 18px; color:var(--theme-color) } .post-copyright .post-copyright-info { padding-left: 6px; font-size: 15px; } .post-copyright-m-info .post-copyright-a, .post-copyright-m-info .post-copyright-c, .post-copyright-m-info .post-copyright-u { display: inline-block; width: fit-content; padding: 2px 5px; font-size: 15px; } .muted-3-color { color: var(--main-color); } /*手机优化*/ @media screen and (max-width:800px){.post-copyright-m-info{display:none}} ------本页内容已结束,喜欢请分享------


0 人点赞