指针进阶之函数指针和函数指针数组

news2024/12/23 4:12:18

文章目录

  • 一、函数指针
    • 1.简单介绍
    • 2.回忆函数
    • 3.函数地址
    • 4.函数指针
    • 5.案例
      • (1)案例一
      • (2)案例二
      • (3)案例三
      • (4)案例四
        • 代码1
        • 代码2
        • 误区
    • 6.补充
  • 二、函数指针数组
    • 1.定义
    • 2.补充
    • 3.案例
      • (1)案例一
      • (2)案例二
    • 4.转移表(计算器实例)
      • (1)一般写法
      • (2)改进
  • 三、指向函数指针数组的指针
    • 定义

一、函数指针

1.简单介绍

一个函数如何设计,才能合理地接收参数?

以前学习过,数组指针 就是 指向数组的指针

那么,函数指针 就是 指向函数的指针

2.回忆函数

回忆一下我们之前说的函数,写一个加法函数:

int Add(int x,int y){
    int z=0;
    z=x+y;
    return z;
}
int main(){
    int a=10;
    int b=20;
    printf("%d\n",Add(a,b));
    return 0;
}

输出结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ubosPukU-1673084595438)(D:\Typora图片\clip_image002-16728185045131.jpg)]

3.函数地址

既然函数指针是用来存放函数地址的,函数也有地址吗?

我们来打印一下:

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

具体代码如下:

int Add(int x,int y){
    int z=0;
    z=x+y;
    return z;
}
int main(){
    int a=10;
    int b=20;
    printf("%p\n",&Add);
    return 0;
}

输出看一下,果然输出了一个地址。

这个地址,就是函数Add的地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EmJUbmaq-1673084595440)(D:\Typora图片\clip_image003-16728185045142.png)]


我们之前说数组的时候:

int arr[10] = { 0 };

&arrarr拿到的结果是一样的。

那么在函数中是否也存在一样的道理?

我们来打印一下:

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

看一下输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IN3RfxvH-1673084595440)(D:\Typora图片\clip_image005.jpg)]

可以看到结果是一样的。

Add是函数首元素地址吗?

胡扯!哪儿有函数首元素啊!函数只有一个,数组才有首元素的概念。

在函数里面,Add&Add是一样的。

函数名&函数名都是函数的地址。

4.函数指针

当我们真的能够拿到函数地址的时候,函数地址存哪儿呢?

还是拿上面的代码举例。

要将函数名Add存起来,就需要一个指针变量pa。如下:

pa=Add;

这个指针变量pa的类型是什么呢?

在之前,我们要存数组的地址。假设要存入指针p,p的类型怎么写?

int arr[10] = { 0 };

P是一个指针,要指向数组,(*p)表示p为指针,[ ]表示指向数组,几个元素呢?10个,即:[10]。

数组每个元素的类型是什么呢?我们指向的数组每个元素是int类型。所以这样写:int (*p)[10]=&arr;


🌵 那么对于函数指针的写法:

①第一种写法

这样写行吗?

int *pa(int,int)=Add;	//不行,这里的pa就相当于一个函数名,有两个参数int,返回类型是int*

pa首先和后面的()结合,说明pa是个函数的函数名。

后面的两个int是参数类型,前面的int*是返回类型。这是不对的!!!

我们希望pa是个指针,指针才能存放地址。

②第二种写法

这种才是正确写法:

int(*pa)(int,int)=Add;

pa首先和*结合,才能保证它是一个指针。

后面的圆括号表示指向的是函数,函数的参数是两个整型。

后边圆括号里面,写int x与int yint与int一样。x和y可以写可以不写,只需要把函数的参数类型(这里是int)交代清楚即可。

指向的函数的返回类型是什么?是int类型。所以最前面写上int即可。

5.案例

(1)案例一

上面我们已经会书写函数指针了:

int(*pa)(int,int)=Add;

这里的pa究竟是不是函数指针呢?里面是否存放的是函数的地址?

pa里面如果存的是函数的地址,那*pa就可以找到那个函数。

找到那个函数后,我们就可以调用那个函数。

调用函数要传参,这里我们把2和3传进去,如下:

(*pa)(2, 3)

再打印一下即可。

整体代码如下:

int Add(int x,int y){
    int z=0;
    z=x+y;
    return z;
}
int main(){
    int a=10;
    int b=20;
    int(*pa)(int,int)=Add;	//将Add函数名赋值给pa指针
    printf("%d\n",(*pa)(2, 3));	//*pa找到函数
    return 0;
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0tU8s3H-1673084595443)(D:\Typora图片\clip_image009-16728185045144.jpg)]

