c指针进阶

一.一级指针传参

void print(int* ptr, int sz)
{
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%d", *(ptr + i));
	}
}
void test(char* p)
{

}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//p是一级指针
	print(p, sz);
	
	char ch = 'w';
	test(&ch);//char*
	char* p1 = &ch;
	test(p1);
	return 0;
}

思考:

1.当我们自己设计函数的时候,一级指针传参,一级指针接受

2.当一个函数的参数部分为一级指针的时候,函数能接收一级指针和地址

二.二级指针传参

void test(int* *p2)
{
	**p2 = 20;
}
int main()
{
	int a = 10;
	int* pa = &a;//pa是一级指针
	int** ppa = &pa;//ppa是二级指针
	//把二级指针进行传参
	test(ppa);
	test(&pa);//传一级指针变量的地址
	int* arr[10] = { 0 };
	test(arr);//传存放一级指针的数组
	printf("%d\n", a);
	return 0;
}

思考:

1.当我们自己设计函数的时候,二级指针传参,二级指针接收

2.当一个函数的参数部分为一级指针的时候,函数能接收二级指针,一级指针变量的地址,存放一级指针的数组

三.函数指针

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int* pa = &a;

	char ch = 'w';
	char* pc = &ch;

	int arr[10] = { 0 };
	int(* parr)[10] = &arr;//取出数组的地址
	//parr是指向数组的指针-存放的是数组的地址

	//函数指针-存放函数地址的指针
	//&函数名-取到的就是函数的地址
	int(* pf)(int,int) = &Add;
	//pf就是函数指针变量
	printf("%p", &Add);
	printf("%p", Add);
	//结果一模一样 都是CF00007FF6D39213CF
	//&函数名=函数名

	return 0;
}

首先从名字上看,整型指针是指向整型的指针,字符指针是指向字符的指针,函数指针当然是存放函数的指针啦。

    int a = 10;
    int* pa = &a;

整型指针,将整型变量的地址,赋值给*pa,前面有*证明pa是指针,变量是整型,所以加上类型int

    char ch = 'w';
    char* pc = &ch;

字符指针,将字符变量的地址,赋值给*pc,前面有*证明pc是指针,变量是字符型,所以加上类型char

  int arr[10] = { 0 };
  int(* parr)[10] = &arr;

数组指针,将数组的地址,也就是取数组名的地址,赋值给*parr,数组类型是int[10],所以要加上类型,为了防止parr先和[]结合要加上括号

通过上述三个指针咱们也能推演出函数指针:

int Add(int x, int y)
{
    return x + y;
}

这个是函数Add,也是我们函数指针要指向的函数

首先我们进行了打印Add的地址,和直接打印函数名Add

    printf("%p", &Add);
    printf("%p", Add);

结果是一样的,证明  函数名==&函数名

int(* pf)(int,int) = &Add;

函数指针,取函数Add的地址,赋值给*pf,前面有*证明pc是指针,函数的返回类型是int,参数类型是int,int,所以将int(int,int)加上,为防止pf()结合,要加上括号

void test(char* str)
{

}
int main()
{
	//?= &test;

	return 0;
}

现在练习一下,将函数test的函数指针写出来,答案在下面

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

答案是:void(* pt)(char*) = &test

你做对了吗

接下来咱们继续对函数指针进行讨论

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//int(*pf)(int, int) = &Add;
	int(*pf)(int, int) = Add;//Add==pf
	//int ret=(*pf)(3, 5);//1
	//int ret = Add(3, 5);//2
	int ret = pf(3, 5);//3

	printf("%d\n", ret);
	return 0;
}

这段代码里面实现了通过函数指针调用函数的三种方法

第一种:

int ret=(*pf)(3, 5)

函数指针是pf,咱们解引用他,*pf,后面再加上要传的参数(3,5),为防止pf先和()结合,所以要用括号把*pf括住,在用整型ret接收返回值

第二种:

int ret = Add(3, 5)

因为函数名==&函数名,所以函数指针可以写为int(*pf)(int, int) = Add,这句代码就可以看出,pf==Add,指针变量里面存的就是Add,所以解引用可以变成Add

第三种:

int ret = pf(3, 5)

因为pf=Add,所以Add可以替换成pf

综上三种,其实(*pf)(3, 5)里面的*本身是没有用的,只是为了好理解,但是只有函数指针是这样子

,其他指针没有这种情况

接下来咱们看两个有趣的问题

(*(void(*)())0)() 和void(*signal(int, void(*)(int)))(int)

下面是简单的理解,具体的请去看(1条消息) 学习函数指针时遇到的两个问题_Любовь всей жизни50的博客-CSDN博客

(*(void(*)())0)()
分析
0先与前面的括号结合,()0,强制类型转换为(void(*)())
void(*)()的地址是0,void(*)()是函数指针类型
前面加*(void(*)()),解引用,就是把这个函数拿出来(函数)()就是调用这个函数

