C语言零基础第12讲:各类指针变量介绍与转移表应用
目录
1.字符指针变量
2.数组指针变量
3.二维数组传参的本质
4.函数指针变量
4.1 函数指针变量的创建
4.2 函数指针变量的使用
5.typedef关键字
6.函数指针数组
7.转移表
正文开始
1.字符指针变量
char*的一般使用如下:
我们再看另一种使用方式:
在《剑指offer》一书中收录了一道和字符串相关的笔试题,我们可以来看看:
2.数组指针变量
指针数组:存放指针的数组。
数组指针是什么呢?
我们可以类比一下:字符指针char*用来存放字符的地址,整型指针int*用来存放整型的地址......那么数组指针用来存放数组的地址,指向的是数组。
数组指针可以用来遍历数组:
这种遍历数组的方式很别扭, 那么数组指针到底有啥用呢?
其实,数组指针,在二维数组传参的时候使用,才更容易理解。
3.二维数组传参的本质
我们来看有一个二维数组传参的例子:
总结:二维数组传参,本质上是传递了第一行这个一维数组的地址,形参可以写成数组的形式,也可以写成数组指针的形式。
4.函数指针变量
顾名思义,函数指针变量是用来存放函数地址的,能够通过地址来调用函数。
函数是有地址的,我们可以看一个测试:
4.1 函数指针变量的创建
那么,我们怎么把函数的地址存放在变量中呢?请看代码:
4.2 函数指针变量的使用
那么,怎么通过函数指针来调用函数呢?请看代码:
接下来,我们可以看2段出自《C陷阱和缺陷》一书的代码,看看如何理解?
代码1:
(* ( void(*)() ) 0)();代码2:
void (* signal( int, void(*)(int) ) ) (int);
对于代码1,( void(*)() ) 0 是把0强制转换为函数指针类型,意思是让想0做函数的地址,这个函数的返回类型是void,没有参数。然后解引用,找到这个函数,再进行调用。当然了,解引用的*其实可以不用写。
对于代码2,signal( int, void(*)(int) )是函数的声明,其中signal是函数名,int和void(*)(int)是参数。我们知道,函数的声明包括3个部分:1.函数名,2.参数,3.返回值类型。显然,还缺少返回值类型部分。而剩余的部分,void (*...)(int) 则是返回类型,即 void(*)(int)。为什么返回值类型的位置这么奇怪呢?因为在C语言中,函数指针的声明必须用括号强制优先级。
5.typedef关键字
typedef是用来对类型进行重命名的,可以将复杂的类型简单化。
比如,如果你觉得unsigned int写起来不方便,想要简写成uint,可以这样做:
我们还可以对一般的指针变量进行重命名:
函数指针变量的类型,是挺复杂的,我们就可以进行重命名:
对于前面的代码:void (* signal( int, void(*)(int) ) ) (int);,signal的第二个参数void(*)(int)是一个函数指针变量,我们可以对其重命名为pf_t,可是该怎么写呢?
这样的话,void (* signal( int, void(*)(int) ) ) (int); 就可以简化为:pf_t signal(int, pf_t),其中pf_t是void(*)(int)。
6.函数指针数组
对于指针数组,可以把指针存放到数组中。
比如:int* arr[5];,float* arr[5];......
函数指针也是指针,也是可以放在数组中的,这样的数组叫函数指针数组。
请看下图:
7.转移表
函数指针数组有啥用呢?转移表就是它的一种用途。
我们来看一个例子,计算器的一般实现方式:
#include <stdio.h>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 r = 0;do{menu(); //调用函数,打印出菜单界面printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("请输入2个操作数:\n");scanf("%d %d", &x, &y);r = Add(x, y); //调用Addprintf("r = %d\n", r);break;case 2:printf("请输入2个操作数:\n");scanf("%d %d", &x, &y);r = Sub(x, y); //调用Sub printf("r = %d\n", r);break;case 3:printf("请输入2个操作数:\n");scanf("%d %d", &x, &y);r = Mul(x, y); //调用Mulprintf("r = %d\n", r);break;case 4:printf("请输入2个操作数:\n");scanf("%d %d", &x, &y); r = Div(x, y); //调用Divprintf("r = %d\n", r);break;case 0:printf("退出计算器\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}
我们来看一下运行效果:
可是,如果我们要实现更多种计算,就需要继续补充对应的函数,然后switch语句越写越长。其实,我们可以不使用switch语句,请看代码:
#include <stdio.h>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 (*pf[5])(int, int) = { NULL,Add,Sub,Mul,Div }; //将函数地址放在函数指针数组中,下标依次为:1,2,3,4,与菜单中的选项对应int input = 0;int x = 0;int y = 0;int r = 0;do{menu(); //调用菜单界面printf("请选择:");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入2个操作数:\n");scanf("%d %d", &x, &y);r = pf[input](x, y); //调用计算函数printf("r = %d\n", r);}else if (input == 0)printf("退出计算器\n");elseprintf("选择错误,请重新选择\n");} while (input);return 0;
}
我们来看一下运行效果:
这样改造代码之后,代码变得更加简洁了,好处在于,未来如果还需要增加更多的运算, 只需要增加函数和修改菜单,然后把函数的地址加到数组中即可,就不需要冗长的switch语句了。
像这样,利用函数指针数组来实现分支逻辑的方式,一般叫做转移表,它有一种跳转的作用,通过下标找到函数地址,再通过函数地址去调用函数,替代了冗长的switch等语句。
所以,函数指针数组也是有用武之地的。
不过,上面的代码只适用于2个整型的运算,所以转移表也有自己的局限性。
完结