(2)案例二

函数指针是指向函数的指针,是存放函数地址的一个指针。

不同函数的地址存起来,对应函数指针的定义方式应该也不相同。

我们来举个例子:

void Print(char* str) {
 	printf("%s\n", str);	//把str指向的字符串打印出来
}
int main() {
  	void(*p)(char*) = Print;	//Print函数名就是地址,存进指针变量p里面
  	(*p)("hello");	//p里面存的是函数的地址,解引用找到该函数
  	//调用函数,将字符串传给字符指针
 	return 0;
}

void(*p)(char*) = Print;这行代码的意思:

(*p)表示p是指针,后面圆括号表示指向的是函数,char*表示指向函数的参数类型,返回类型是void。

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VUSt18sU-1673084595444)(D:\Typora图片\clip_image011.jpg)]

(3)案例三

再来看一段代码,巩固一下。

如下:

void test() {
 	printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void* pfun2();

能存储函数的地址,就要求pfun1或者pfun2是指针,那哪个是指针?

①pfun1先与*结合,说明它是指针,指针可以存放地址,没有问题。指向函数的参数是无参,返回类型是void。

②pfun2首先和()结合,说明它是个函数,函数参数是无,返回类型是void。pfun2不是指针,是个函数名而已。

所以pfun1有能力存放test函数的地址。

(4)案例四

我们再来看两段有趣的代码:

代码1

  (*(void(*)())0) ();

🚗 分析:

先来看一下它的括号如何配对。

经过分析,应该是以下的配对:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZQBIXpH-1673084595444)(D:\Typora图片\image-20230105211837509.png)]

<1> 红色括号中间void(*)()*代表指针类型。指针指向后边的蓝色括号部分,说明它是函数,返回类型是void。

那么这个void(*)()部分,就是函数指针类型

将这个类型放进红色括号里面,叫强制类型转换。(一个括号里面放一个类型,叫强制类型转换)

<2> 这个强制类型转换,放在了0前面,说明是把0进行强制类型转换。

原来的0是整数类型,现在将它强制类型转换成函数指针类型。意味着想要把0当成某函数的地址

<3> 将0强制转换成函数的地址之后,前面有一个解引用(就是最前面的*号)。

解引用之后,就找到了这个函数。

这个函数是无参的,返回类型是void—>void(*)()

<4> 接下来去调用这个函数(最后的橙色括号就是调用函数),没有传参。因为指向的函数是无参的。

🌵总结

总而言之,这行代码就是在调用函数,调用0地址处,参数为无参,返回类型是void的函数。

把0强制类型转换为void(*)()函数指针类型,0就是一个函数的地址。然后解引用,调用0地址处的该函数。


代码2

void(*signal(int,void(*)(int)))(int);

🚗 分析:

先来看一下它的括号如何配对。

经过分析,应该是以下的配对:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KW7v3PgX-1673084595445)(D:\Typora图片\image-20230105215006239.png)]

<1> 首先看到了signal,是个名字,后边一对圆括号,说明是一个函数。

函数有两个参数,第一个是int整数类型

第二个是void(*)(int)*表示它是指针,指向函数的参数类型是int,返回类型是void,是一个函数指针类型

<2> 经过上面的分析,这个signal函数有两个参数,第一个是整型,第二个是函数指针类型。

那么,函数名确定了,参数也确定了。

<3> 函数的返回类型是什么呢?

之前接触过函数:int Add(int,int):函数名叫Add,参数有两个,都是int类型。去掉函数名和参数,剩下的就是函数的返回类型。所以Add函数的返回类型是int。

之前我们看到这样的代码也是类似:

1int(*p)[10]=&arr;

//P是指针变量,指向的是一个数组,数组元素是int。我们把p去掉的话,剩下的就是它的类型。P是指向数组的指针类型。2void (*p)(char*)=Print;

//把p变量去掉,剩下的就是类型。是指针,指向的是函数。

再回到signal函数,将函数名(signal)和参数(int,void(*)(int))去掉,剩下的void(* )(int)就是函数返回类型。

<4> 对于void(* )(int)*表示它是指针(*号在括号里面,所以先于*结合),指向的是一个函数(因为(*)后边有括号),参数是int类型(因为后边括号里面是int),返回类型是void

所以函数返回类型是函数指针类型

🌵总结

总而言之,这行代码就是在函数声明。(告诉我们,函数名、参数、返回类型分别是什么)

