C语言指针进阶(一)——深入详解“函数指针”与“指针函数”

前言:C语言最难的地方莫过于各种类型的指针,光听名字就把人给绕晕了,本文是在一些重要的基础概念之上进行说明的,需要一些知识储备,即:什么是数组指针、指针数组、函数指针、指针函数等,然后才能看得懂本文。

一、简单概述

1.1 函数指针

所谓函数指针即定义一个指向函数的指针变量,定义的格式如下:

int (*p)(int x, int  y);  //注意:这里的括号不能掉,因为括号()的运算优先级比解引用运算符*高

这个函数的类型是有两个整型参数,返回值是个整型。对应的函数指针类型

int (*) (int a, int b);  

对应的函数指针定义:

int (*p)(int x, int  y);  //参数名可以去掉,并且通常都是去掉的。这样指针p就可以保存函数类型为两个整型参数,返回值是整型的函数地址了。

int (*p)(int, int);

我们一般可以这么使用,通过函数指针调用函数:

int maxValue (int a, int b) {
    return a > b ? a : b;
}    

int (*p)(int, int) = NULL;  //定义一个与maxValue兼容的指针
p = maxValue;
p(20, 45);  //通过指针调用

1.2 指针函数

指针函数:指的是函数的返回值是一个指针,比如我的函数返回的是一个指向整数int的指针,定义格式如下:

int *p(int a,int b); //注意这里的*与P之间是没有括号的,所以含义是函数p(int,int)会返回一个(int *)指针

当然,函数本身也可能返回一个函数指针,后面会说到。

最重要的点:如何确定指针变量的类别是非常重要的,我们可以通过c++的

typeid(variable).name(); //查看变量的类别

总结如下:

名称(xx指针)含义(指向...的指针)定义形式指针p的类型typeid(p).name
变量指针指向变量的指针

int *p=&a

int (*p)=&a

int *
常量指针指向常量的指针

int const *p=&a(括号省略了)

const int *p=&a(括号省略了)

int const *
一维数组指针指向一维数组(首元素)的指针int  *p=a(括号省略了)int *
二维数组指针指向二维数组(第一行整体是首元素的指针)int (*p)[4]=aint (*)[4]
函数指针指向函数的指针int (*p)(int a,int b)=addint (*)(int,int)

二、函数也可以作为参数

2.1 回调函数

上述内容是函数指针的基础用法,很多语言都支持函数作为参数和返回值,典型的像python动态语言,C语言当然也可以了,没错,其实函数指针更重要的意义在于函数回调。
举个例子:
现在我们有这样一个需求:实现一个函数,将一个整形数组中比50大的打印在控制台,我们可能这样实现:

void compareNumberFunction(int *numberArray, int count, int compareNumber) 
{
    for (int i = 0; i < count; i++)
   {
        if (*(numberArray + i) > compareNumber) 
        {
            printf("%d\n", *(numberArray + i));
        }
    }
}
int main() 
{

    int numberArray[5] = {15, 34, 44, 56, 64};
    int compareNumber = 50;
    compareNumberFunction(numberArray, 5, compareNumber);

    return 0;
}   

这样实现是没有问题的,然而现在我们又有这样一个需求:实现一个函数,将一个整形数组中比50小的打印在控制台。当然我么可以完全又把上面的代码copy一遍,将大于改写成小于。这样做当然可以,然而作为开发者,我们要未雨绸缪,要考虑到将来可能添加更多类似的需求,那么你将会有大量的重复代码,使你的项目变得臃肿,所以这个时候我们需要冷静下来思考,其实这两个需求很多代码都是相同的,只要更改一下判断条件即可,而判断条件我们如何变得更加灵活呢?

这时候我们就用到回调函数的知识了,我们可以定义一个函数,这个函数需要两个int型参数,函数内部实现代码是将两个整形数字做比较,将比较结果的bool值作为函数的返回值返回出来,以大于被比较数字的情况为例:

BOOL compareGreater(int number, int compareNumber) {
    return number > compareNumber;
}   

同理,小于被比较的数字函数定义如下:

BOOL compareLess(int number, int compareNumber) {
    return number < compareNumber;
}

接下来,我们可以将这个函数作为compareNumberFunction的一个参数进行传递(没错,函数可以作为参数),那么我们就需要一个函数指针获取函数的地址,从而在compareNumberFunction内部进行对函数的调用,于是,compareNumberFunction函数的定义变成了这样:

void compareNumberFunction(int *numberArray, int count, int compareNumber, BOOL (*p)(int, int)) 
{
    for (int i = 0; i < count; i++) 
    {
        if (p(*(numberArray + i), compareNumber)) //通过函数指针调用比较函数
        {
            printf("%d\n", *(numberArray + i));
        }
    }
}

具体使用时代吗如下:

int main() {

    int numberArray[5] = {15, 34, 44, 56, 64};
    int compareNumber = 50;
    // 大于被比较数字情况:
    compareNumberFunction(numberArray, 5, compareNumber, compareGreater);
    // 小于被比较数字情况:
    compareNumberFunction(numberArray, 5, compareNumber, compareLess);

    return 0;
}

所以,函数回调本质为函数指针作为函数参数,函数调用时传入函数地址,这使我们的代码变得更加灵活,可复用性更强。

说了这么多,其实函数指针作为函数参数很简单,我们只要能知道函数指针的类型即可,一般格式如下:

void MyFunction(..., int (*p)(int,int),....)

下面是一些常见的函数指针,注意函数的返回值和参数类型要匹配哦!!!另外注意解引用运算符*上面的括号不能掉啊!!

int (*p)(int,int)   //有参数,有返回值的函数
void (*p)(int,int)  //有参数,无返回值的函数
void (*p)()         //无参数,无返回值的函数
void (*p)(void) 

2.2 借助于函数指针作为参数实现“动态排序”

首先我们应该理解动态这个词,我的理解就是不同时刻,不同场景,发生不同的事,这就是动态。动态排序就是根据不同的排序指标进行排序,不用书写很多重复性的代码,话不多说,直接上案例。

需求: 有30个学生需要排序
按成绩排
按年龄排

这种无法预测的需求变更,就是我们上文说的动态场景,那么解决方案就是函数回调:

//定义一个结构体
typedef struct student
{
    char name[20];
    int age;
    float score;
}Student;

//比较两个学生的年龄
BOOL compareByAge(Student stu1, Student stu2)
 {
    return stu1.age > stu2.age ? YES : NO;
}

//比较两个学生的成绩
BOOL compareByScore(Student stu1, Student stu2) 
{
    return stu1.score > stu2.score ? YES : NO;
}

void sortStudents(Student *array, int n, BOOL(*p)(Student, Student)) 
{
    Student temp;
    int flag = 0;
    for (int i = 0; i < n - 1 && flag == 0; i++) 
    {
        flag = 1;
        for (int j = 0; j < n - i - 1; j++) 
        {
            if (p(array[j], array[j + 1])) 
            {
                temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
                flag = 0;
            }
        }
    }
}

int main() {

    Student stu1 = {"小明", 19, 98};
    Student stu2 = {"小红", 20, 78};
    Student stu3 = {"小白", 21, 88};
    Student stuArray[3] = {stu1, stu2, stu3};
    sortStudents(stuArray, 3, compareByScore);

    return 0;
}

没错,动态排序就是这么简单!

 

三、函数指针作为函数的返回值

函数既然可以作为参数,自然也可以作为返回值。

比如我们有一个函数AFunction,这个函数的参数为一个字符串,即char类型的指针,还有一个函数指针参数接受一个函数作为参数

要返回这样一个函数BFunction,这个函数有一个int类型的返回值,有两个int类型的参数,那指向这个函数的指针定义为如下:

int (*p)(int a,int b)=BFunction;

