热门网游活动集合_每日福利更新_玩家互动论坛 - hfhzlhj

【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

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

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

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

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

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

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类型,不需要返回值。

以上就是对指针进阶内容的讲解,希望能帮助到大家,如果有写的不好的地方,欢迎大家指正!!