signal是函数名,有两个参数(整型和函数指针类型),函数的返回类型是函数指针类型。


误区

🙅注意

对于第二个代码,有的同学可能这样书写。

比如现在让大家写一个signal函数,有两个参数,分别是int类型和void(*)(int)类型,函数返回类型是void(*)(int)类型。

可能有的同学会这样写signal函数:

void(*)(int) signal(int,void(*)(int))

这种写法是不对的!

函数指针的返回类型不能就这样写在前面。

必须要按照规范,*号要靠近函数名,后半部分(即(int))要放在最后面。如下:

void(*signal(int,void(*)(int)))(int);

这种正确写法不容易理解。


👹精简

那能不能精简一些呢?

以前学过一个关键字typedef,这个关键字可以让某些类型简单一些。如下:

typedef unsigned int uint;	// unsigned int是类型,uint是新取的名字

我们现在觉得signal函数的参数类型(void(*)(int))有一点复杂,可以给它取一个新名字pfun_t

注意,这样写是不对的:(上面已经说明了这个错误)

typedef void(*)(int) pfun_t;

应该这样写:(将名字靠近*号)

typedef void(*pfun_t)(int);

现在的pfun_t就是函数指针类型了。

signal返回的是函数指针类型,现在重新命名为了pfun_t,那么signal函数就可以这样精简。

如下:

pfun_t signal(int,pfun_t);

综上,这一行代码可以精简为两行代码:

//第一种写法
void(*signal(int,void(*)(int)))(int);

//第二种写法
typedef void(*pfun_t)(int);
pfun_t signal(int,pfun_t);

6.补充

在上面的【案例一】,我们看过这样的代码:

int Add(int x,int y){
    int z=0;
    z=x+y;
    return z;
}
int main(){
    int a=10;
    int b=20;
    int(*pa)(int,int)=Add;	//将Add函数名赋值给pa指针
    printf("%d\n",(*pa)(2, 3));	//*pa找到函数
    return 0;
}

pa里面存的是Add函数的地址,调用的时候是*(pa),解引用找到这个函数。

❓这里的*号有什么意义呢?

比如我们这里给它加一颗*,或者加两颗*

如下:

printf("%d\n",(**pa)(2, 3));
printf("%d\n",(***pa)(2, 3));

这个输出结果是啥呢?

看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FqWNc31R-1673084595446)(D:\Typora图片\image-20230106093611185.png)]

可以看见,三个结果都是5,这说明了什么?

说明这个*只是摆设!

那我们现在将*号去掉:

printf("%d\n",(pa)(2, 3));

再次输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GOTpICpQ-1673084595446)(D:\Typora图片\image-20230106093940224.png)]


🍰总结

如果pa是函数指针,那么调用的时候,可以解引用调用函数,也可以不解引用。

所以,一般是以下两种写法:

①pa是指针,想要找到它所指向的函数并且调用,先解引用,然后再传参调用。

这是我们对指针的最初印象,就是:指针解引用才能找到它指向的对象。

即:

 int(*pa)(int,int)=Add;	//将Add函数名赋值给pa指针
 printf("%d\n",(*pa)(2, 3));	//第一种(不可以将*pa的括号去掉)
//如果*pa去掉括号,pa就会先与后面的(2,3)结合,算出5,5再解引用,这时候就会出问题-->非法的间接寻址)

②将Add函数传给了pa,说明Add和pa是一回事。

没有pa的时候,调用函数是这样Add(2,3),函数名是地址,pa也是存放函数地址的。

所以也可以这样写:

 int(*pa)(int,int)=Add;	//将Add函数名赋值给pa指针
 printf("%d\n",Add(2, 3));
 printf("%d\n",(pa)(2, 3));		//第二种(可以将pa的括号去掉)

对于函数来说,*没有什么价值,但是写上之后也是有意义的。写的时候,必须将*pa括号括起来。

写上之后,比较好理解。说明是解引用找到了这个对应的函数,然后去调用它。

二、函数指针数组

1.定义

数组是一个存放相同类型数据的存储空间,我们已经学了指针数组,详情请见指针进阶之数组指针和指针数组

比如:

int* arr[10];	//arr是一个指针数组,数组里面10个元素,每个元素是int*类型

现在要把函数的地址存到一个数组中,这个数组就叫函数指针数组

那函数指针数组如何定义呢?

举个例子。

现在有一个函数Add:

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

将函数Add的地址存起来:

int (*pa)(int,int)=Add;	//pa是一个函数指针,指向的函数参数类型有两个,都是int类型,返回类型也是int