按照第一节的内容,这个指针的类型应改为:

int (*)(int a,int b)   //这就是BFunction的类型

那我们怎么去定义AFunction呢?

按照我们面向对象的思想,知道了函数的返回类型,我们这样定义是不是就可以了:

int (*)(int, int) AFunction(char *ch,int (*p)(int,int))
{
} 
//前面的 int (*)(int, int) 就是我要返回的函数的指针

然而:这看起来很符合我们的理解,然而,这并不正确,编译器无法识别两个完全并行的包含形参的括号(int, int)和char *ch,int (*p)(int,int),

那到底该怎么做呢?真正的形式其实是这样:

int (*AFunction(char *ch,int (*p)(int,int)))(int, int);  

这种声明从外观上看更像是脸滚键盘出来的结果一团乱糟糟的,现在让我们来逐步的分析一下这个声明的组成步骤:AFunction() 是一个函数

  • (1)AFunction(char *ch,int (*p)(int,int)) 函数接受一个类型为char *的参数和一个函数指针int (*p)(int,int)
  • (2)*AFunction(char *ch,int (*p)(int,int)) 函数返回一个指针,这不就是“指针函数(返回一个指针的函数)”的通用形式吗?这不过这里返回的指针本神又指向一个函数而已,所以类比于通用形式:
int *p(int,int)  //指针函数的通用形式
  • 我们将这里的  *AFunction(char *ch,int (*p)(int,int))  这个整体看成是通用形式里面的  p
  • (3)(*findFunction(char *ch,int (*p)(int,int)))()  这个指针指向一个函数
  • (4)(*findFunction(char *ch,int (*p)(int,int)))(int, int)  指针指向的函数接受两个整形参数
  • (5)int (*findFunction(char *ch,int (*p)(int,int)))(int, int)指针指向的函数返回一个整形

现在我们的分析已经完成了,编译器可以通过了,我们来看一个简单的例子,要实现的功能如下:

函数AFunction同上面不变,他接受的参数是一个BFunction函数,然后根据给AFunction传递的参数信息,选择性的返回这个BFunction函数,如下:

#include <iostream>
#include <stdlib.h>

using namespace std;

//即上面需要返回的BFunction函数,执行加法操作
int add(int a, int b)
{
	return a + b;
}

int (*AFunction(const char * ch, int(*p)(int, int)))(int a,int b) //实际上就是 int (*p)(int,int)
{
	if (ch == "add")  //只有传入“add”的时候才返回加法函数,否则返回null
	{
		return p;
	}
	else
	{
		return NULL;
	}
}


int main()
{
	//返回的类型要与定义的BFunction兼容
	int(*p)(int, int) = AFunction("add", add);
	int result = p(1000, 2000);
	printf("the result is : %d\n", result);

	getchar();
	return 0;
}

我们可能会疑惑,这用一个简单的条件判断,然后直接调用BFunction还不是一样的,何必多此一举,为什么我要以函数去获取函数呢,直接使用BFunction不就好了么,其实在以后的编程过程中,很有可能maxValue和minValue被封装了起来,类的外部是不能直接使用的,那么我们就需要这种方式,如果你学习了Objective-C你会发现,所有的方法调用的实现原理都是如此。

但是,上面的这个定义是在是太过于难看,括号那么多,看的不清楚,有没有简单一些的方法,当然是有的,借助于typedef即可完成。

我们说有下面的关系:

int (*p)(int,int) 实际上等价于 int (*)(int,int)  p

前者是正确的书写,后者是面向对象更直观的展现,方便人看,最然并不能通过编译,我们借助于typedef可以完成这一转变。

typedef int(*FUNC)(int, int); 
//这就相当于自定定义一个 “类型  对象” 的转换
//等价于  int(*)(int, int) 这个类型用FUNC来简短表示 

等价于  int(*)(int, int) 这个类型FUNC来简短表示 ,FUNC在这里就有了类型的含义了,注意理解,这个很重要;

