【C指针详解】进阶篇之——数组传参、指针传参、函数指针、函数指针数组、指向函数指针数组的指针
4. 数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1 一维数组传参
比如,有这样一个一维数组:
int arr[10];//一维整型数组
int* arr2[20];//一维整型指针数组
我们把它们作为参数传给两个函数:
test(arr);
test2(arr2);
那现在函数test和test2的参数应该如何设计呢?
1. 先来看函数test(接收arr):
test函数要接收arr,首先我们想到,arr是一个一维数组,那我们是不是可以用一个同类型一维数组来接收,这当然是没问题的,所以test(假设不需要返回值)的参数可以这样设计:
void test (int arr[10])
当然【】里的10其实可以省略的:
void test (int arr[]),这样也是可以的,因为这里设计的形参我们只是写成数组的形式,本质上还是指针(因为接收的是地址),所以不要求必须指定大小。
然后,
因为arr是数组名,表示的是数组首元素的地址,所以我们当然也可以把直接设计成指针,那传过来的是数组首元素(整型变量)的地址,我们应当用一个整型指针变量来接收:
void test (int* arr)
所以。函数test的形参,我们可以设计成这三种:
void test (int arr[10])
void test (int arr[])
void test (int* arr)
2. 然后我们来看函数test2(接收arr2):
那test2其实还是一个一维数组,只不过是整型指针数组,那我们的参数设计还是用同类型的数组数组,或者用指针:
同类型的指针数组:
void test2(int* arr[20]
void test2(int* arr[]
数组arr2的首元素是一个一级整型指针变量,一级指针的地址我们要用一个二级指针来接收:
void test2(int** arr)
4.2 二维数组传参
那现在我们要把二维数组作为参数传递给函数:
int main()
{
int arr[3][5] = {0};
test(arr);
}
此时,函数test的参数可以如何设计呢?
首先,传过去的是二维数组,我们当然可以用一个同类型的二维数组来接收:
void test(int arr[3][5])
或
void test(int arr[][5]
但注意不能写成int arr[][],因为二维数组的列数是不能省略的,二维数组传参,函数形参的设计只能省略第一个[]的数字。
那然后我们当然也可以用指针接收。
在【3.3 数组指针的使用】我们已经知道了,二维数组的首元素是二维数组的第一行(相当于一个一维数组),所以这里传递的arr其实相当于第一行的地址,是一维数组的地址,既然是数组的地址,当然要用数组指针来接收了。
所以我们可以这样设计:
void test(int (*arr)[5])
4.3 一级指针传参
如果我们调用一个函数传过去的实参是一级指针,那当然要用一个同类型的一级指针作为形参来接收:
比如:
#include <stdio.h>
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\n", *(p + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
那现在我们思考这样一个问题:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
void test1(int *p)
{}
test1函数能接收什么参数?
首先实参传一个同类型的一级指针变量,这肯定是没问题的。
int a=9; int *p=&a; test1(p);
然后我们是不是还可以传一个变量的地址,形参为int *p,当然可以接收一个整型变量的地址了。
int b=0;test1(&b);
那我们是不是还可以传一个一维数组的数组名,因为数组名也是一个地址,是数组首元素的地址,那形参为int *p,当然我们要传一个整型数组的数组名。
int arr[10];test1(arr);
4.4 二级指针传参
若实参为二级指针,那形参应该是同类型的二级指针,这样肯定是可以的。
举个例子:
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
}
那现在我们要讨论的是,当函数的参数(形参)为二级指针时,可以接收什么样的参数(实参)?
看这段代码:
void test(char** p)
{
}
int main()
{
char c = 'b';
char* pc = &c;
char** ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}
调用test函数,我们可以传什么参数?
1.形参为二级指针,实参也传二级指针,这样肯定可以:
test(ppc);
2. 二级指针,当然可以接收一级指针变量的地址:
test(&pc);
我们是不是还可以传一个一级指针数组的数组名,因为它是该数组首元素的地址,即还是一级指针变量的地址:
test(arr);
5. 函数指针
什么时函数指针呢?
函数指针,即指向一个函数的指针,用来存放函数的地址。
5.1函数的地址
那既然要存放函数的地址,那函数的地址怎么来表示呢?
首先,我们已经知道,对于数组来说,比如:
int arr[10]={0};
数组名和&数组名的意义是不同的:
数组名arr表示数组首元素地址,而&arr才是整个数组的地址。
- 那现在如果这里有一个函数,函数的地址要如何表示:
void test()
{
printf("hehe\n");
}
函数test的地址要如何表示呢?
会不会像数组一样,&test表示函数地址呢?那函数名test表示啥呢?函数可没有首元素这一说。
我们来看一段代码:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
我们一起来看一下test和&test打印出来是什么:
输出的是两个地址,且两个地址都是 test 函数的地址。
因为对于函数来说,函数名和&函数名表示的意义是完全一样的,都表示函数的地址。
即函数名==&函数名
5.2函数指针如何书写
那函数指针又应该怎么写呢?
现在有这样一个函数:
int add(int x, int y)
{
return x + y;
}
如果我们要写一个函数指针来存储上面add函数的地址,我们可以这样写:
int (*p) (int,int)=&add;
或
int (*p) (int ,int)=add;
这里的P就是一个函数指针,解释一下:
首先,p和*结合,说明p是一个指针变量,然后该指针指向的是一个函数,函数有两个参数,都是 int 类型,函数的返回值类型也是 int 。
其它类型的函数指针书写也是同样的方法,大家按函数自己的参数类型,返回值类型写就行了。
5.3函数指针如何使用
那么,接下来我们怎么通过函数指针去调用上面的add函数呢?
我们知道,如果我们要通过函数名调用的话,可以这样写:
int ret=add(3,5);
5.4练习
我们一起来阅读两段有趣的代码:
(*(void (*)())0)();
大家思考一下,这段代码是什么意思。
这段代码的效果其实是:调用首地址为0的地址处的函数
给大家解释一下:
我们先来看中间这一部分(void (*)())0的意思:
数字0前面一个括号,括号里面放的是啥,是不是一个函数指针类型啊,首先一个(*)表明是一个指针,指针指向一个函数,该函数没有参数,也不需要返回值(void)。
也就是说将0强制类型转换为一个函数指针。
然后我们再看整个表达式, (*(void (*)())0)();:
其实是 对该函数指针解引用,并调用该函数。
在《C陷阱与缺陷》这本书中提及该代码,我们来看一下:
void (*signal(int , void(*)(int)))(int);
这句代码看起来很复杂,大家思考一下它的意思。
这句代码其实是一个:函数声明
解释一下:
我们直接去看这句代码可能不容易理解,我们可以将这句代码写成这样:
void(*)(int) signal (int,void(*)(int));
函数返回类型 、函数名、 参数类型
这样相信大家很容易就看懂了,就是一个函数声明。
但是我们要知道,这种写法是语法不支持的。
void (*signal(int , void(*)(int)))(int);这句代码看上去可能太复杂了,不过我们可以简化一下它:
我们使用关键字 typedef 对 void(*)(int)进行一个类型重命名。
typedef void(*)(int) pfun_t;,
将void(*)(int)重命名为pfun_t,这样写对吗?
错误的!!!
语法规定正确的写法是这样的:
正确的:typedef void (*pfun_t) (int);
那现在我们就可以这样写了:
pfun_t signal (int, pfun_t);
这句代码同样在《C陷阱与缺陷》中提及:
6. 函数指针数组
6.1如何定义
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组。
比如:
int *arr[10];
//数组的每个元素是int*
那函数指针数组就是存放函数指针(或函数地址)的数组,那函数指针的数组如何定义呢?
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];
这3句代码那一句正确定义了一个函数指针数组?
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组有10个元素,每个元素的类型是 int (*)() 类型的函数指针。
(把数组名及元素个数parr1[10]去掉剩下的就是元素类型。)
6.2函数指针数组的使用
函数指针数组的用途:转移表
比如我们想要写代码实现一个计算器的功能(加减乘除),在没学函数指针数组之前,我们可能会这样写:
#include <stdio.h>
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;
}
使用switch、case语句选择相应的功能,就去调用对应的函数来实现对操作数的加减乘除。
但这样写好像不是特别好。
那有没有更好的办法呢?
当然有,那我们就可以使用函数指针数组去实现。
#include <stdio.h>
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;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************\n");
printf(" 1.add 2.sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
这次代码就没有那么多重复的部分了,更加简洁。
解释一下:我们定义了一个函数指针数组int(*p[5])(int x, int y),5个元素,每个元素是一个函数指针,指向的函数两个参数为int类型,返回类型也是int。
然后对数组初始化:{ 0, add, sub, mul, div }; ,把加减乘除4个函数的地址存入数组。
为啥数组最前面要加一个0呢?
因为我们四个函数对应的选项是1,2,3,4,这样使得它们的下标正好是1,2,3,4。
我们可以通过下标直接找到并调用函数。
7. 指向函数指针数组的指针
什么是指向函数指针数组的指针?
即指向函数指针数组的指针,用来存放函数指针数组的地址。
那 指向函数指针数组的指针 如何定义呢?
举个例子:
#include <stdio.h>
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
函数指针和函数指针数组我们已经知道怎么回事了。
解释一下:void (*(*ppfunArr)[5])(const char*)
首先,ppfunArr和*结合(*ppfunArr),说明它是一个指针。
然后指向一个数组,数组有5个元素(*ppfunArr)[5],每个元素是一个函数指针void (*) (const char*)。
该函数指针指向一个函数,函数一个参数,参数类型为const char* str类型,不需要返回值。
以上就是对指针进阶内容的讲解,希望能帮助到大家,如果有写的不好的地方,欢迎大家指正!!
- 点赞
- 收藏
- 关注作者
评论(0)