pa存放的就是Add地址。


现在,不仅仅有一个加法函数Add,还有一个减法函数Sub。

int Sub(int x,int y){
    return x-y;
}

还有一个乘法函数Mul。

int Mul(int x,int y){
    return x*y;
}

还有一个除法函数Div。

int Div(int x,int y){
    return x/y;
}

这几个函数地址类型一模一样。

所以pa指针也可以存放Sub,Mul,Div。

但是现在pa里面只能存放一个地址,我们想要把四个地址都存起来。

这时候就需要一个数组,这个数组可以存放四个函数的地址。即:函数指针数组

那么函数指针数组咋写呢?假设数组名为parr。

很简单,将之前的代码改一下,让parr与[]结合,成为数组即可。

如下:

int (*parr[4])(int,int)

parr先与[]结合,是一个数组,数组里面有4个元素。

将数组名parr和元素个数[4]去掉,剩下的就是数组元素类型,即:函数指针类型int(* )(int,int)

此时的parr就是一个存放4个函数指针的数组

可以初始化一下:

int (*parr[4])(int,int)={Add,Sub,Mul,Div};	//parr为函数指针数组

2.补充

来说一下函数指针数组的正确写法,不要写错了。

①代码1

int (*parr1[10])();

这种写法是正确的。

parr1先与[]结合,说明parr1是一个数组,数组内容是int(* )()类型的函数指针,该函数无参,返回类型是int。

②代码2

int *parr2[10]();

这个代码啥也不是,语法错误

不要写这样的代码。

③代码3

int (*)() parr3[10];

这种写法也是错误的。

parr3[10]应该放在*号旁边。

3.案例

(1)案例一

函数指针数组如何使用呢?

举个例子吧。

还是拿上面的函数举例子。

现在有这么多函数,用parr函数指针数组存放起来了。

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 (*parr[4])(int,int)={Add,Sub,Mul,Div};	//parr为函数指针数组
    return 0;
}

找到数组parr的每个元素:parr[i]

数组parr的每个元素是函数地址,想要调用它,就需要解引用(这里就不解引用使用了,上面[误区](#### 误区)说明过)。

再给参数赋值,比如:parr[i](2,3)

int main(){
    int (*parr[4])(int,int)={Add,Sub,Mul,Div};	//parr为函数指针数组
    int i=0;
    for(i=0;i<4;i++){
        printf("%d\n",parr[i](2,3));
    }
    return 0;
}

i=0的时候,调用Add函数,算出结果5。

i=1的时候,调用Sub函数,算出结果-1。

i=2的时候,调用Mul函数,算出结果6。

i=3的时候,调用Div函数,算出结果0。

来看一下输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hDXPERQe-1673084595446)(D:\Typora图片\image-20230106112741495.png)]

(2)案例二

现在有一个函数my_strcpy,参数类型有char* destconst char* src,返回类型是char *

char* my_strcpy(char* dest,const char* src){
    
}

🍸需求

<1> 写一个函数指针pf,能够指向my_strcpy。

步骤:

①既然是指针,就在名字前面加一个*,用括号括起来,即:(*pf)

②函数指针,既然是函数,就需要在后面加一个括号,即:(*pf)()

③括号里面写上两个参数,即:(*pf)(char*,const char*)

④最后在最前面写上函数返回类型char*,即:char* (*pf)(char*,const char*)

char* (*pf)(char*,const char*)

<2> 写一个函数指针数组pfArr,能够存放4个my_strcpy函数的地址。

步骤:

①既然是数组,就先在名字后面写上[],表示数组,数组4个元素,所以这样写:pfArr[4]

②数组里面元素类型是函数指针类型,该函数两个参数char* const char*,返回类型是char *

所以函数指针这样写的:char* (* )(char*,const char*)

③最后将数组名字和元素个数,放在*后面即可。即:char* (* pfArr[4])(char*,const char*)

char* (* pfArr[4])(char*,const char*)

4.转移表(计算器实例)

函数指针数组在一些时候,可以让代码更加简洁。

这里举一个简单的计算器实例,让大家感受一下。

(1)一般写法

首先设计一个menu函数:

void menu(){
    printf("********************\n");
    printf("**  1.add  2.sub  **\n");
    printf("**  3.mul  4.div  **\n");
    printf("**     0.exit     **\n");
    printf("********************\n");
}

在主函数中,输入一个值。

int main(){
    int input=0;
	do{
    	menu();
		printf("请选择:>");
		scanf("%d",&input);
	}
    return 0;
}

再根据输入的值,进行对应的加减乘除运算。

可以使用switch函数来进行判断,将input的值传进去。

switch(input){
        
}

比如Add函数:

switch(input){
    case 1:
        Add();
        break;
}

现在来设计一下函数:

加法函数:

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

减法函数Sub。

int Sub(int x,int y){
    return x-y;
}

乘法函数Mul。

int Mul(int x,int y){
    return x*y;
}

除法函数Div。

int Div(int x,int y){
    return x/y;
}

输入操作数,并用变量x和y存储。

如下:

int input=0;
int x=0;
int y=0;
do{
    menu();
	printf("请选择:>");
	scanf("%d",&input);
    printf("请输入两个操作数:>");
    scanf("%d%d",&x,&y);
}

再将x和y放入Add函数参数中,输出打印:

switch(input){
    case 1:
        printf("%d\n",Add(x,y));
        break;
}

同样可以写出其他函数:

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");	//用户输入的是0,就提示退出
        break;
    default:
        printf("选择错误\n");	//输入其他值
        break;
        
}

do后面while还没有写,加上:

int main(){
    int input=0;
	int x=0;
	int y=0;
	do{
   	 	menu();
		printf("请选择:>");
		scanf("%d",&input);
    	printf("请输入两个操作数:>");
   		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");	//用户输入的是0,就提示退出
                break;
            default:
                printf("选择错误\n");	//输入其他值
                break;  
        }
	}while(input);	
    //将input写进去,如果输入1~4,就进入do-while循环输出相应的值;如果输入0,就跳出了循环;如果输入其他值,用户就重新选择
    return 0;
}

整体代码如下:

void 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=0;
	int x=0;
	int y=0;
	do{
   	 	menu();
		printf("请选择:>");
		scanf("%d",&input);
    	printf("请输入两个操作数:>");
   		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");	//用户输入的是0,就提示退出
                break;
            default:
                printf("选择错误\n");	//输入其他值
                break;  
        }
	}while(input);	
    //将input写进去,如果输入1~4,就进入do-while循环输出相应的值;如果输入0,就跳出了循环;如果输入其他值,用户就重新选择
    
    return 0;
}

输出看一下;

在这里插入图片描述

我们输入1,回车:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vAEmsFfC-1673084595447)(D:\Typora图片\image-20230106164637092.png)]

操作数写2 3:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QWghiwB-1673084595447)(D:\Typora图片\image-20230106164724011.png)]

可以看到输出了结果5。

还可以选择其他值,比如3,操作数是2 3:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cjHCF0nM-1673084595448)(D:\Typora图片\image-20230106164833335.png)]

输入0,退出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uMHEXf6T-1673084595448)(D:\Typora图片\image-20230106165323859.png)]


其实这个代码有不好的地方,比如刚才输入了0,还要再输入操作数才能退出。

这里可以将输入代码放进case语句里面。

📚完整代码如下:

void 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=0;
	int x=0;
	int y=0;
	do{
   	 	menu();
		printf("请选择:>");
		scanf("%d",&input);
    	//printf("请输入两个操作数:>");	//移动
   		//scanf("%d%d",&x,&y);	//移动
    
    	switch(input){
            case 1:
                printf("请输入两个操作数:>");
   				scanf("%d%d",&x,&y);
                printf("%d\n",Add(x,y));	//加法
                break;
            case 2:
                printf("请输入两个操作数:>");
   				scanf("%d%d",&x,&y);
                printf("%d\n",Sub(x,y));	//减法
                break;
            case 3:
                printf("请输入两个操作数:>");
   				scanf("%d%d",&x,&y);
                printf("%d\n",Mul(x,y));	//乘法
                break;
            case 4:
                printf("请输入两个操作数:>");
   				scanf("%d%d",&x,&y);
                printf("%d\n",Div(x,y));	//除法
                break;
            case 0:
                printf("退出\n");	//用户输入的是0,就提示退出
                break;
            default:
                printf("选择错误\n");	//输入其他值
                break;  
        }
	}while(input);	
    //将input写进去,如果输入1~4,就进入do-while循环输出相应的值;如果输入0,就跳出了循环;如果输入其他值,用户就重新选择
    
    return 0;
}

(2)改进

上面的代码,如果有特别多的函数,那么case语句就要写很长。

可以发现,case语句里面,函数调用的时候,参数都是两个。

将switch语句删掉,我们重新写一个。

