C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)

Posted 大家好我叫张同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)相关的知识,希望对你有一定的参考价值。

在这里插入图片描述

本篇文章的内容仍然为指针进阶的相关内容,继续上一篇文章的内容。【C语言进阶学习笔记】二、指针的进阶(1)(重点必看+代码图解+练习)
这是一篇干货满满的文章,希望有所收获~



5、函数指针

我们创建函数的时候,就会在内存中开辟一块空间,既然占用了内存空间,那就有对应的内存空间地址。
函数指针,顾名思义就是指向函数的指针。
在这里插入图片描述

注意:
& 函数名 和 函数名均表示函数的地址!
数组名 != &数组名
函数名 == &函数名


既然函数有地址,那么函数的地址该存放哪里呢?
在这里插入图片描述


思考:函数指针如何使用呢?
在这里插入图片描述

通过函数指针,我们可以找到函数,然后去调用这个函数。 函数指针是 &
函数名,而我们函数调用的时候可以直接使用函数名,那么这里通过函数指针调用函数也可以这样写:

在这里插入图片描述

既然这个地方的 * 可以省略,那么我们在使用的时候 * 可以用多个,也可以不要, * 号在这里就是一个摆设,这个地方放 * 是为了方便理解+
学习指针。


阅读两个有意思的代码:

//代码1
(*(void (*) ())0)();
//代码2
void(*signal(int, void(*)(int)))(int);

这两个代码均是书籍《C陷阱与缺陷》中提及的内容,推荐阅读这本书。

这两个代码怎么阅读和理解呢?

//代码1 
(*(void (*) ())0)()

1.先将 void () ()理解清楚,这个是一个函数指针,指针指向的函数返回类型是void的。
2.再理解(void (
) ())0 我们之前的学习中学到过强制类型转换,需要将强制转换之后的类型用括号()括起来,这个地方就是将 0 强制类型转换成void() ()类型。 为什么要将0强制类型转换成void() ()类型呢? 原因:想要将0当做某个函数的地址
深入扩展:如果一个数字想要当作一个地址,直接使用这个数字肯定是不行的,而是要将这个数字转换成也给地址编号的类型。这也是代码1中为什么要将0强制类型转换的原因。
3.接着再看((void () ())0),对一个指针加*, 就是对其进行解引用操作,对函数指针解引用就是找到这个函数
4.((void () ())0)() ,((void () ())0)找到函数后,对其使用(), 就是调用函数,所以((void () ())0)(); 是一个函数调用。 整体理解下来就是:将0强制类型转换成一个函数指针void(*)
(),再通过对这个函数指针进行解引用操作,找到这个函数,对其进行调用!


//代码2
void(*signal(int, void(*)(int)))(int);

1.signal是一个函数声明
2.signal函数的参数有两个,第一个是int类型,第二个是函数指针,该函数指针指向的函数的参数是int类型,返回类型是void(void()(int))
3.signal的返回类型是一个函数指针,该函数指针指向的函数的参数是int类型,返回类型是void(void(
)(int))

这种形式看起来就比较复杂和难以理解,我们可以用typedef类型重定义对其进行简化:

在之前的学习中,我们使用过typedef来定义过无符号整型 typedef unsigned int u_int;

但是我们并没有学过指针类型如何进行类型重定义,比如说 void()(int) ,如果我们要将其进行重定义,可以写成:
**typedef void(
)(int) pfun_t; 这种形式吗?**
在这里插入图片描述
我们尝试将其放到编译器下,就会发现编译器报错,显然这种方式是行不通的!


思考:那么可以将这个类型重定义后的名称类似与定义函数指针一样放到(* )里面吗?也就是typedef void(*pfun_t)(int);

在这里插入图片描述
写成这种形式后,编译器没有在报错或者警告,说明这种方式是对的,实际上正确的书写方式也正是这样!


//代码2
void(*signal(int, void(*)(int)))(int);
//那么这个类型可以简化成:
typedef  void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

虽然将一行代码变成了两行,但是简化后的代码更便于阅读和理解。
typedef 在进行类型重定义的时候,如果是函数指针类型,那么名称需要放到* 旁边,也就是说不能写成这种形式:typedef void(*)(int) pfun_t;
正确的形式是:typedef void(*pfun_t)(int);

深入扩展:当函数的返回类型是一个函数指针的时候,函数名需要放到函数返回类型-- - 函数指针内部,而不是直接放到返回类型— 函数指针后面。
void(signal(int, void()(int)))(int);


6、函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,比如︰

int* arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢 ?

int (*parr1[10])();
int* parr2[10]();
int (*)() parr3[10];

答案是:parr1 parr1先和[结合,说明parr1是数组。
数组的内容是什么呢 ?
是int(*)()类型的函数指针。


#include<stdio.h>
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;
}
int main()
{
	//指针数组
	//int* arr[5];
	//需要一个数组,这个数组可以存放4个函数的地址---函数指针的数组
	int (*pa)(int, int) = Add;
	int (*p[4])(int, int) = { Add,Sub,Mul,Div };//函数指针的数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\\n", (*p[i])(2, 3));
	}
	return 0;
}

在这里插入图片描述


练习:

char* my_strcpy(char* dest, const char* src);

练习要求:

1.写一个函数指针pf,能够指向my——strcpy
2.写一个函数指针数组 pfArr,能够存放4个my_strcpy函数的地址

答案:

1.char* (pf)(char, const char*)
2.char* (pfArr[4])(char, const char*)


函数指针数组的用途:

转移表—《C和指针》这本书提及

请看下面简易计算器的例子:

#include<stdio.h>
void cal_menu()
{
	printf("*****************************\\n");
	printf("*****   1.Add     2.Sub  ****\\n");
	printf("*****   3.Mul     4.Div  ****\\n");
	printf("******    0.exit     ********\\n");
	printf("*****************************\\n");
}
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;
}
int main()
{
	int input;//根据菜单提示输入操作符
	int x = 0;
	int y = 0;
	do
	{
		cal_menu();
		printf("请选择操作符:>>\\n");
		scanf("%d", &input);
		printf("请输入两个操作数:>>\\n");
		scanf("%d%d", &x, &y);
		switch (input)
		{
		case 1:
			printf("%d\\n", Add(x, y));
			break;
		case 2:
			printf("%d\\n", Sub(x, y));
			break;
		case 3:
			printf("%d\\n", Mul(x, y));
			break;
		case 4:
			printf("%d\\n", Div(x, y));
			break;
		case 0:
			printf("退出!\\n");
			break;
		default:
			printf("选择错误!\\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述

这里我们用函数调用的方式实现两个数之间的加、减、乘、除操作,但是当我们想要退出程序的时候,选择0并不能之间退出游戏,还需要进行输入两个操作数,这种方式明显不符合我们的预期,对其稍微进行改造以下:


#include<stdio.h>
void cal_menu()
{
	printf("*****************************\\n");
	printf("*****   1.Add     2.Sub  ****\\n");
	printf("*****   3.Mul     4.Div  ****\\n");
	printf("******    0.exit     ********\\n");
	printf("*****************************\\n");
}
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;
}
int main()
{
	int input;//根据菜单提示输入操作符
	int x = 0;
	int y = 0;
	do
	{
		cal_menu();
		printf("请选择操作符:>>\\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>>\\n");
			scanf("%d%d", &x, &y);
			printf("%d\\n", Add(x, y));
			break;
		case 2:
			printf("请输入两个操作数:>>\\n");
			scanf("%d%d", &x, &y);
			printf("%d\\n", Sub(x, y));
			break;
		case 3:
			printf("请输入两个操作数:>>\\n");
			scanf("%d%d", &x, &y);
			printf("%d\\n", Mul(x, y));
			break;
		case 4:
			printf("请输入两个操作数:>>\\n");
			scanf("%d%d", &x, &y);
			printf("%d\\n", Div(x, y));
			break;
		case 0:
			printf("退出!\\n");
			break;
		default:
			printf("选择错误!\\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述

这种改造方式虽然符合我们的预期效果,但是switch语句内部的代码太过冗长,而且后续如果我们想要添加新的功能时,switch语句内部还会继续不断变长,这种方式显然不够简洁和具备良好的扩展能力。


方法一:

下面我们通过函数指针数组的方式来实现吧!

#include<stdio.h>
void cal_menu()
{
	printf("*****************************\\n");
	printf("*****   1.Add     2.Sub  ****\\n");
	printf("*****   3.Mul     4.Div  ****\\n");
	printf("******    0.exit     ********\\n");
	printf("*****************************\\n");
}
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;
}
int main()
{
	int input;//根据菜单提示输入操作符
	int x = 0;
	int y = 0;
	int(*pArr[])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		cal_menu();
		printf("请选择操作符:>>\\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		case 2:
		case 3:
		case 4:
			printf("请输入两个操作数:>>\\n");
			scanf("%d%d", &x, &y);
			printf("%d\\n", (*pArr[input])(x, y));
			break;
		case 0:
			printf("退出!\\n");
			break;
		default:
			printf("选择错误!\\n");
			break;
		}

	} while (input);
	return 0;
}

在这里插入图片描述

利用函数指针数组的方式,不仅可以简化我们的代码,也可以方面后面的扩展,比如说后面我们添加一个取模功能或一个异或功能,整个代码仅仅需要进行细微的调整即可满足我们的要求:


#include<stdio.h>
void cal_menu()
{
	printf("*****************************\\n");
	printf("*****   1.Add     2.Sub  ****\\n");
	printf("*****   3.Mul     4.Div  ****\\n");
	printf("*****   5.Mod     6.Xor  ****\\n");
	printf("******    0.exit     ********\\n");
	printf("*****************************\\n");
}
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;
}
int Mod(int x, int y)
{
	return x % y;
}
int Xor(int x, int y)
{
	return x ^ y;
}
int main()
{
	int input;//根据菜单提示输入操作符
	int x = 0;
	int y = 0;
	int(*pArr[])(int, int) = { 0,Add,Sub,Mul,Div,Mod,Xor };
	do
	{
		cal_menu();
		printf("请选择操作符:>>\\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
		case 6:
			printf("请输入两个操作数:>>\\n");
			scanf("%d%d", &x, &y);
			printf("%d\\n", (*pArr[input])(x, y));
			break;
		case 0:
			printf("退出!\\n");
			break;
		default:
			printf("选择错误!\\n");
			break;
		}

	} while (input);
	return 0;
}

在这里插入图片描述

这个地方我们仅仅调整了打印的菜单栏,函数指针数组初始化的值以及switch语句中加上case 5,case6。即可满足我们的要求,后续如果进行更多的功能扩展也可以按照这种方式,非常简单。


方法二:

通过函数指针来实现

printf("请输入两个操作数:>>\\n");
scanf("%d%d"C语言进阶学习笔记二指针的进阶(练习篇)

C语言基础学习笔记六初始指针(重点必看)(详细讲解+代码举例+练习巩固)

C语言学习笔记(12)指针进阶

豆瓣9.0,百万程序员的宝藏书,C语言进阶必看

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针