【C语言】如何使用函数指针提高代码质量

本文介绍了C语言函数指针的概念,包括如何定义和使用。通过回调、多态和跳转表三个示例展示了函数指针在实现代码复用、解耦、灵活性、简洁性、扩展性和效率上的优点。同时,文中也提到了使用函数指针可能带来的复杂性、安全问题,并提供了相关学习资源。
摘要由CSDN通过智能技术生成

C 语言函数指针

本文介绍了 C 语言函数指针的概念、用法和作用,并且给出了三个使用函数指针的例子,分别是回调、多态和跳转表。通过这些例子,我们可以看到函数指针的优点有:可以实现代码的复用、解耦和灵活性;可以实现代码的简洁、清晰和易扩展性;可以实现代码的效率和可维护性。本文也指出了函数指针的一些缺点和风险,并且提供了一些注意事项和学习资源。

什么是函数指针?

在 C 语言中,我们可以使用指针来存储变量或数组的地址,从而实现间接访问或修改它们的值。但是,除了变量和数组之外,我们还可以使用指针来存储函数的地址,从而实现间接调用或传递函数。这样的指针就叫做函数指针

函数指针的定义和使用与普通指针类似,只是在声明时需要加上函数的返回类型和参数类型。例如:

int (*p)(int, int); // 定义一个函数指针 p,它可以指向一个返回 int 类型并接受两个 int 类型参数的函数

注意:括号不能省略,否则会被解释为一个返回 int 指针类型并接受两个 int 类型参数的函数。

要让一个函数指针指向一个具体的函数,我们只需要把该函数的名字(不带括号)赋值给该指针即可。例如:

int add(int a, int b) // 定义一个加法函数
{
    return a + b;
}

p = add; // 让 p 指向 add 函数

注意:不要在赋值时加上括号和参数,否则会被解释为调用该函数并把返回值赋给该指针。

要通过一个函数指针来调用它所指向的函数,我们有两种方法:

  • 使用(*p)(...)的形式,在括号中加上实际参数。
  • 使用 p(...) 的形式,在括号中加上实际参数。

例如:

int result;

result = (*p)(3, 4); // 通过 (*p) 调用 add 函数,并把结果赋给 result

result = p(3, 4); // 通过 p 调用 add 函数,并把结果赋给 result

注意:两种方法是等价的,但是第二种方法更简洁。

函数指针有什么用?

使用函数指针有以下几个好处:

  • 可以实现回调机制。回调就是把一个函数作为参数传递给另一个函数,在后者执行过程中调用前者。这样可以实现解耦和灵活性。
  • 可以实现多态机制。多态就是根据不同情况执行不同行为。通过改变不同的函数指针,可以动态地选择不同功能。
  • 可以实现跳转表机制。跳转表就是根据索引或条件选择执行不同分支。通过使用包含多个不同功能的数组或结构体来存储多个不同功能。

下面举一些例子来说明这些好处。

回调

假设我们有以下需求:编写一个通用排序算法(比如快速排序),使其能够对任意类型和大小的数组进行排序,并且能够根据用户自定义比较规则进行比较。

如果我们只使用普通函数来实现这个需求,那么我们可能需要为每种类型和大小都编写一个单独版本,并且在每次比较时都要硬编码比较规则。这样会导致代码冗余、复杂、不灵活。

如果我们使用函数指针来实现这个需求,那么我们只需要编写一个通用排序算法,它接受一个数组的首地址、元素个数、元素大小和一个比较函数指针作为参数。在排序过程中,它通过调用比较函数指针来进行比较。这样可以实现代码复用、简洁、灵活。

例如:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

// 定义一个通用的比较函数指针类型
typedef int (*Compare)(const void*, const void*);

