函数指针 —— 《C和指针》

函数指针

函数指针是指向函数的指针。 因此“函数指针”本身首先是指针变量,只不过该指针变量指向函数。

声明

返回值类型 ( *指针变量名) (形参列表);

int (*pfunc)(int x);
int (*pfunc)(int);       //无需将形参名给出

初始化

和普通指针一样,函数指针在使用之前必须初始化为指向某个函数。

int func(int x);    //被指向的函数
pfunc = func;    //初始化函数指针pfunc,使其指向函数func
pfunc = &func;    //也是正确的

int (*pfunc)(int) = func;   //也可以直接在声明时进行初始化

注意:在函数指针初始化之前需要有被指向函数func的原型,否则编译器就无法检查func的类型是否和pfunc所指向的类型一致。

使用:

int ans;
ans = func(5);
ans = pfunc(5);
ans = (*pfunc)(5);   //也是正确的,但间接访问操作是不必要的

注意:在初始化赋值语句中的操作符 & 和调用函数指针时的 * 操作符都是可选的:

1. 函数名被使用时总是由编译器把它转换为函数指针。& 只是显示地说明了编译器将隐式执行的任务。

2. *pfunc把函数指针pfunc转换为一个函数名,编译器在执行函数调用之前又会把它转换回去。因此这个转换是不必要的。

函数指针常见用途:回调函数和转移表(函数指针数组)

回调函数

把函数指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数的意义

百度百科:可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。简而言之,回调函数就是允许用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。

例:在单链表中查找一个值:

//参数node是一个指向链表第一个节点的指针,参数value是需要查找的值
Node *search_list(Node *node, int const value)
{
    while(node != NULL)
    {
        if(node->value == value)  break;
        node = node->link;
    }
    return node;
}

上述函数只适用于值为int型的链表。如果需要在一个字符串链表中查找,又需要另外编写一个函数。

我们想要实现的是使查找函数与类型无关,事实上在查找函数的内部,只有在进行比较操作时才与类型有关,我们可以将这一部分分离出来,这可以使用函数指针实现,通过如下两个步骤:

1. 另编写一个用于比较的函数,把一个指向该函数的指针作为参数传递给查找函数,查找函数通过调用这个函数来执行比较。

2. 向用于比较的函数传递一个指针以便可以灵活地选择比较的类型,由于void指针可以被强制转换为任何类型的指针,因此可以用来作为形参。

声明函数指针:

int(*compare)(void const *, void const *);

函数指针作为查找函数的形参,并在查找函数内部被调用:

Node *search_list(Node *node, void const *value, int(*compare)(void const *, void const *))
{
    while(node != NULL)
    {
        if(compare(&node->value, value) == 0)  break;
        node = node->link;
    }
    return node;
}

整型比较函数或字符串比较函数都可以被查找函数调用:

int compare_ints(void const *a, void const *b)
{
    if( *(int *a) == *(int *)b )   //void指针强制转换为int指针,并对指针取值进行比较
        return 0;
    else
        return 1;
}

int strcmp(char const *s1, char const *s2); //库函数strcmp正好符合我们的功能需求

//调用
desired_node = search_list(root, &desired_value, compare_ints);
desired_node = search_list(root, "desired_str", strcmp);

注意:库函数strcmp的参数被声明为char*而不是void*,有些编译器可能会发出警告信息。

转移表(函数指针数组)

函数指针数组是一个其元素是函数指针的数组。

//函数指针数组声明
void (*func[10])();   //数组在声明时未初始化,需要给定数组的长度

//声明+初始化
int a(), b(), c(), d();
int (*state[])() = {a, b, c, d}; //数组声明的同时进行初始化,数组大小可以省略

//通过数组下标索引
result = func[index]();
result = (*func[index])();  //也是正确的,但间接访问操作是不必要的

把具体的操作和选择操作的代码分开是一种良好的设计方案。

例:取自一个实现袖珍式计算器的一段代码,用switch语句实现将会使代码非常长:

switch(oper)
{
    case ADD:
        result = add(op1, op2);
        break;
    case SUB:
        result = sub(op1, op2);
        break;
    case MUL:
        result = mul(op1, op2);
        break;
    case DIV:
        result = div(op1, op2);
        break;
    ...
{

使用函数指针数组代替:

double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);

//定义函数指针数组,并对其进行初始化
double (*oper_func[])(double, double) = {add, sub, mul, div,...};

//声明和初始化也可以分开进行
double (*oper_func[10])(double, double);
oper_func[0] = add;
oper_func[1] = sub;
...

//通过数组下标index找到正确的函数指针,调用对应的函数
result = oper_func[index](op1, op2);

注意:1. 在函数指针数组初始化之前,确保有这些函数的原型。2. 转移表的越界下标引用同普通数组一样是不合法的,但C编译器通常不检查下标(下标检查会带来不小的开销),因此最好一开始就保证转移表所使用的下标位于合法的范围内。例如可以在读取oper(值为ADD,SUB等)时,确定其值是有效的,以便能够正确地向数组下标(从0开始的连续整数)映射。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
指针是C编程语言中非常重要的概念之一,对于初学者来说可能会感到害怕和困惑。但只要理解了指针的原理和用法,就会发现它其实并不可怕。 首先,指针是一个存储变量地址的变量,它可以指向任何数据类型的变量,包括整型、浮点型、字符型等等。我们可以通过指针访问变量的值,也可以通过指针修改变量的值,这是指针的一大优势。 其次,理解指针的应用场景能够帮助我们更好地使用它。比如,当我们需要在函数之间传递大量的数据时,通过传递指针可以提高程序的执行效率。另外,在动态内存分配和释放中,指针也是一个必不可少的工具。 理解指针的用法也是很重要的。首先,我们需要理解指针的声明和初始化。指针的声明使用“类型 * 变量名”的语法,例如 int *ptr; 表示声明了一个指向整型变量的指针指针的初始化可以通过给指针赋值一个变量的地址或者通过取地址符&获取变量的地址。 然后,我们需要了解指针的运算。指针可以进行四种基本的运算:取地址运算符&,取值运算符*,指针加法和指针减法。取地址运算符&用于获取变量的地址,取值运算符*用于获取指针所指向的变量的值。指针加法和指针减法用于指针地址的增加和减少,不同数据类型的指针相加或相减会有不同的结果。 最后,我们需要注意指针的安全性。在使用指针的过程中,需要特别小心避免空指针、野指针等问题的出现,因为这些问题容易引发程序的崩溃或者产生不可预知的结果。 总结来说,指针作为C语言中的重要概念,我们应该尽早学习和掌握。只要理解指针的原理和用法,我们就能够更加灵活地操作内存,提高程序的效率和功能。通过不断的实践和学习,我们可以逐渐摆脱对指针的恐惧,让指针成为我们编程的得力助手。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值