前言:人们总说指针是c语言的灵魂,是因为指针的使用技巧是“千姿百态”的,程序员可以通过指针来直接访问内存,这就赋予了它功能的多样性以及更多意想不到的编程技巧与方式,在本篇文章中,笔者就给大家带来指针使用的更多高级技巧
目录
一.指针数组
指针数组的用处
二.数组指针
一般形式
三.函数指针
一般形式
四.函数指针数组
一般形式
五.回调函数
什么是回调函数
进阶技巧使用(降低代码冗余度)
方法一:函数指针数组法
完整代码
方法二:回调函数法
完整代码
首先我们得明确指针数组究竟是指针还是数组
整形数组 —— 装有整形数据的数组
浮点型数组 —— 装有浮点型数据的数组
字符型数组 —— 装有字符型数据的数组
指针数组 —— ?
按照上面的逻辑,我们就可以判断出指针数组是数组,只不过里面装的是指针类型的数组
int* arr[10] = { NULL };//整形指针数组,里面放的是空指针
int* arr1[10]; //整形指针的数组
char* arr2[4]; //一级字符指针的数组
char** arr3[5];//二级字符指针的数组
指针数组最大的一个用处就是可以用来构造二维数组,试曾设想,我们创造了一列数组,这个数组中每一个元素都是一个地址,而数组也是有地址的,换言之,我们可以将这一列中的的每一个元素放入不同数组的地址,每个元素又代表了一行数组,这样就构造成了一个二维数组。大概图示如下:
我们创建了一个名为 Arr 的指针数组,其中放了6个地址,分别是6个数组的地址,那我们就相当于完成了一个二维数组的构建
同样,我们也得明确数组指针到底是数组还是指针
整形指针 —— 是一个指针,指向一个整形变量
浮点型指针 —— 是一个指针,指向一个浮点型变量
结构体指针 —— 是一个指针,指向结构体变量
数组指针 —— ?
我们推断出数组指针是一个指针,指向一个数组
返回类型 ( * 指针名 )[数组大小]
数组指针可以指向一整个数组,而不在局限与访问数组中一个元素的地址,这样说起来可能有点不明确,那我们写个程序如下,观看一个输出的结果
#include
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr + 1);
printf("&arr+1= %p\n", &arr + 1);
return 0;
}
在研究这个问题之前,我们必须知道一下的基础知识
在上述代码中,直接使用数组的名字,是在访问数组的首元素的地址
printf("arr = %p\n", arr); printf("arr+1 = %p\n", arr + 1);
而使用 &数组名,则是在访问整个数组的地址,整个值与首元素的地址是一样的,但是意义完全不一样
printf("&arr= %p\n", &arr); printf("&arr+1= %p\n", &arr + 1);
上述代码输出结果如下:
我们可以观察出,使用取值符的时候,访问地址加一,地址值加了40,也证明了上述结论
而指针数组的使用就可以让我们更好的实现上述对整个数组操作的需求,方便了后续诸如函数指针数组的更高级的使用技巧(后文会介绍到,这里就不再继续赘述)
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int(*p)[10] = &arr;
//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码
return 0;
}
诸如上述数组指针的推理判断,函数指针也是指针,只不过指向的是函数,常量,变量,数组我们都已经充分了解到这些数据都是有地址的,那函数有地址吗?我们做以下测试
#include
void test()
{
printf("this is a test\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
输出结果如下,我们可以看见函数是有地址的,我们也可以取到这个地址,同时函数名也代表了地址
返回类型 ( * 指针名) (函数参数类型)
既然函数有地址,那我们就可以使用指针来保存,我们具体实现如下:
void test()
{
printf("this is a test\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
void (*p)() = &test;
printf("%p\n", p);
return 0;
}
我们试运行一下看看,可以看见第三行的输出确实是使用了指针p对函数进行了访问
对于函数指针数组,主语是数组,所以他的本质就是一个数组,只不过在这个数据结构中,我们需要用到上述铺垫的知识,我们将不同函数的地址放进同一个数组中,可以帮助我们更高效的完成程序。
返回类型 ( * 指针名[数组大小] )(函数参数类型)
在使用函数指针数组的情况下,我们就可以用数组来调用函数了,在部分程序中,可以有非常高的效率,以下笔者仅做简单示例
void F1()
{
printf("F1\n");
}
void F2()
{
printf("F2\n");
}
void F3()
{
printf("F3\n");
}
int main()
{
void (*pf[3])() = { F1,F2,F3 };
for (int i = 0; i < 3; i++)
{
pf[i]();
}
return 0;
}
输出结果中显示我们确实成功的使用数组,通过下标的方式对函数进行了调用
回调函数就是一个通过函数指针调用的函数 。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
如下图所示,在程序 x 中放有函数 f,并且函数 f 中又有函数指针,而函数 f 又可以将函数 1,函数 2,函数 3,函数 4的地址作为参数,当我们通过函数 f 中的函数指针进行调用函数 1~4 的时候,我们就叫做回调函数
接下里,笔者就通过计算器小程序,来对以上进阶技巧进行演示
首先,我们按照一般的算法思路进行编程,设计个计算器小程序,那么最基本的加减乘除是肯定要包含的,那我们就分别写出加减乘除的函数,然后再分别调用他们
诸如下面代码,我们可以发现,这个程序是非常的臃肿的,代码冗余度非常高,有很多重复且效率不高的代码,目前还只是4个功能,要是以后需求变大变多,我们就又要加入 case 语句,程序会变的越来越长
#include
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
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 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;
}
然后我们将这4个函数封装在数组里面,数组中之所以第一个元素放空指针,是为了和菜单中的数字对应起来
int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
// 0 1 2 3 4
void menu()
{
printf("****************************\n");
printf("*** 1. add 2. sub ***\n");
printf("*** 3. mul 4. div ***\n");
printf("*** 0. exit ***\n");
printf("****************************\n");
}
在运算部分,我们使用函数指针数组,将输入的数字作为数组的下标来访问对应的函数,并且将值赋给 ret
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("ret = %d\n", ret);
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 ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
//函数指针数组 - 转移表
int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
// 0 1 2 3 4
if (0 == input)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("ret = %d\n", ret);
}
else
{
printf("选择错误,重新选择!\n");
}
} while (input);
return 0;
}
在完整代码中,我们明显会发现,尤其是在主函数中,省去了很多冗余的代码,看起来精简了很多
我们可以使用下面这种结构来完成设计
我们建立一个函数,将加减乘除的函数地址作为参数传进来调用
void calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
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;
}
void calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入2个操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("ret = %d\n", ret);
}
int main()
{
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;
}
我们可以看出来,函数冗余度还是降低了不少的
本次分享就到此为止了,如有错误,欢迎积极指出,感谢您的支持