目录
嘿嘿,家人们,今天咱们继续来剖析指针的进阶部分,好啦,废话不多讲,开干!
1:函数指针
在之前,我们学习过整型指针是用来存储整型变量的地址,数组指针是用来存储数组的地址,那么同理,函数指针则是存放函数的地址,那么在之后呢我们则通过函数指针找到函数的地址来对函数进行调用,那么函数的地址是什么呢?,在数组阶段我们学习到数组名表示的是首元素的地址,也同样代表着数组的起始地址,那么我们能否推断函数名代表着函数的地址呢?我们来看下面这段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void Function()
{
printf("hello world");
}
int main()
{
printf("Function = %p\n", Function);
printf("&Function = %p\n", &Function);
return 0;
}
通过上面的代码,我们可以清晰地看到,函数名就是函数的地址,同样我们也能够通过&函数名的方式来获取函数的地址,那么要存储函数的地址,就要使用函数指针变量,那么函数指针该如何表示呢?我们来看下面这两种表达方式哪种是函数指针变量的表达方式.
void Function()
{
printf("hello world");
}
void (* pf1) () = Function;
void * pf2() = Function;
uu们可以推断一下哪种表达方式是函数指针变量的表达方式,答案是:第一个,之前我们学习过数组指针的表达方式,这里可以将二者进行一下类比,在使用数组指针存储数组的呢,我们有以下几步要确定
(1):确定数组所存储元素的数据类型.
(2):确定数组的大小.
(3):操作符的优先级
那么,同理,如果想用函数指针来存储函数的地址话,我们是不是得确定下面这几步
(1):函数的返回值类型
(2):函数的形参的数据类型以及个数
(3):操作符的优先级
那么我们来解析下为什么答案是第一个,首先由于 * pf被()所包围,那么 * 与pf1就会与 pf1结合,那就说明 pf1是个指针,那具体指向的是什么,我们把pf1拿出来,然后再看整个式子,我们就能清晰地发现,这个式子就是定义函数时的式子,那么根据这个表达式我们能推断出pf是一个函数指针,指向的是一个返回值为void,形参个数为0的函数;再来看第二个式子,在操作符的优先级里面()比 *的优先级要高,因此pf2先与()结合,再看整个式子,就能推断出,pf2是一个函数,其返回值为void *,形参个数为0.
1.1函数指针的使用
讲完了函数指针的概念后,接下来我们来讲解函数指针的使用,我们来看下面这段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Add(int value1,int value2)
{
return value1 + value2;
}
int main()
{
int (*pf)(int, int) = Add;
printf("%d\n", (*pf)(1, 2));
printf("%d\n", pf(1, 2));
return 0;
}
有两种方式来通过函数指针来调用函数
(1):既然函数指针里面存放的是函数的地址,那么我们则可以先通过解引用操作符找到所指的对象,然后再通过函数调用操作符对该函数进行调用.
(2):在上面有讲到,函数名代表的是函数的地址,在之前我们能够直接通过函数名去调用函数,而函数指针变量里面存储的是函数的地址,因此我们则能够直接通过函数指针变量去调用函数.
1.2:两段有趣的代码
1.2.1:代码1
(*(void (*)())0)();
uu们看到上面这段代码时,会很疑惑,这是什么代码?来,我们一步一步来看
(1):void (*) ()这个是不是很眼熟,如果还不熟悉的话,可以在里面放个变量名pf.这是个函数指针的类型,该函数指针的返回值类型为void,无参数.
(2):这个函数指针的类型被放到了()里面,将一个类型放到了()括号里面时对其进行强制类型转换,也就是说,这一步是将0地址处的函数进行强制类型转换
(3):强制类型转换后,再将其进行解引用,接着解引用之后,再去进行调用.
1.2.2:代码2
void (*signal(int , void(*)(int)))(int);
uu们看到上面这段代码,又会疑惑,这又是啥,我们一步步来看
(1):()的优先级大于*,那么signal是先与()结合,()里面又有两个参数,说明signal是一个函数,里面有两个参数,第一个参数为int类型,第二个参数为void (*)(int)函数指针,该函数指针指向的是一个参数类型为int,返回值为void的函数.
(2):我们说函数有一般有四个部分:函数名,返回值类型,函数参数,函数体;现在函数名与函数参数都有了,而函数体是没有的,那就说明剩下的部分是返回值,也就是说外面这一层的void (*)(int)是signal函数的返回值,也是一个函数指针,指向的是一个参数类型为int,返回值为void的函数.
2:typedef关键字命名函数指针与数组指针
在之前我们学习过,typedef关键字能够对复杂的类型进行重定义,这样子方便我们使用.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
typedef unsigned int uint;
typedef unsigned int* ptr_t;
int main()
{
uint value1 = 20;
ptr_t pf = &value1;
cout << typeid(value1).name() << endl;
cout << typeid(pf).name() << endl;
return 0;
}
为了方便uu们观察,博主在这里使用了一些C++的相关知识,typeid().name()能够查看变量所属的数据类型.
那么除了对那些基础的数据类型进行重命名外,我们也同样可以使用此关键字对函数指针与数组指针进行类型重定义,不过与一般的数据类型重定义有些不太一样,使用typedef关键字对函数指针和数组指针进行重定义时有些小细节需要注意一下.
2.1:typedef关键字命名函数指针
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
typedef void (*Vpf)(int);
typedef int (*Ipf)(int,int);
void Function1(int value1)
{
}
int Function2(int value1, int value2)
{
return 0;
}
int main()
{
Vpf vpf1 = &Function1;
Ipf ipf1 = &Function2;
cout << "Function1:> " << typeid(Function1).name() << endl;
cout << "vpf1:> " << typeid(vpf1).name() << endl;
cout << "Function2:> " << typeid(Function2).name() << endl;
cout << "ipf1:> " << typeid(ipf1).name() << endl;
return 0;
}
一般我们在进行类型重定义时,新的类型名是跟在数据类型的后面,但是在函数指针这里不一样,新的类型名必须放在*的右边,这是语法规定滴!
2.2:typedef关键字命名数组指针
使用typedef关键字命名数组指针也是一样的,新类型的名字要跟在*的右边~
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
typedef int (*Iparr)[20];
typedef double (*Dparr)[20];
int main()
{
int arr1[20] = { 1,2,3,4,5,6,7,8,9,10 };
double arr2[20] = { 1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9,10.10};
Iparr pArr1 = &arr1;
Dparr pArr2 = &arr2;
cout << "&arr1:> " << typeid(&arr1).name() << endl;
cout << "pArr1:> " << typeid(pArr1).name() << endl;
cout << "&arr2:> " << typeid(&arr2).name() << endl;
cout << "pArr2:> " << typeid(pArr2).name() << endl;
return 0;
}
3:函数指针数组
在之前我们学习过数组是用于存放相同数据类型的存储空间,譬如
int * arr[10] 是一个指针数组,里面的每一个元素都是整型指针.
int arr[10]是一个整型数组,里面的每一个元素都是整型数据.
那么将函数指针存放在一个数组中,这个数组就叫做指针数组,那函数指针数组该如何定义呢?我们来看下面的几种定义方式哪个是函数指针数组的定义方式.
int ( * parr1 [ 10 ])();int * parr2 [ 10 ]();int ( * )() parr3 [ 10 ];
uu们可以自己分析看看,看哪个是函数指针数组的定义方式,答案是第一个哦~
(1):由于[]的优先级大于*,因此parr1先与[]结合,说明parr1是一个数组,
(2):然后我们将其拿出来,会发现int (*)()不就是一个函数指针嘛,我们知道定义数组的时候有三个要素:
数组名
数组里面元素的数据类型
数组的大小现在数组名为parr1,数组的大小为10,那就说明int (*)()为数组里面元素的数据类型.因此这就是一个函数指针数组的定义方式.
3.1:函数指针数组的用途
了解了函数指针数组的定义后,接下来我们来看看函数指针数组的用途,如果说没有使用函数指针数组的话,我们在实现一个小型的计算机滴,应该是按照如下方式实现的
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Add(int value1,int value2)
{
return value1 + value2;
}
int Subtraction(int value1,int value2)
{
return value1 - value2;
}
int Multiplication(int value1, int value2)
{
return value1 * value2;
}
int Division(int value1,int value2)
{
return value1 / value2;
}
void menu()
{
printf("**************************************************\n");
printf("***** 1:Add 2:Subtraction ************\n");
printf("***** 3:Multiplication 4:Division ************\n");
printf("***** 0:exit ************\n");
printf("**************************************************\n");
printf("**************************************************\n");
printf("请进行选择:> ");
}
int main()
{
int input = 0;
int result = 0;
int value1 = 0;
int value2 = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 0:
printf("退出计算器\n");
break;
case 1:
printf("请进行输入:>");
scanf("%d %d", &value1,&value2);
result = Add(value1, value2);
printf("计算结果为:>%d\n", result);
break;
case 2:
printf("请进行输入:>");
scanf("%d %d", &value1, &value2);
result = Subtraction(value1, value2);
printf("计算结果为:>%d\n", result);
break;
case 3:
printf("请进行输入:>");
scanf("%d %d", &value1, &value2);
result = Multiplication(value1, value2);
printf("计算结果为:>%d\n", result);
break;
case 4:
printf("请进行输入:>");
scanf("%d %d", &value1, &value2);
result = Division(value1, value2);
printf("计算结果为:>%d\n", result);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
以上呢,我们使用do while循环和switch case语句相结合所实现的那么一个小型计算器,我们来仔细分析一下这个代码,uu们有没有发现每次case语句后面的代码的整体框架与逻辑是不是一样的,都是(1):输入操作数(2):调用所对应的函数并且用变量接收(3):输出结果.使用switch和case语句感觉显得有些冗余,因为我所有的case语句后面的整体框架与逻辑是一样,那么我们有什么办法能够使这个代变得简短些呢?我们再来观察下每个case语句所调用的函数.
我们仔细观察每一个case语句所调用的函数就会发现,上面的函数的返回值与参数个数以及参数的数据类型是一样的.而我们之前学到过数组是用来存储相同数据类型的一个集合并且我们也学习了函数指针数组,那么我们能够将每个函数封装到函数指针数组里面,然后通过下标索引去获取里面的每个元素,然后再进行调用,这样子的话是不是会将这个代码变得简便些呢?我们来实现看看.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Add(int value1,int value2)
{
return value1 + value2;
}
int Subtraction(int value1,int value2)
{
return value1 - value2;
}
int Multiplication(int value1, int value2)
{
return value1 * value2;
}
int Division(int value1,int value2)
{
return value1 / value2;
}
void menu()
{
printf("**************************************************\n");
printf("***** 1:Add 2:Subtraction ************\n");
printf("***** 3:Multiplication 4:Division ************\n");
printf("***** 0:exit ************\n");
printf("**************************************************\n");
printf("**************************************************\n");
printf("请进行选择:> ");
}
int main()
{
int input = 0;
int result = 0;
int value1 = 0;
int value2 = 0;
int (*Function[5])(int, int) = { NULL,Add,Subtraction,Multiplication,Division };
do
{
menu();
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
else if(input >= 1 && input <= 4)
{
printf("请进行输入:>");
scanf("%d %d", &value1, &value2);
result = Function[input](value1, value2);
printf("计算结果为:>%d\n", result);
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
看,这样子实现的话是不是能够达到同样的效果,而且代码量还减少了许多.PS:函数指针数组第一个值给NULL的原因是:数组下标是从0开始的,然后为了和我们正常的输入相匹配,因此第一个值给NULL.
我们对比一下这两个代码可以清晰地观察到,很明显右边那段代码要更加简洁些.
这就是函数指针数组的用途:转移表
转移表是一种编程中的思想和技术,主要是用于优化多分支的选择结构,尤其是当分支的数量较多且每个分支对应一个特定操作的情况时.
好啦,家人们,关于指针进阶(二)这块的相关细节知识,博主就讲到这里了,如果uu们觉得博主讲的不错的话,请动动你们滴滴给博主点个赞,你们滴鼓励将成为博主源源不断滴动力!