void(*signal(int,void(*)(int)))(int)
signal是函数名,后面括号里的int,void(*)(int)是参数,void(*)(int)是函数指针指向,返回类型为void,参数为int的函数
对于函数还有一个函数返回类型,将signal(int,void(*)(int)去掉,void(*)(int),是一个函数指针类型
signal的返回类型也是一个函数指针,指向一个参数为int,返回类型为void的函数
signal就是一个函数声明

四.函数指针数组

什么是函数指针数组呢?整型数组,是存放整型的数组,函数指针数组当然是存放同类型函数指针的数组啦

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int(*pf1)(int, int) = Add;
	int(*pf2)(int, int) = Sub;
	int(*pfArr[2])(int, int)={Add,Sub};//函数指针数组  
	return 0;
}

如上述代码所示,pfArr就是函数指针数组啦

那么这个函数指针数组有什么应用呢

---------------------------------------------------------------------------------------------------------------------------------

有一个题目,写一个代码,实现计算器-+-*/

代码如下

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x - y;
}
int Div(int x, int y)
{
	return x - y;
}
void menu()
{
	printf("*************************\n");
	printf("*******1.add  2.sub******\n");
	printf("*******3.mul  4.div******\n");
	printf("*******   0.exit   ******\n");
}
int main()
{
	int input = 0;
	do {
		menu();
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数>:\n");
			scanf("%d %d", &x, &y);
			ret=Add(x, y);
			break;
		case 2:
			printf("请输入两个操作数>:\n");
			scanf("%d %d", &x, &y);
			ret=Sub(x, y);
			break;
		case 3:
			printf("请输入两个操作数>:\n");
			scanf("%d %d", &x, &y);
			ret=Mul(x, y);
			break;
		case 4:
			printf("请输入两个操作数>:\n");
			scanf("%d %d", &x, &y);
			ret=Div(x, y);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
		printf("ret=%d\n", ret);
	} while (input);
	return 0;
}

这个代码很容易看懂,但是太过于冗余,而且有很多重复代码,那么咱们有没有办法把它简化一下呢?

当然有,并且咱们要用到函数指针数组的知识

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x - y;
}
int Div(int x, int y)
{
	return x - y;
}
void menu()
{
	printf("*************************\n");
	printf("*******1.add  2.sub******\n");
	printf("*******3.mul  4.div******\n");
	printf("*******   0.exit   ******\n");
}
int main()
{
	int input = 0;
	do {
		menu();
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		int (*pfArr[5])(int, int) = {NULL,Add,Sub,Mul,Div };
		if (input > 0 && input < 5)
		{
			printf("请输入两个操作数>:\n");
			scanf("%d %d", &x, &y);
			ret=pfArr[input](x, y);
			printf("ret=%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出程序\n");
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	} while (input);
	return 0;
}

 首先,咱们肯定要使用函数指针数组,原本程序(最开始的那个代码)里面的+-*/四个函数,类型相同所以可以存在函数指针数组

int (*pfArr[ ])(int, int) = {Add,Sub,Mul,Div }; 这就是一个函数指针数组啦,但是我们还没有把[]里面的数字填上,按道理应该填4呀,一共四个函数嘛,可是咱们看函数menu,1是选择add,2是sub,3是mul,4是div,每个选择的数字都比下标多1呀,那怎么办呢

咱们在Add之前,再加一个函数指针,加一个NULL空指针

int (*pfArr[5])(int, int) = {NULL,Add,Sub,Mul,Div };这样就很妙啦

pfArr[input](x, y)就可以调用咱们需要的函数

再通过if else语句就可以实现计算器啦,不需要使用switch语句

这个函数指针数组起到了一个跳板的作用,我们经常把这样一个数组称为转移表----《C和指针》

五.指向函数指针数组的指针

函数指针数组是数组,数组就有地址,那么就可以&函数指针数组

那么这个指针该怎么表示呢

整型数组,int arr[5]   -----------   int(*p1)[5]=&arr

整型指针的数组,int* arr[5]  -----------   int* (*p2)[5]=&arr

所以      int(*p)(int,int)   ------------   int(* p3[4])(int,int)   --------------   int(* (*p4)[4])(int,int)=&p3  

六.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于该事件或条件进行响应。

这是官方的概念,比如有一个A函数和一个B函数,把A函数的地址传递给B函数,A函数的形参部分就是A函数的指针,我们进入B函数时,B函数通过A函数的指针调用A函数。

接下来我们通过改造一下,最开始的计算器代码,来解释一下:

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x - y;
}
int Div(int x, int y)
{
	return x - y;
}
void menu()
{
	printf("*************************\n");
	printf("*******1.add  2.sub******\n");
	printf("*******3.mul  4.div******\n");
	printf("*******   0.exit   ******\n");
}
int Calc(int(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数>:");
	scanf("%d %d", &x, &y);
	return pf(x, y);
}
int main()
{
	int input = 0;
	do {
		menu();
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Calc(Add);
			ret = Calc(Add);
			break;
		case 2:
			Calc(Sub);
			ret = Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			ret = Calc(Mul);
			break;
		case 4:
			Calc(Div);
			ret = Calc(Div);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
		printf("ret=%d\n", ret);
	} while (input);
	return 0;
}

如上述代码,咱们没有直接调用Add等函数而是通过函数Calc来间接使用,这里的参数我们用的时函数指针,通过函数指针调用函数。

七.qsort函数

(2条消息) 对库函数qsort的理解_Любовь всей жизни50的博客-CSDN博客

这个前几天写的一个博客里面就有写到,感兴趣的同志们可以去看一下 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值