int main(){
    int input=0;
	int x=0;
	int y=0;
	do{
   	 	menu();
		printf("请选择:>");
		scanf("%d",&input);
    	printf("请输入两个操作数:>");	
   		scanf("%d%d",&x,&y);	   
    	
	}while(input);	
    
    return 0;
}

现在用pfArr存储所有函数的i地址。

pfArr是函数指针数组。

❓ 怎么书写呢?

pfArr是数组,先和[]结合。即:pfArr[]

数组里面存放的是函数指针类型,即:(* )()

该函数,有两个参数,每个参数都是int类型,返回值是int类型。所以函数指针这样写:int (* )(int,int)

上面的案例一共有4个函数,pfArr就要存4个函数的地址。

就可以写出pfArr函数指针数组了:int (*pfArr[4])(int,int)

这里我们初始化5个元素,因为要存一个0进去。

所以初始化这样写:

int (*pfArr[5])(int,int)={0,Add,Sub,Mul,Div};

当用户输入1,2,3,4中的一个值,就可以用pfArr[input]来访问对应元素。

比如,pfArr[1]就可以访问Add函数。刚好和之前我们设定的,用户输入数字1,调用Add函数对应。

找到函数,然后调用函数:

pfArr[input](x,y)

用一个变量ret接收这个值:

int ret=pfArr[input](x,y);

最后输出:

printf("%d\n",ret);

如果输入的值不是1,2,3,4中的一个,就需要退出。

所以在用户选择数字之后,我们需要用if语句判断一下:

if(input>=1 && input<=4){
    
}

加上之前的代码,完整的if语句如下:

if(input>=1 && input<=4){
    printf("请输入两个操作数:>");	
   	scanf("%d%d",&x,&y);
    int ret=pfArr[input](x,y);
    printf("%d\n",ret);
}else if(input==0){
    printf("退出\n");
}else{
    printf("选择错误\n");
}

📚 完整代码如下

void 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=0;
	int x=0;
	int y=0;
    int (*pfArr[5])(int,int)={0,Add,Sub,Mul,Div};
	do{
   	 	menu();
		printf("请选择:>");
		scanf("%d",&input);
    	if(input>=1 && input<=4){
            printf("请输入两个操作数:>");	
            scanf("%d%d",&x,&y);
            int ret=pfArr[input](x,y);
            printf("%d\n",ret);
        }else if(input==0){
            printf("退出\n");
        }else{
            printf("选择错误\n");
        }
    	
	}while(input);	
    //将input写进去,如果输入1~4,就进入do-while循环输出相应的值;如果输入0,就跳出了循环;如果输入其他值,用户就重新选择
    
    return 0;
}

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gEiYpYsK-1673084595449)(D:\Typora图片\image-20230107095811842.png)]


改造之后的逻辑还是之前的逻辑。

但是,以后不管要增加什么运算。

只需要添加以下函数即可。

比如现在要增加一个异或运算。

int Xor(int x,int y){
    return x^y;
}

那么在主函数中,只需要在pfArr初始化的地方,增加Xor函数即可。如下:

int (*pfArr[6])(int,int)={0,Add,Sub,Mul,Div,Xor};

注意,菜单栏也需要增加一下这个选项:

void menu(){
    printf("********************\n");
    printf("**  1.add  2.sub  **\n");
    printf("**  3.mul  4.div  **\n");
    printf("**  5.Xor  0.exit **\n");
    printf("********************\n");
}

if语句判断也要加一个5:

if(input>=1 && input<=5){
    
}

可以看到,用函数指针数组的形式去解决问题,会简单很多。

“转移表”的概念其实也是因为通过函数指针直接调用函数。

三、指向函数指针数组的指针

定义

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针

来捋一下吧:

①这是整型数组arr

int arr[10]={0};

②这是数组的地址&arr

&arr

③将数组地址存进数组指针p

int (*p)[10]=&arr;

那么:

①这是一个函数Add

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

②用一个数组pfArr存放这个函数的地址:

int (*pfArr[4])(int,int);//pfArr是函数指针数组,里面有4个元素,每个元素是函数指针类型:int (* )(int,int)

③这是数组pfArr的地址

&pfArr

④用一个指针ppfArr指向数组pfArr

如果把握不住ppfArr的写法,可以这样写。

首先将pfArr拿过来:int (*pfArr[4])(int,int),然后将名字改为指针ppfArr,即:int (*ppfArr[4])(int,int)

然后ppfArr是一个指针,所以再加一个*号,括起来,即:

int (*(*ppfArr)[4])(int,int)=&pfArr;