现在我们来重新实现上面的代码,如下:

#include <iostream>
#include <stdlib.h>

using namespace std;
typedef int(*FUNC)(int, int);      //定义一个FUNC代表 int(*)(int, int) 类型

//即上面需要返回的BFunction函数,执行加法操作
int add(int a, int b)
{
	return a + b;
}

FUNC AFunction(const char * ch, int(*p)(int, int)) //这实际上就是 “类型名称 对象名称”,看起来就比较自然了
{
	if (ch == "add")  //只有传入“add”的时候才返回加法函数,否则返回null
	{
		return p;
	}
	else
	{
		return NULL;
	}
}


int main()
{
	//返回的类型要与定义的BFunction兼容,也是“类型名称 对象名称”的表示
	FUNC p = AFunction("add", add);
	int result = p(1000, 2000);
	printf("the result is : %d\n", result);

	getchar();
	return 0;
}


是不是觉得原来C语言也可以这么灵活,也可以这么玩啊。

四、C语言使用typedef 简化指针定义

typedef的作用就是专门给类型名起一个别名的,如下:

typedef int HaHa;
typedef double Hello;

所以我们同样可以给指针类型起一个别名。

(1)变量指针

int a = 100;
typedef int * Pointer; //Pointer就是类型  int *,int *是类型名,Pointer是别名
Pointer p = &a;

(2)常量指针

int const a = 100;
typedef int const * Pointer; //Pointer的类型就是 int const *,int const *是类型名,Pointer是别名
Pointer p = &a;

(3)数组指针(二维的)

int a[][4] = { {1,2,3,4},{5,6,7,8} };
typedef int(*Pointer)[4]; 	 // Pointer等价于类型 int (*)[4],int (*)[4]是类型名,Pointer是别名
Pointer p = a;

(4)函数指针

typedef int (*Pointer)(int,int); 	//Pointer等价于类型 int (*)(int,int),int (*)(int,int)是类型名,Pointer是别名
Pointer p = add; //但是这里由于C语言语法的关系,我们不能写成 int (*)(int,int) Pointer 这样的形式

//函数本身又返回一个指向int的指针
typedef int *(*Pointer)(int,int); 	//Pointer等价于类型 int *(*)(int,int),int *(*)(int,int)是类型名,Pointer是别名
Pointer p = add;

总结:通过typedef我们可以将C语言晦涩难懂的各种指针统一成一样的格式,即

类型  变量

这样的规范格式,方便查看。

 

C语言中的指针函数是指返回值指针类型函数,它们在函数的定义和调用上与普通函数有所不同。 指针函数的定义需要在函数名前加上返回类型指针的声明,并在函数体内返回一个指针类型的值。例如,我们可以定义一个返回整型指针函数如下: ```c int* allocateIntArray(int size) { int* array = (int*)malloc(size * sizeof(int)); return array; } ``` 在调用指针函数时,需要用一个与返回类型相匹配的指针变量接收返回值。例如,调用上述函数并将返回值存储在指针变量中: ```c int* myArray = allocateIntArray(5); ``` 上述代码中,myArray是一个整型指针变量,它用于存储allocateIntArray函数返回值。这个返回值是一个指向分配的整型数组的指针指针函数的调用过程中,我们还需要注意以下几点: 1. 为了避免内存泄漏,我们通常需要在不使用指针的时候手动释放内存,可以使用free函数来释放通过malloc分配的内存。 2. 在函数内部分配的内存空间返回给调用者后,我们应该确保在使用这块内存之前不会被其他操作修改或释放掉,否则可能导致程序运行时错误。 3. 指针函数也可以作为参数传递给其他函数,使得我们可以更方便地使用和操作指针类型的数据。 总结来说,指针函数是具有返回值指针类型函数,其定义和调用方式与普通函数有所区别。使用指针函数可以更灵活地操作和传递指针类型的数据,但在使用时需要注意内存的释放和确保指针的有效性。
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值