指针函数和函数指针 函数指针数组 回调函数

举报
子亦半截诗 发表于 2023/08/09 01:20:37 2023/08/09
【摘要】 1>作者:他们说街角有#include2>知识点:指针函数和函数指针的讲解,函数指针的应用:函数指针数组和回调函数。3>开发环境:vs 2022 前言在我们学习C语言的时候,会遇到什么数组指针,指针数组,函数指针数组,指针函数,函数指针等让我们头疼且十分繁琐的知识点。但是理解了这些知识点的本质,这些内容学习起来其实也没有什么难度。 一. 指针函数指针函数本质是一个函数,只不过这个函数返回的是...

1>作者:他们说街角有#include
2>知识点:指针函数和函数指针的讲解,函数指针的应用:函数指针数组和回调函数。
3>开发环境:vs 2022

前言

在我们学习C语言的时候,会遇到什么数组指针,指针数组,函数指针数组,指针函数,函数指针等让我们头疼且十分繁琐的知识点。但是理解了这些知识点的本质,这些内容学习起来其实也没有什么难度。

一. 指针函数

指针函数本质是一个函数,只不过这个函数返回的是一个指针,和我们常见的函数的定义其实区别并不大。

1.1 指针函数的定义

  • 我们经常使用的函数定义的方式是 (类型名 标识符 (形参列表) {函数体}) 一般形式是(举例):int ret(int x,int y) {}
  • 指针函数的定义其实也并无不同,我们定义的方式也是 (类型名 标识符 (形参列表) {函数体}) 只不过使用的是指针类型。一般形式是(举例):int* ret(int x,int y) {} 或者 int *ret(int x,int y) {} 怎么去写看个人习惯,并无区别。

这么看来其实也是比较简单,和普通函数的定义并没有什么大的区别,也就是多了一个* ,仅此而已。

1.2 指针函数的使用

指针函数在一些特定的场景下会经常被使用到,所以是一个非常重要的知识点。

  • 我们举个例子,比如我们现在要使用malloc(对malloc不清楚的可以看这里:动态内存分配)在内存中开辟一块空间,假如,我们后期要使用realloc进行扩容但为了增加函数的可读性,我们定义一个指针函数去实现它:
int* Main_realloc(int* p, int n)
{
	if (p != NULL)
	{
		int* tmp = (int*)realloc(p, n);
		if (tmp != NULL)
		{
			p = tmp;
		}
		else
		{
			printf(扩容失败);
		}
	}
	else
		{
			printf(扩容失败);
		}
	//************
	//函数体
	//对内存的一些操作
	//***********
	return p;
}
int main()
{
	int* p = (int*)malloc(sizeof(int));
	//*******************
	// 
	//    *函数体*
	// 假设在执行完后发现空间不够
	// 需要扩容并执行一些操作
	// 
	//*******************

	p = Main_realloc(p,100);  //扩容函数

	free(p);
	p = NULL;
	return 0;
}

指针函数在一些算法上的的使用可以极大的降低空间复杂度,但是在使用指针函数的时候有一点需要特别注意:

  • 一定一定不要返回一个局部变量的地址或者函数形参的地址,因为局部变量在函数使用完后便会直接销毁,并不是连同地址中的数据一起抹除,而是交还这部分内存的使用权,所以我们可能会机缘巧合的得出我们想要的结果,但是这部分内存中的数据的使用权被交还了,所以可能会被重新分配,导致被其他的数据占用,就会得不到我们想要的结果。很有可能会因为访问到不可访问的内容而导致程序崩溃等严重的问题。
  • 但是如果我们迫不得已一定要返回一个局部变量的地址,一定要用static关键字去修饰,static可将局部变量的生命周期延长。具体可阅读我之前的文章(static关键字详解

二.函数指针

与指针函数不同的是,函数指针的本质是一个指针,这个指针指向一个函数。函数的定义存在与代码段中,而每个函数在代码段中都有自己的入口地址,函数指针便是指向这个入口地址的指针。

2.1 函数指针的定义和使用

函数指针的声明:函数返回类型 (*标识符) (形参类型1, 形参类型2,···) 如int (*pf)(int, int)
代码示例:

int test()
{
	return 0;
}

int main()
{
 	int (*pTest)(int, int) = &test;
}

另外,关于函数指针,其实函数名就是函数的入口地址,类似于数组名。但函数指针并没有普通指针相加减,加减整数等一些操作。甚至我们用函数指针去调用函数都不需要解引用。
代码示例:


#include <stdio.h>
 
void test1(int a){
	printf("hello bit\n");
}
 
int main(){
    //通过typedef关键字将void(*)(int)类型的函数指针重命名为P简化代码
	typedef void(*P)(int a);
  
	//用P的函数指针类型定义一个p指针变量;
	P p = test1; 
	p(1);  
	(*p)(1);//解引用或者不解引用效果相同
	P p1 = &test1;//是否对函数名取地址不影响,无论取不取,函数名代表的都是函数的入口地址
	p1(1);
	return 0;
}

2.2 函数指针的应用

2.2.1 函数指针数组

数组是一个存储相同类型的存储空间,我们已经学习了指针数组;我们先来回忆一下指针数组的定义,如下:

int * arr[ 10 ];

我们吧n个函数的地址放到一个数组中,那么这个数组就叫函数指针数组,那么函数指针数组应该如何定义呢:(如下)

int (*parr1[10])();   //正确
int *parr2[10]();      //错误
int (*)() parr3[10];   //错误

在定义函数指针数组的时候极其容易出错,我们在定义的时候要让p先和 [] 结合,说明我们定义的是一个数组,而数组的内容是int (*)() 类型的函数指针

应用举例:(计算器)
在我们还不知道函数指针数组的时候我们写的代码可能是这样的:

#include <stdio.h>
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)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
do
{
	printf( "*************************\n" );
	printf( " 1:add 2:sub \n" );
	printf( " 3:mul 4:div \n" );
	printf( "*************************\n" );
	printf( "请选择:" );
	scanf( "%d", &input);
	switch (input)
	{
		case 1:
			printf( "输入操作数:" );
			scanf( "%d %d", &x, &y);
			ret = add(x, y);
			printf( "ret = %d\n", ret);
			break;
		case 2:
			printf( "输入操作数:" );
			scanf( "%d %d", &x, &y);
			ret = sub(x, y);
			printf( "ret = %d\n", ret);
			break;
		case 3:
			printf( "输入操作数:" );
			scanf( "%d %d", &x, &y);
			ret = mul(x, y);
			printf( "ret = %d\n", ret);
			break;
		case 4:
			printf( "输入操作数:" );
			scanf( "%d %d", &x, &y);
			ret = div(x, y);
			printf( "ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			breark;
			default:
			printf( "选择错误\n" );
			break;
		}
	} while (input);
	return 0;
}

然后我们就发先代码又长有繁琐,不容易阅读,显得十分冗余。但是现在我们现在不一样了,我们已经学习了函数指针数组,我们就应该写出下面这样的代码:

#include <stdio.h>
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)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	while (input)
	{
		printf( "*************************\n" );
		printf( " 1:add 2:sub \n" );
		printf( " 3:mul 4:div \n" );
		printf( "*************************\n" );
		printf( "请选择:" );
		scanf( "%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf( "输入操作数:" );
			scanf( "%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf( "输入有误\n" );
		printf( "ret = %d\n", ret);
		}
	return 0;
}

2.2.2 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。比如排序函数 qsort(以后会专门写一篇文章重点讲解) 就是回调函数的典型应用。

我们现在使用回调函数实现一个排序函数(冒泡排序的方式)

#include <stdio.h>
int int_cmp(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)p1 + i);
		*((char*)p1 + i) = *((char*)p2 + i);
		*((char*)p2 + i) = tmp;
	}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < count - 1; i++)
	{
		for (j = 0; j < count - i - 1; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
int main()
{
	int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
	//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
	int i = 0;
	bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

求一件三连,码文不易,你的支持是我最大的动力。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。