ppfArr是什么呢?

首先ppfArr*号结合,说明是一个指针。

该指针指向什么呢?

往后看,指向的是一个数组[4],4个元素。

每个元素的类型是什么呢?

将刚才的*ppfArr[4]去掉,剩下的是int (* )(int,int),即:函数指针类型。

所以,ppfArr是一个数组指针,指针指向的数组有4个元素,每个元素是函数指针类型。


再来区分一下概念:

int (*pf)(int,int);	//函数指针
int (*pf[4])(int,int);	//函数指针数组
int (*(*ppf)[4])(int,int);	//函数指针数组指针

这个了解即可,后边可以按照需求使用。


整理文章和码字不容易,多多支持~
有缘再见喽~请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147484.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剑指offer----C语言版----第十二天

目录 打印从1到最大的n位数 1.1 题目描述 1.2 Leetcode上的解题思路 1.3 考虑大数的问题 1.3.1 使用字符串模拟数字的加法 1.3.2 使用全排 打印从1到最大的n位数 原题链接&#xff1a;剑指 Offer 17. 打印从1到最大的n位数 - 力扣&#xff08;LeetCode&#xff09;1.1 题…

算法刷题打卡第58天:删除排序链表中的重复元素

删除排序链表中的重复元素 难度&#xff1a;简单 给定一个已排序的链表的头 head &#xff0c;删除所有重复的元素&#xff0c;使每个元素只出现一次。返回已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2]示例 2&#xff1a; 输入…

Cesiumlab对人工模型、建筑矢量面和BIM模型的处理参数设置 CesiumLab系列教程

CesiumLab中将人工模型&#xff08;fbx、obj&#xff09;、建筑矢量面&#xff08;shp&#xff09;和BIM模型&#xff08;clm&#xff09;的处理都集中在一起&#xff0c;统一使用通用模型处理。 输入文件部分&#xff0c;加载文件在这里不在赘述&#xff0c;输入了文件后&…

陪诊系统搭建,陪诊平台应当具备什么功能?

随着近些年来市场的变化&#xff0c;陪诊服务也在慢慢的受到人们的关注&#xff0c;自从有了陪诊系统之后&#xff0c;帮助了许许多多独立就医不便的人群&#xff0c;给了像是搞不清就诊流程的老年人、家人不方便陪伴的孕妇、残障人士&#xff0c;以及需要陪伴就医的独居人士等…

上海市“专精特新”中小企业和杨浦区“专精特新”中小企业给予5万元和3万元资助

杨浦区“专精特新”中小企业认定一、主管部门杨浦区商务委员会二、政策依据《关于印发<杨浦区“专精特新”中小企业培育工程实施办法>的通知》&#xff08;杨商务委规〔2018〕1号&#xff09;《关于组织推荐2021年度杨浦区“专精特新”中小企业申报(复核)的通知》三、扶持…

【Qt】加载.ui转化的.h头文件显示窗体

【Qt】加载.ui转化的.h头文件显示窗体1、背景2、实例3、验证1、背景 将.ui文件转化为.h头文件参考如下博客&#xff1a; 【Qt】将QtDesigner生成的.ui文件转化为.h头文件 https://jn10010537.blog.csdn.net/article/details/128589666其中生成的ui_widget.h头文件内容如下&am…

TensorFlow之超级参数调优

Keras技术框架提供工具类库&#xff0c;用于对TensorFlow程序相关的超级参数进行调优&#xff0c;为机器学习选择正确的超级参数集合的过程被称之为超级参数调优。 超级参数是指用于治理一个机器学习模型的训练过程及其拓扑结构的变量&#xff0c;这些变量在整个训练过程中保持…

尚医通-项目启动过程

1.先启动Redis&#xff1a; redis-server redis.conf & 2.启动docker&#xff1a; systemctl start docker 3.进入mongo容器&#xff1a; docker exec -it mymongo /bin/bash 4.使用MongoDB客户端进行操作 mongo 5.启动nginx&#xff1a;cmd 输入命令nginx 前期使…

【Kotlin】空安全 ③ ( 手动空安全管理 | 非空断言操作符 !! | 使用 if 语句判空 )

文章目录一、非空断言操作符 !!二、使用 if 语句判空一、非空断言操作符 !! Kotlin 中的 可空类型 变量 , 在运行时 可以选择 不启用 安全调用 操作 , 在调用 可空类型 变量 成员 与 方法 时 , 使用 非空断言操作符 !! , 如果 可空类型 变量为 空 , 则 直接抛出 空指针异常 K…

