C语言函数指针的老生常谈

函数指针

本质上是一个指针,只不过指向函数而已。

编译器在编译期间对函数开辟了一块空间,而这快空间的开始地址,就是它的函数指针 。

下面我们也直接用最直观的程序来了解函数指针:

#if 1
void func()
{
	printf("hello ptr!");
}
int main()
{
	void (*p)();
	p = func;
	p();
}
#endif

在这里我们给出了func()函数,并在其中打印hello ptr! 。在这里,我们定义了一个函数指针p,大家会发现他的定义语句十分的奇特: void (*p)() ,其实与int a,float b……十分的类似,定义嘛,实际上也就是给出类型,给出变量名,在这里void (*p)() 给出的类型是指向返回值为void 无参数传入的函数 ,在这里我们把这个指针命名为p。

这条程序中,我们直接将与p类型相对应的函数func()的地址func赋值给p,于是我们便能拿着p去做func的事情了。

C语言函数指针的老生常谈_第1张图片

换言之,如果我们需要定义一个指向返回值为double,有一个int型参数a,一个long型参数b,名称为f_ptr的函数指针呢?

那么显然便是: double (*f_ptr)(int a,long b)

但其实因为我们只需要告诉编译器我们的函数指针指向的是一个什么类型的函数,所以参数名并不是必须的只需告诉他是什么类型即可,所以我们还可以简写为 double (*f_ptr)(int , long)

我们还要注意到一个小细节:

标准规定:函数名,可以认为是其开始地址

所以函数指针p获取函数地址: p = &Max; == p = Max;

函数指针p怎么调用: (*p)(10,20); == p(10,20);

我们同样能够拿上述程序来做个实验:

C语言函数指针的老生常谈_第2张图片

C语言函数指针的老生常谈_第3张图片

函数指针的应用

函数指针在我们程序中最常见的应用其实应该是作为函数的参数。比如,我们可以这样:

int Add(int a, int b)  //加法实现
{
	return a + b;
}
int Sub(int a, int b)  //减法实现
{
	return a - b;
}
int Mul(int a, int b)  //乘法实现
{
	return a * b;
}
int Div(int a, int b)  //除法实现
{
	if (b == 0)
		return -1;
	return a / b;
}
int Computer(int a, int b, int(*p)(int, int))   //模板函数
{
	return p(a, b);
}
int main()
{
	printf("a+b=%d\n", Computer(10, 20, Add));
	printf("a-b=%d\n", Computer(10, 20, Sub));
	printf("a*b=%d\n", Computer(10, 20, Mul));
	printf("a/b=%d\n", Computer(10, 20, Div));
	return 0;
}

在这里,我们做出了一个模板函数computer,在他的参数列表中,我们给出了int (*p)(int,int)的参数,我们调用函数的时候需要给到这个函数一个函数指针,而我们也在模板函数中使用了函数指针来调用具体函数。

我们定义了Add、Sub、Mul、Div四个具体的实现函数,用来实现相应的加减乘除,而computer只需要接受具体的函数指针来决定他需要干什么,这样为程序的可拓展性提供了非常大的帮助,我们不需要在想要增删功能的时候修改主要代码,仅需增删具体的功能函数就行,甚至部分库函数利用函数指针作为参数的优点来为我们提供可自定义的功能,下面也用一个例子来解释。

函数指针作为参数实例(qsort函数)

qsort函数包含在库中

qsort函数的原型描述为:

void qsort( void *base, size_t n_elements, size_t el_sizeint ,int (*compar) (void const * , void const * ) )

qsort函数其实是C语言为我们编写好的一个快速排序函数,他通过函数指针为我们开放了一定的自定义功能

各参数解析,base中传入需要排序的数组,n_elements为该数组中元素的个数,el_sizeint为数组各元素的大小,而compar,则是我们今天反复提及的函数指针,在这里可以理解成为数组内各元素的比较规则。

干说可能很难理解,直接上程序:

int compare_int(const void* a,const void* b)  //用于比较两个整型值的具体函数
{
	//将函数传入的参数进行强转
	int _a = *(const int*)a;
	int _b = *(const int*)b;
	//不相等
	if (_a > _b)
		return 1;
	else if (_a < _b)
		return -1;
	//相等
	else
		return 0;
}
int main()
{
	int arr[] = { 1,3,5,7,2,4,6,7,10,9,8 };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), compare_int);
	for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
		printf("%d ", arr[i]);
	return 0;
}

此程序中,我们定义了自己的比较规则compare_int函数,传入到qsort函数的最后一个参数,其余参数正常传入,排序成功!

(qsort函数的比较规则默认返回1则第一个参数大于第二个参数,返回-1则反之,返回0则传入的两个参数相等)

C语言函数指针的老生常谈_第4张图片

在这里强转是必须的,因为默认传入的是两个const void*类型,这在程序中是无法进行比较的。

同时,参数的类型也必须都写为const void* 因为在qsort函数声明中明确表示传入的函数指针应为返回值为int,两个参数都为const void*类型,自定义比较规则函数的参数类型若不为const void*则会造成函数类型不匹配的问题。

我们在这里也不妨想想一个字符串的指针数组应该如何写出我们的自定义函数来排序呢?

int compare_string(const void* _str1, const void* _str2)
{
	//强转类型
	const char* str1 = *(const char**)_str1;
	const char* str2 = *(const char**)_str2;
	//利用string.h库中stramp函数(返回值契合qsort函数比较规则)来进行比较
	int tem = strcmp(str1, str2);
	return tem;
}
int main()
{
	const char* arr[] = { "abc","dsa","adfw","odc","adsfa","afsd" };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(const char*), compare_string);
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]);i++)
		printf("%s ", arr[i]);
	return 0;
}

可能在这大家疑惑的地方在于为什么需要使用一个二级指针来进行强转,其实在这里可以用我们平时的指针参数传入来理解,比如void func(int* a),我们传入给这个带*参数时实际上传入的是a的地址。

同理我们传入_str的是什么?我们原数组是一个指针数组,每个元素本质上是个一级指针,那么我们每次比较时传入给compare_string函数一个带*的参数,是不是那便是一级指针的地址,即二级指针,加上一个解引用的*变回一级指针便可以赋给str1了。

C语言函数指针的老生常谈_第5张图片

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

你可能感兴趣的:(C语言函数指针的老生常谈)