目录
1.字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针 char* 。示例如下:
还有一种使用方法如下:
代码 char* pstr = "ABCDEFG" 容易让人以为是吧字符 ABCDEF 放到了字符只恨 pstr中,但是本质上是把字符串的首字符放到了pstr中。
2.数组指针变量
2.1 什么是数组指针变量?
在之前的学习中我们知道,指针数组是一种数组,数组中存放的是地址(指针)。
根据之前的指针变量:
- 整形指针变量:int* pi;存放的是整型变量的地址,能够指向整型数据的指针。
- 字符指针变量:char* pc;存放字符变量的地址,能够指向字符型数据的指针。
那么,数组指针变量就是:存放数组的地址,能够指向数组的指针变量。
分辨指针数组和数组指针变量
int* p1[10]; //指针数组
int(*p2)[10]; //数组指针变量
解析:数组指针变量:p 先和 * 结合,说明 p 是一个指针变量,然后指向的是一个大小为10个正行的数组,所以 p 是一个指针,指向一个数组,叫数组指针。
注意:[] 的优先级要高于 * 号的,所以必须加上 () 来保证 p 先和 * 结合。
2.2 数组指针变量初始化
数组指针是用来存放数组的地址,如果要存放整个数组的地址,就得存放在数组指针变量中。示例如下:
解析:int 是 p 指向的数组元素类型;(*p) 的 p 是数组指针变量名,[5] 是p指向数组的个数;&arr 是得到数组的地址。
3.二维数组传参的本质
在之前,我们有一个二维数组需要传参给一个函数时,我们是这样写的。代码如下:
这里的实参是二位数组,形参也可以写成二位数组的形式,那还有什么写法呢?
首先来再次理解一下二维数组,二位数组其实可以看做是每个元素的一维数组的数组,也就是二位数组的每个元素是一个一维数组。那么二维数组的首元素(也就是第一行),是一个一维数组、
那么,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。。再根据上面的二维数组给函数传参的例子,第一行的一维数组的类型及时 int[4] ,所以第一行的地址的类型就是数组指针类型 int(*)[5]。那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址。
总结:二维数组传参,传参的部分可以写成数组,也可以写成指针形式。
4.函数指针变量
4.1 函数指针变量的创建
在之前的学习中我们知道,数组指针是用来存放数组的地址(指针)。
所以,函数指针变量,是用来存放函数的地址(指针)。
测试一下怎么打印出函数的地址。示例如下:
能够打印出函数的地址,所以函数时有地址的,函数名就是函数的地址。当然也可以通过 &函数名 的方式获得函数的地址。
如果我们要讲函数的地址存放起来,就需要创建函数指针变量,函数指针变量的写法其实和数组指针的写法非常类似。示例如下:
解析:以 int (*pf3) (int x,int y) 为例,int pf3指向函数的返回类型,(*pf3)是函数指针变量名,(int x,int y)是pf3指向函数的参数类型和个数的交代。int (*) (int x,int y)是pf3函数指针变量的类型。
4.2 函数指针的使用
4.3 两段有趣的代码
下面来介绍两段代码,均出自《C陷阱和缺陷》这本书。
代码一:
(*(void (*)())0) ();
在第一个括号内的是一个类型转换表达式,即 *(void (*)())0 ,表示的是将整数 0 强制转换为函数指针类型。
* 是对函数指针的一个解引用,获取指针指向的函数
最后的 () 是对函数进行调用。
代码二:
void (*signal(int, void(*)(int)))(int);
在第一个括号内的是一个函数指针,signal 是函数名。
int是函数中的第一个参数的类型;void(*)(int)是函数第二个参数的类型,是一个函数指针,并且该函数指针的返回类型是 void。
刚开始的 void 是signal函数的返回类型,表示该函数不返回值。
最外层的 void(*)(int) 是一个函数指针,指向一个接受整数参数并且返回类型为void的函数。
4.3.1 typedef关键字
typedef 是用来类型重命名的,可以将复杂的类型简单化。
例如,想要写一个 unsigned int 类型,但是写起来不方便,就可以使用typedef定义成 unint ,这样就会方便一点。
那么,如何将这种方法应用到指针类型上呢?示例如下:
typedef int* ptr_t;
//将 int* 重命名为 ptr_t
对于数组指针和函数指针,会有一点区别:
比如,数组指针 int (*)[5],将其重命名为parr_t,示例如下:
typedef int(*parr_t)[5];
//新的类型名必须写在*号右边
函数指针类型的重命名也要将新的类型名写在*号右边。比如:void (*)(int) 类型重命名为 pf_t ,示例如下:
typedef int(*pf_t)(int);
//新的类型名必须写在*号右边
那么对之前的两个函数指针类型嵌套使用的情况,我们就可以使用typedef来简化代码。首先我们要知道,代码二整体的函数指针类型是 void (*)(int),其次,signal函数中的第二个参数类型也是 void (*)(int) ,那么就可以重新命名这个类型名,然后重复使用。示例如下:
void (*signal(int, void(*)(int)))(int); //简化之前
typedef void (*pfu_t)(int); //将 void (*)(int) 类型名重命名为 pfu_t
pfu_t signal(int, pfu_t); //简化之后
5.函数指针数组
整数指针数组:是数组,数组中存放的都是整数指针。
根据这个定义,那么可以推断,把函数的地址存放到一个数组中,那么这个数组就叫函数指针数组。
pArr先和 [] 结合,说明pArr是一个数组,数组的内容是 int(*)() 类型的函数指针。
6.转移表
函数指针数字的用途:转移表。
比如:计算机的一般实现。当我们使用以前的知识写代码,可能会使用 do......while 循环,switch语句,以及一些函数来组成这个代码。示例如下:
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");
printf("*******************************\n");
}
int main()
{
int x = 0;
int y = 0;
int ret = 0;
int input = 0;
do
{
menu();
printf("请输入你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
switch 语句中的每个case部分都有大量的重复,我们也可以将其写成函数,形参就用函数指针,这样可以精简一部分代码。示例如下:
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");
printf("*******************************\n");
}
void calc(int (*pf)(int ,int ))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int x = 0;
int y = 0;
int ret = 0;
int input = 0;
do
{
menu();
printf("请输入你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
但是当出现新的功能(如:%、>>、<<等)时,除了必要的写新的函数之外,还要增加switch语句中的case部分,最后主函数的代码会越来越长。
我们可以使用函数指针数组来试一试。代码如下:
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");
printf("*******************************\n");
}
int main()
{
int x = 0;
int y = 0;
int input = 1;
int (*pf[10])(int, int) = { 0,add,sub,mul,div };
do
{
menu();
printf("请输入你的选择:");
scanf("%d",&input);
if (input > 0 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
int ret = pf[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
printf("退出计算器\n");
else
printf("输入错误,请重新输入\n");
} while (input);
return 0;
}
这样在扩展代码时,代码就不会继续变长。只需要添加新功能的函数指针到数组中,并更改if判断条件中的 input<=4 中的数字即可,代码容易更改,也不会变得繁长。