目录
一.二级指针
指针即地址,地址即指针
假设存在下面这段程序
a变量在栈区有相应自己的空间进行存储,而空间肯定就有其对应的地址,而p变量则开辟了相应的空间,恰好存储了a的地址,所以&a,p实际上是同一个东西.
既然p变量也开辟了相应的空间存储(指针上篇已经讨论过这个问题,4个字节或8个字节大小),有空间肯定就有对应地址,而我们把这个地址再次存起来,就是我们的二级指针.
就像有个名叫a的人,在酒店定了四间连续编号的房间,而有个仰慕a的名叫p(一级指针)的人,偷偷记下来第一间房间的房间号,所以通过p,我们可以知道a住的四个房间.
但是p不知道,他定的四间连续编号的房间,早已经被一个名叫pp(二级指针)的人也偷偷记住,通过pp,我们可以知道p住的四个房间.
二.数组指针
1.数组指针的定义
数组指针中的数组是定语,是用来修饰指针的,所以数组指针,本质上是一个指针.
指向数组的指针,我们便称之为数组指针.
那如何定义呢?我们先尝试类比一下
我们知道存储浮点数的地址,就在float/double后面加一个*,存储整型的地址,就在int,long,char后面加一个*,于是假设一个存在一个数组int arr[10](存储了10个整型)
然后指针需要加入*说明它是一个指针,那我们就可以得到
int *p [10]
但是这样就可以吗?我们还知道[]的优先级要高于*号的,所以我们还需要加上()来保证p先和*结合
于是就可以得到 int (*p) [10],这个指针指向一个数组,总共10个元素,每个元素类型为int.
2.数组指针的使用
指针中篇中我们其实也谈到过,数组名代表的是首元素地址,而&数组名,取出的是整个数组的地址,表面上两者的数值是相同的,但是比如说+1等步长,两者跳过的空间大小是不同的.
现在我们便知道它们两者本质区别在哪里了
前者的类型是int *,加步长1跳过的只是int
后者的类型是int (*) [num],加步长1跳过的是一个数组的大小.
那我们具体如何使用呢?
三.指针数组
和数组指针相反,现在的定语变为指针,是用来修饰数组的,所以数组指针,本质上是一个数组.
存储指针(元素是指针)的数组,我们便称之为指针数组.
通过得到数组指针的推理,我们可以轻松得到指针数组的表现形式,比如说:
int * p[10];(1)
四.函数指针
1.函数指针的定义
既然存在数组指针,那我们稍微改变一下定语,将数组改为函数,便得到函数指针.
指向函数的指针,我们便称为函数指针.
函数也是有地址的,因为C语言调用一个函数,也同时需要开辟一个空间,具体可以看函数栈帧的开辟一节,而空间就对应有地址.
&函数名,函数名都是代表函数的地址,两者没有区别.

