【c语言进阶】函数与指针有什么联系?函数指针又是什么东东?快来深入学习吧!

目录

前言:

一、数组参数:

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 <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
}

思考:
当函数的参数为二级指针的时候,可以接收什么参数?

 答:函数可以接受:二级指针,一级指针的地址,数组数组的地址。

三、函数指针:

        我们都知道,在我们的程序中,各种值和组成成分都有自己的一片空间,我们的自定函数也不例外。

        那么当我们想要将函数的地址储存起来时,又该如何进行处理呢?

数组指针:指向数组的指针

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.函数指针数组在计算器中的用途:转移表,可以减少大量冗余代码,并且可以随时加函数。

今天我们学习了数组参数、指针参数以及函数指针的相关知识,希望我的文章和讲解能对大家的学习提供一些帮助。

辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!

  • 24
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小参宿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值