目录
前言:
一、数组参数:
1.一维数组传参:
2.二维数组传参:
弄清指针数组和二维数组的区别:
二、指针参数:
1.一级指针传参:
思考:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
2.二级指针传参:
思考:当函数的参数为二级指针的时候,可以接收什么参数?
三、函数指针:
注:&函数名和函数名都是函数的地址
阅读两段有趣的代码:
2.函数指针数组:
下面,如果让家人们写一个计算器,你会如何书写?
四、总结:
博客主页:张栩睿的博客主页
欢迎关注:点赞+收藏+留言
系列专栏:c语言学习
家人们写博客真的很花时间的,你们的点赞和关注对我真的很重要,希望各位路过的朋友们能多多点赞并关注我,我会随时互关的,欢迎你们的私信提问,也期待你们的转发!
希望大家关注我,你们将会看到更多精彩的内容!!!
前言:
前面我们学习了指针与数组的相关知识,那么指针和函数又有什么关系呢?你知道指针和数组是如何传参的吗?你知道函数指针如何使用吗?在这节课中我们继续学习指针的进阶部分知识,继续向更高阶升级我们的指针,希望能对大家的学习有所帮助!
我们在写代码的时候难免要把数组或指针传给函数,那函数的参数该如何设计呢?
一、数组参数:
在之前我的五子棋游戏中,常常会需要将【数组】或【指针】作为参数传递给函数,于是我们在复杂情况下还需要考虑函数参数的设计。
1.一维数组传参:
我们都知道,在对数组进行传参时并不会真实的在内存中创建临时数组,数组名的意义是数组的首元素的地址,因此当函数参数为数组名时,实际上传递的是数组中首元素的地址,于是我们可以发现下面三种传参方式都是可行的:
这三种将形参写作数组形式的方式都是可行的,但是推荐大家尽可能的使用第一种方式,其次是第二种方式。第三种方式虽然也可以运行,但有可能会出现难以预料的错误。
同时,以上三种将形式参数写成数组形式的写法,也可以改写为使用指针做形式参数的形式:
方式1:标准传参方式
void test(int arr[10])//ok?
{}
方式2:省略数组大小
//形参部分的数组大小可以省略
void test(int arr[])//ok?
{}
方式3:使用指针作为形式参数:
void test(int *arr)//ok?
一级指针:
{}
方式4:用指针数组接受
void test2(int *arr[20])//ok?
{}
方式5:二级指针接收,因为指针数组传的是首元素地址,而首元素地址是一个指针的地址,所以用二级指针接受。
void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2)
}
2.二维数组传参:
我们知道,二维数组在进行传参时可以不知道有多好行,但必须知道有多少列,这样计算机才知道应该在何时进行换行。如此只要知道了什么时候进行换行,对于行数就不需要再进行强制要求了,所以在进行传参时,允许写成以下形式:
方式1:
标准数组传参:ok
void test(int arr[3][5])//ok?
{}
省略了列:NO
void test(int arr[][])//ok?
{}
方式2:
省略行没有省略列:ok
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
二维数组传的是第一行的地址,第一行的地址则说明是一个数组指针来接受。所以:NO
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
方式3:
用数组指针来接受:ok
void test(int (*arr)[5])//ok?
{}
用二级指针来接受:NO
void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr)
弄清指针数组和二维数组的区别:
这里要额外注意的是,二维数组与指针数组不同,不可以使用二级指针进行传参。二级指针的作用是用于存储一级指针的的地址。首先我们要知道,数组指针传参传的是首元素地址,也就是数组里面的第一个指针的地址,他传的就是一个二级指针。而二维数组的首元素地址是二维数组第一行(这里可以简单理解为一个一维数组,但本质上不是)的地址,这个第一行的数组类型是整形,传过去的是一个一级指针,所以不能用二级指针存储。
在这里我们就更加能明白二维数组和指针数组的异同:
同:
他们在访问数据的方法是相同的。都可以用[][]下标访问。
异:
他们虽然用法相似,但是其实类型是不同的,指针数组的第一个【】是说明访问数组中的第几个指针,第二个【】是访问该指针内的内容。而二维数组的第一个【】是说明访问第几个数组(即第几行数组),第二个【】是访问该行数组的第几个元素。而且指针数组的每一个指针地址是不连续的,而二维数组的每一行数组却是连续的。
二、指针参数:
1.一级指针传参:
当我们在函数调用,并使用一级指针作为参数时,很容易理解:一级指针 p 中存放的是数组 arr 中首元素的地址,即传址做参,于是我们就可以在函数参数设计时,使用一级指针进行接收,就可以达到我们的目的。
void test(int* p)
//传递的是一级指针,存储的是arr首元素的地址,使用一级指针进行接收
{
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *p + i);
}
}
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
//数组名为首元素地址
test(p);
//等价于:test(arr);
return 0;
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
答:函数可以接受:数组首元素地址,一个变量的地址,一个一级指针。
2.二级指针传参:
首先最基础的用法很好理解,无非是传递二级指针,就使用二级指针进行接收,无需过多阐述。我们直接上示例即可:
#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
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
答:函数可以接受:二级指针,一级指针的地址,数组数组的地址。
三、函数指针:
我们都知道,在我们的程序中,各种值和组成成分都有自己的一片空间,我们的自定函数也不例外。
那么当我们想要将函数的地址储存起来时,又该如何进行处理呢?
数组指针:指向数组的指针
int (*p)[10],这里我们变量类型是*p,指向的是int [10].
同理:
函数指针:指向函数的指针
我们知道应用函数的时候是
int add(int x,int y)
所以我们的函数指针也应该和这个形式相对应:
int (*p)(int,int)=&add/add;
使用:
int ret=p(x,y);
这里表示:返回类型为int,两个参数类型为int。pf是一个存放函数地址的指针变量
注:&函数名和函数名都是函数的地址
阅读两段有趣的代码:
//代码1 (*(void (*)())0)();
不难看出:
该代码是一次函数调用调用0地址处的一个函数
首先
代码中将0强制类型转换为类型为void (*)()的函数指针,
然后
去调用0地址处的函数
//代码2 void (*signal(int , void(*)(int)))(int);
该代码是一次函数的声明
声明的函数名字叫signal(名字先与()结合)
signal函数的参数有2个,第一个是int类型,第二个是函数指针类型,该函数指针能够指向的那个函数的参数是int,返回类型是void
signal函数的返回类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回类型是void。
很明显这段代码的可读性非常差,理解起来非常麻烦,于是我们可以通过使用函数指针来提升我们代码的可读性,这样也便于理解:
typedef void(*pf_t)(int); pf_t signal(int,pf_t);
我们将函数指针 void(*)(int)的类型重定义为pf_t,然后在申明函数的时候就一目了然了。
2.函数指针数组:
由前面所学知识,根据他的名字我们可以轻易知道,对于函数指针,可以使用函数指针数组存储多个函数指针:
指针数组
char* ch[5];
pa是数组指针
int (*pa)[10] = &arr;
pf是函数指针
int (*pf)(const char*) = &my_strlen;
函数指针数组
int (*pfA[5])(const char*) = { &my_strlen};
根据我之前的命名方法,pfA先与[]结合,说明类型为数组,什么数组呢?包含函数指针的数组。
下面,如果让家人们写一个计算器,你会如何书写?
我相信大部分人是这样写的:
#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"); breark; default: printf( "选择错误\n" ); break; } } while (input); return 0
是的,作为一个初学者,这样写很正常,但是我们发现,代码中出现了很多重复的代码,这就是我们学习函数指针数组的用途:转移表!
下面我们来看看函数指针数组的实现计算器:
#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 (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div }中添加函数名、修改成员数即可。
四、总结:
本文讲解的注意知识点有:
1.数组与指针如何传参
2.二维数组与指针数组的异同
3.函数指针的介绍
4.函数指针数组在计算器中的用途:转移表,可以减少大量冗余代码,并且可以随时加函数。
今天我们学习了数组参数、指针参数以及函数指针的相关知识,希望我的文章和讲解能对大家的学习提供一些帮助。
辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!