// 定义一个通用的快速排序算法
void QuickSort(void* arr, int left, int right, size_t size, Compare cmp) {
    if (left >= right) return; // 递归出口
    int i = left;
    int j = right;
    char* pivot = (char*)arr + left * size; // 选择第一个元素作为基准(注意强制转换为 char* 类型以便进行地址运算)
    char* temp = malloc(size); // 分配临时空间存储交换元素
    while (i < j) {
        while (i < j && cmp((char*)arr + j * size, pivot) >= 0) j--; // 从右向左找到第一个小于基准的元素
        while (i < j && cmp((char*)arr + i * size, pivot) <= 0) i++; // 从左向右找到第一个大于基准的元素
        if (i < j) { // 如果 i 和 j 都没有越界,交换它们所指向的元素
            memcpy(temp, (char*)arr + i * size, size);
            memcpy((char*)arr + i * size, (char*)arr + j * size, size);
            memcpy((char*)arr + j * size, temp, size);
        }
    }
    // 将基准元素和 i 所指向的元素交换(此时 i 和 j 相等)
    memcpy(temp, pivot, size);
    memcpy(pivot,(char*)arr + i * size,size);
    memcpy((char*)arr + i *size,temp,size);

    free(temp); // 释放临时空间

    QuickSort(arr,left,i-1,size,cmp); // 对左半部分递归排序
    QuickSort(arr,i+1,right,size,cmp); // 对右半部分递归排序

}

// 定义一个比较整数大小的函数
int CompareInt(const void* a,const void* b){
   return *(int*)a - *(int*)b; 
}

// 定义一个比较字符串长度的函数
int CompareStr(const void* a,const void* b){
   return strlen(*(char**)a)-strlen(*(char**)b); 
}

// 测试代码

int main(){
   int arr[] = {5 ,3 ,7 ,2 ,9 ,1 ,4 ,6 ,8}; // 定义一个整数数组
   
   printf("Before sorting:\n");
   for(int i=0;i<9;i++){
      printf("%d ", arr[i]);
   }
   
   printf("\n");

   QuickSort(arr ,0 ,8,sizeof(int),CompareInt); // 使用快速排序对整数数组按照大小排序
   
   printf("After sorting:\n");
   for(int i=0;i<9;i++){
      printf("%d ", arr[i]);
   }
   
   printf("\n");

   
}

运行结果:

Before sorting:
5 3 7 2 9 1 4 6 8 
After sorting:
1 2 3 4 5 6 7 8 9 

从这个例子中,我们可以看到,通过使用函数指针作为参数,我们可以实现一个通用的快速排序算法,它可以对任意类型和大小的数组进行排序,并且可以根据用户自定义的比较函数进行比较。这样就避免了为每种类型和大小都编写一个单独版本的排序算法,并且提高了代码的复用性和灵活性。

多态

假设我们有以下需求:编写一个通用计算器程序,它可以根据用户输入的不同运算符(+、-、*、/)来执行不同的运算,并且输出结果。

如果我们只使用普通函数来实现这个需求,那么我们可能需要使用 switch-case 或者 if-else 来判断用户输入的运算符,并且根据不同情况调用不同的运算函数。这样会导致代码冗长、复杂、不易扩展。

如果我们使用函数指针来实现这个需求,那么我们只需要定义一个函数指针变量,它可以指向任何返回 double 类型并接受两个 double 类型参数的函数。然后根据用户输入的运算符,让该变量指向相应的运算函数。最后通过该变量来调用所指向的运算函数,并输出结果。这样可以实现代码简洁、清晰、易扩展。

例如:

#include<stdio.h>

// 定义一个通用的运算函数指针类型
typedef double (*Operation)(double, double);

// 定义四种基本运算函数
double Add(double a, double b) {
    return a + b;
}

double Sub(double a, double b) {
    return a - b;
}

double Mul(double a, double b) {
    return a * b;
}

double Div(double a, double b) {
    if (b ==0) {
        printf("Error: divide by zero!\n");
        return -1;
    }
    return a / b;
}

// 测试代码

int main() {
    char op; // 定义一个字符变量存储用户输入的运算符
    double x,y; // 定义两个浮点数变量存储用户输入的操作数
    Operation p; // 定义一个运算函数指针变量

    printf("Please enter an expression (such as '3 + 4'):\n");
    
    while(scanf("%lf %c %lf", &x,&op,&y)==3){ // 循环读取用户输入
        
        switch(op){ // 根据不同情况让 p 指向相应的运算函数
            case '+':
                p = Add;
                break;
            case '-':
                p = Sub;
                break;
            case '*':
                p = Mul;
                break;
            case '/':
                p = Div;
                break;
            default:
                printf("Error: invalid operator!\n");
                continue; // 跳过本次循环
            
        }

        printf("%f %c %f = %f\n", x ,op ,y ,p(x,y)); // 调用 p 所指向的运算函数,并输出结果
        
        printf("Please enter another expression (or press Ctrl+Z to exit):\n");
        
    }

    
}

运行结果:

Please enter an expression (such as ‘3 + 4’): 
3 + 4
3.000000 + 4.000000 =7.000000 
Please enter another expression (or press Ctrl+Z to exit): 
5 *6 5.000000 *6.000000 =30.000000 
Please enter another expression (or press Ctrl+Z to exit): 
10 /2 
10.000000 /2.000000 =5.000
Please enter another expression (or press Ctrl+Z to exit):
10 /0
Error: divide by zero!
-1.000000
Please enter another expression (or press Ctrl+Z to exit):
9 % 2
Error: invalid operator!
Please enter another expression (or press Ctrl+Z to exit):

从这个例子中,我们可以看到,通过使用函数指针变量,我们可以实现一个通用的计算器程序,它可以根据用户输入的不同运算符来执行不同的运算,并输出结果。这样就避免了使用 switch-case 或者 if-else 来判断用户输入的运算符,并且提高了代码的可读性和可扩展性。

跳转表

假设我们有以下需求:编写一个菜单程序,它可以根据用户输入的不同选项(1、2、3、4)来执行不同的功能(比如打印一些信息)。

如果我们只使用普通函数来实现这个需求,那么我们可能需要使用 switch-case 或者 if-else 来判断用户输入的选项,并且根据不同情况调用不同的功能函数。这样会导致代码冗长、复杂、不易修改。

如果我们使用函数指针来实现这个需求,那么我们只需要定义一个包含四个功能函数指针的数组或结构体,它相当于一个跳转表。然后根据用户输入的选项,从该数组或结构体中取出相应的函数指针,并通过该指针来调用所指向的功能函数。这样可以实现代码简洁、清晰、易修改。

例如:

#include<stdio.h>

// 定义四种功能函数
void Func_1() {
    printf("This is function 1.\n");
}

void Func_2() {
    printf("This is function 2.\n");
}

void Func_3() {
    printf("This is function 3.\n");
}

void Func_4() {
    printf("This is function 4.\n");
}

// 测试代码

int main() {
    int choice; // 定义一个整数变量存储用户输入的选项
    
    // 定义一个包含四个功能函数指针的数组
    void (*funcs[4])() = {Func_1, Func_2, Func_3, Func_4};

    printf("Please enter a choice (1-4):\n");

    while(scanf("%d", &choice)==1){ // 循环读取用户输入
        
        if(choice>=1 && choice<=4){ // 如果选项合法
            
            funcs[choice-1](); // 从数组中取出相应的函数指针,并调用所指向的功能函数
            
        }else{ // 如果选项非法
            
            printf("Error: invalid choice!\n");
            
        }
        
        printf("Please enter another choice (or press Ctrl+Z to exit):\n");
        
    }

    
}

运行结果:

Please enter a choice (1-4): 
1 
This is function 1. 
Please enter another choice (or press Ctrl+Z to exit):
2 
This is function 2. 
Please enter another choice (or press Ctrl+Z to exit): 
3 
This is function 3. 
Please enter another choice (or press Ctrl+Z to exit): 
4 
This is function 4. 
Please enter another choice (or press Ctrl+Z to exit):
5
Error: invalid choice! 
Please enter another choice (or press Ctrl+Z to exit):

从这个例子中,我们可以看到,通过使用包含多个功能函数指针的数组或结构体,我们可以实现一个菜单程序,它可以根据用户输入的不同选项来执行不同的功能,并输出结果。这样就避免了使用 switch-case 或者 if-else 来判断用户输入的选项,并且提高了代码效率和可维护性。

总结

本文介绍了 C 语言函数指针的概念、用法和作用,并且给出了三个使用函数指针的例子,分别是回调、多态和跳转表。通过这些例子,我们可以看到函数指针的优点有:

  • 可以实现代码的复用、解耦和灵活性。
  • 可以实现代码的简洁、清晰和易扩展性。
  • 可以实现代码的效率和可维护性。

当然,函数指针也有一些缺点,比如:

  • 可能增加了代码的复杂度和难度。
  • 可能导致了内存泄漏或安全问题。
  • 可能与其他语言特性不兼容或不一致。

因此,在使用函数指针时,我们需要根据具体情况权衡利弊,并且注意避免一些常见的错误或风险。

希望本文对你有所帮助,如果你有任何疑问或建议,请在评论区留言。谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值