部署k8s集群

环境准备准备三台虚拟机&#xff0c;建议最小硬件配置&#xff1a;2核CPU、2G内存、20G硬盘 &#xff0c;可以访问外网&#xff0c;&#x1f4a1;ps&#xff1a;以下命令在三台虚拟机上都要执行一遍&#xff0c;直到kubeadm init设置虚拟机hostname sudo hostnamectl set-hostn…

性能优化系列之『混合式开发:小程序内核及优势介绍』

文章の目录一、愿景二、技术优势三、底层内核四、行业背景五、选型建议写在最后一、愿景 触手可及&#xff1a;用户扫一扫或者搜一下即可打开应用用完即走&#xff1a;不用关心是否安装太多应用 二、技术优势 H5 相比 App 开发&#xff0c;开发门槛更低优于 H5&#xff0c;接…

指针进阶篇(1)

目录 &#x1f914; 前言&#x1f914; 一、&#x1f60a;字符指针&#x1f60a; 二、&#x1f61c;指针数组&#x1f61c; 三、&#x1f61d;数组指针&#x1f61d; 3.1数组指针的定义 3.2&数组名VS数组名 3.3数组指针的使用 四、&#x1f31d;数组参数&#xff0c…

LeetCode算法之----回溯

目录 【一】前言 【二】全排列 【三】电话号码的字母组合 【四】括号生成 【五】组合总和 【六】子集 【七】总结 【一】前言 回溯算法采用试错的思想&#xff0c;尝试分步的来解决一个问题。在分步解决问题的过程中&#xff0c;当它通过尝试发现现有的分步答案不能得到有效的…

helm、k8s dasboard、rancher、kubesphere介绍及使用

文章目录1. helm 安装及使用概述1.1 helm 安装1.1.1 添加仓库1.2 helm 常用命令2. dashboard 部署使用2.1 安装helm repo 源2.2 安装dashboard2.3 查看dashboard 运行状态2.4 创建dashboard-admin.yaml文件2.5 创建登录用户2.6 查看admin-user账户的token2.7 登录dashboard2.8 …

非对称加密实战(二):解决web项目不支持https问题 ,添加证书【附源码】

目录web项目http请求变为https请求解决无法访问https问题重启再次访问https出现链接不安全,但是可以继续访问认证文件加入域名参数生成客户端认证文件证书安装源码地址web项目 http请求变为https请求 http请求 https请求 解决无法访问https问题 需要把 非对称加密实战(一…

【博客576】警惕docker本身iptables规则对网络的影响

警惕docker本身iptables规则对网络的影响 警惕1&#xff1a;k8s环境下&#xff0c;独立拉取docker容器时&#xff0c;进行端口映射会有问题 场景&#xff1a; 在k8s节点由于某种原因&#xff0c;比如&#xff1a;需要拉起一个docker环境来制作镜像&#xff0c;需要拉起一些不…

靶机测试Os-hacknos-3笔记

靶机介绍Difficulty: IntermediateFlag: 2 Flag first user And the second rootLearning: Web Application | Enumeration | Privilege EscalationWeb-site: www.hacknos.comContact-us : rahul_gehlautThis works better with VirtualBox rather than VMware靶机地址https://…

nuPlan: A closed-loop ML-based planning benchmark for autonomous vehicles

Paper name nuPlan: A closed-loop ML-based planning benchmark for autonomous vehicles Paper Reading Note URL: https://arxiv.org/pdf/2106.11810.pdf TL;DR nuPlan 比赛&#xff0c;提出了规控领域新数据集 Introduction 背景 当前自动驾驶规划任务中使用专家系统…

正确实践Jetpack SplashScreen API —— 在所有Android系统上使用总结,内含原理分析

1.前言 文章末尾有演示的APK链接&#xff0c;感兴趣的同学&#xff0c;可以自行下载体验一下 官方Android 12的Splash Screen文档地址 官方Splash Screen兼容库&#xff0c;支持所有版本系统 本篇文章主要围绕下面三个问题来介绍&#xff1a; 我们能从Android 12 SplashScree…

订单数据越来越多,如何优化数据库性能?

“增删改查”都是查找问题&#xff0c;因为你都得先找到数据才能对数据做操作。那存储系统性能问题&#xff0c;其实就是查找快慢问题。 存储系统一次查询所耗时间取决两个因素&#xff1a; 查找的时间复杂度数据总量 查找的时间复杂度取决于&#xff1a; 查找算法存储数据…