同样,我们可以知道函数调用运算符优先级最高,为了说明它是一个指针,需要加一个括号
void (*pfun) (),这样pfun和*优先结合,说明是一个指针,剩下的部分就是指针指向的内容,一个不需要参数,返回也是void的函数.
再看上面这个例子,我设计了一个pf的函数指针,存放Add函数的地址,然后通过解引用pf,就可以调用Add这个函数.
值得注意的是,因为我们在函数调用的时候,Add(3,5)即可,而此时pf存放了Add这个函数的地址,因此pf无论是否加*使用,都是可以的.
接下来,我们再看两个具体的例子
先看代码1
第一个突破口就在0这,0是一个int类型的变量,我们注意到最前面还有一个*,只有指针也就是地址,才可以解引用.
第二个突破口是void (*)(),这是一个函数指针类型,指向一个不需要返回值,参数的函数.
结合两个突破口,我们便知道,这段代码的实际含义
将0强制类型转化为一个函数指针类型,然后解引用,访问0地址处的函数.
再看代码2
先看第一部分,signal是一个函数名,signal第一个参数是Int,第二个参数是一个函数指针.
再看第二个部分,void (*) (int)是一个函数指针类型,括号里面应该放一个函数指针.
结合两个部分,我们便知道,这是一个函数声明,声明的函数叫signal
signal返回的类型也是一个函数指针,第一个参数是int,返回类型是void.
但本身这段代码是不易阅读的,而我们还注意到void (*) (int),实际上重复出现了两次,所以我们可以考虑用typedef对它进行简化.
首先将函数指针类型,重命名为pf_t
然后根据我们上面分析的思路,可以将代码大大化简.
2.回调函数
实际上,函数指针的概念我们并不陌生,在qsort函数模拟实现中,我们曾经就运用过这个概念,qsort函数其中一个参数,就需要我们传递一个比较两个元素方法的函数指针.
无独有偶,在实现顺序线性表L中查找第1个值与我们给定元素满足compare()的元素的位序的函数时,我们也运用到函数指针作为形参.
int LocateElem_Sq(Sqlist L,ElemType e,int (* compare) (ElemType,ElemType));
像这样
五.函数指针数组
1.函数指针数组的定义
我们第三部分曾经提到过指针数组,也就是每一个元素是一个指针,而函数指针本质就是一个指针.
所以自然存在函数指针数组这样的概念.
2.函数指针数组的应用(转移表)
假设设计一个简单的计算器,代码如下
void menu()
{
printf("*********************************\n");
printf("******* 1.Add 2.sub ******\n");
printf("******* 3.mul 4.div ******\n");
printf("******* 0.exit ******\n");
printf("*********************************\n");
printf("*********************************\n");
}
int main()
{
int input = 0;
int ret = 0, x = 0, y = 0;
do
{
menu();
printf("请输入数字: \n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
}while (input);
return 0;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do{
menu();
printf("请选择:");
scanf("%d", &input);
if (input == 0)
{
printf("退出程序\n");
}
else if ((input <= 4 && input > 0))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
}
else
printf("输入有误\n");
} while (input);
return 0;
}
于是,假如存在新的元素,我们只需要改变函数指针数组的大小,和menu菜单函数即可,大大优化了代码的可读性.
六.指向函数指针数组的指针
函数指针数组是一个数组,那同样可以通过一个指针指向该数组.
这个指针,就被称为指向函数指针数组的指针.
假设存在这样一个函数指针数组
七.经典题解析
1.第一道题
求输出的值为多少?
解析:
a为首元素的地址,则a作为二维数组的数组名,实际上它的类型是int (*)[5],而p的类型仅仅只能指向4个int类型的数组.因此会先发生强制类型转化.
p[4][2]代表的仅是*(*(p + 4) + 2),p作为int (*)[4]仅仅只能访问含4个int的数组,因此p+4,最后移动到图示位置,然后解引用可以访问一个4个int的数组,加2则访问这个数组的第二个元素.
因此&p[4][2] - &a[4][2]得到的是-4,地址打印即是-4的补码,因为数在电脑中以补码形式存储,地址无正负号区别,因此,%p打印的是-4的补码.
最后答案:
2.第二道题
求最后输出的答案是什么?
解析:
根据题目,我们可以画出如上图的表示图
cpp++后,两次解引用,我们可以得到POINT中P的首元素地址,%S打印出来即是POINT
接下来,cpp++,由于*,++,--优先级都比+号高,所以会先对cpp++,进行解引用,得到c+1,但此时又对它进行--,变成了c
此时对c又解引用,得到E的首元素地址,+3得到E的地址,然后打印,结果是ER
注意:此时cpp没有发生移动
cpp[-2]意味着又移动cpp回到初始位置,解引用得到c+3
前面又加了颗*,则F首元素的地址,+3得到S的地址,最后打印ST
最后cpp[-1][-1] = *(*(cpp - 1)- 1) + 1
*(cpp - 1)得到c + 2,-1得到c+1
然后解引用得到N的地址,+1得到E的地址,最后打印EW
答案: