C语言学习之函数指针及其应用场景

该文章参考了网络上的部分代码(关于三星的那一部分),但是网络代码部分有错,在此我已经改正

(一)基本用法

不通过函数名调用函数,注意!!对于函数指针p, (*p)(5)和p(5)等价,都可以调用函数(实测过),(p)(5)不能写成p(5)

#include 

void printInt(int n);

typedef void (*pFunc_t)(int);//给返回值为void 参数为一个int的函数指针重命名,命名为pFunc_t

int main(void){
	pFunc_t p = printInt;//如果没有重命名,不可以使用此句,应该为:void (*p)(int) = printInt;
	p(5);//或者(*p)(5)
	return 0;
}

void printInt(int n){
	printf("%d\n", n);
}

(二) 应用场景

  1. 将函数指针作为参数(本质:调用函数在不同的场景需要不同的函数指针作为参数)

    先来个简单的,比如,编程的时候,经常用到for循环,想偷懒,可将其封装成函数。(当然实际开发应该没有人这么干,只适合用来举例学习,for循环并没有繁杂到非得去写一个函数,我们假设for循环很累人,需要把它封装为函数)

    #include 
    
    void print(int *n);
    void opposite(int *n);
    void myabs(int *n);
    
    typedef void (*pFunc_t)(int*);
    
    void for_each(int *arr, int size, pFunc_t p);
    
    
    int main(void){
    	pFunc_t pri = print;
    	pFunc_t opp = opposite;
    	pFunc_t abs = myabs;
    	int arr[]={1,2,3,-4,-5,-6};
    	for_each(arr, 6, pri);
    	printf("\n");
    	for_each(arr, 6, opp);
    	for_each(arr, 6, pri);
    	printf("\n");
    	for_each(arr, 6, abs);
    	for_each(arr, 6, pri);
    	printf("\n");
    	return 0;
    }
    
    /*
     * 该循环函数,下标为0~size-1,自增1
     * 该函数用来遍历数组,并对数组中单个数进行操作
     * arr :数组指针
     * size:数组大小
     * p:	 对每个数进行的操作
     * */
    void for_each(int *arr, int size, pFunc_t p){
    	int i = 0;
    	for(i = 0; i<size; i++){
    		p(arr+i);		
    	}
    }
    
    //打印指针指向的值
    void print(int *n){
    	printf("%d ", *n);
    }
    
    //将指针指向的值取反
    void opposite(int *n){
    	*n = -*n;
    }
    
    //将指针指向的值取绝对值
    void myabs(int *n){
    	*n = *n < 0 ? -(*n) : *n;
    }
    

    如果还没体会到函数指针在这里发挥的作用,可以这样理解:以前写程序,当遇到“打印数组的值”“取反数组的值”“将数组的值取绝对值”这种需求:

    1. 我们通常写成三个函数,每个函数都写一遍for循环,3个函数之间的唯一区别就在于for循环遍历时,对数组中的值所进行的操作不一样,这种方法是代码量最大的一种写法。

    2. 写一个函数进行遍历,该函数参数除了数组指针,数组大小外,还应有一个变量值,作为标记,代码如下。在for循环遍历时,通过if语句对标记变量进行判断,以执行不同的操作。这样的坏处在于增加了耦合度(产生了控制耦合),独立的代码聚集在if语句的各个分支:耦合度低的一个特点就是你不知道该怎么给函数起名了,这个函数,标记为1时干这个活儿,标记为2时干那个活儿,这个函数到底叫打印数组,还是叫取反数组?如果我新加上flag == 3, flag ==4时,功能更多了怎么办?所以有了函数指针。

      for_each(int *arr, int size, int flag){
      	for(int i=0; i<size; i++){
      		if(flag==1){
      			//打印数值
      		}else if(flag == 2){
      			//取反数值
      		}else if(flag ==3){
      			//取绝对值
      		}
      		else{
      			//... ...
      		}
      	}
      }
      
    3. 函数指针的写法。函数指针相比于上一种依赖flag的方法,最直观上的体验就是,我可以给函数起名了:该函数完成了循环遍历数组并执行pFunc_t p功能。那为什么2.中的函数不可以这样描述为:该函数完成了循环遍历数组并执行flag功能?因为flag只是一个变量,而pFunc_t p函数指针真真实实的就是一个功能,另外,flag取值数量很庞大时,我们还需要额外的开销来记录每一个flag值对应的功能是什么。最重要的是,依赖flag来控制,严重降低了模块化。比如:在函数指针的实现方法中,当我们在.c的其他文件处想对一个数进行取反的时候,我可以调用opposite方法,也可以用函数指针进行调用,但flag的那种不行,因为flag实现的for_each,其中的各种操作(比如取反、打印、取绝对值)都是直接写在for_each里面的,没有现成的函数供使用,无法调用,并且每当我有新的需求需要增加功能的时候,我都需要重新修改for_each,得不偿失

      再来一个例子体会一下这种思想,例子来源 分析函数指针及其两个主要用途

      #include   
         
      //Calculate用于计算积分。一共三个参数。第一个为函数指针func,指向待积分函数。二三参数为积分上下限  
      double Calculate(double(*func)(double x), double a, double b)  
      {  
          double dx = 0.0001;//细分的区间长度  
          double sum = 0;  
          for (double xi = a+dx; xi <= b; xi+=dx)  
          {  
             double area = func(xi)*dx;  
             sum +=area;  
          }  
          return sum;  
      }  
         
      double func_1(double x)  
      {  
          return x*x;  
      }  
         
      double func_2(double x)  
      {  
          return x*x*x;  
      }  
         
      void main()  
      {  
          printf("%lf\n", Calculate(func_1, 0, 1));  
          printf("%lf\n", Calculate(func_2, 0, 1));  
      }  
      
  2. 引用不在代码段中的函数
    来自 分析函数指针及其两个主要用途 他的调用方法稍微不对,在此已经改正。

    此功能在嵌入式系统中经常使用。我们知道,我们写的用户程序的code是存放在代码段中的,在嵌入式系统中,一般情况下是存放在flash中的。什么叫不在代码段中的函数?很多微控制器在出厂前会将一些功能函数(系统函数)固化在rom中(类似于PC机中的BIOS),如Flash擦写功能,Flash Copy功能。而我们写的代码是不认识这些函数的,不能直接使用函数名调用。所以,当我们想在用户程序中调用这些系统函数时,就只能使用函数指针的方式,通过将系统函数的入口地址传给函数指针,来达到调用rom中程序的目的。这些系统函数一般都会在官方手册中给出功能,返回值类型和参数列表。

    下面是从三星的S5PV210_applicationnote中截取的一个系统函数。

    C语言学习之函数指针及其应用场景_第1张图片使用define来定义函数指针
    #define CopySDMMCtoMem(z,a,b,c,e)(((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned int *)0xD0037F98)))(z,a,b,c,e))

    从上我们可以分析出,此系统函数的入口地址为0xD0037F98。返回bool型,带有int, unsigned int, unsigned short, unsigned int*, bool型五个参数。实际使用时,我们可以如下调用:

     //方法一
     #define CopySDMMCtoMem(z,a,b,c,e)(((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned int *)0xD0037F98)))(z,a,b,c,e))
     CopySDMMCtoMem(1,1,1,1,1);
    
    //方法二
    typedef bool (*CopySDMMCToMem_t)(int, unsigned int, unsigned short, unsigned int*, bool);
    CopySDMMCToMem_t p = (CopySDMMCToMem_t)0xD0037F98;
    p(1,1,1,1,1);//或者 (*p)(1,1,1,1,1);
    
    
    

你可能感兴趣的:(C语言)