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

简介: 【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

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

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

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

目录
相关文章
|
18天前
|
安全 C语言
【C语言】如何规避野指针
【C语言】如何规避野指针
20 0
|
8天前
|
C语言
c语言指针总结
c语言指针总结
14 1
数组指针、函数指针、指针数组、函数 指针数组、指针函数详细总结
数组指针、函数指针、指针数组、函数 指针数组、指针函数详细总结
|
5天前
|
人工智能 测试技术 C语言
C语言学习
C语言学习
13 1
|
8天前
|
搜索推荐 C语言
详解指针进阶2
详解指针进阶2
|
14天前
|
存储 程序员 C语言
【C 言专栏】C 语言指针的深度解析
【4月更文挑战第30天】C 语言中的指针是程序设计的关键,它如同一把钥匙,提供直接内存操作的途径。指针是存储其他变量地址的变量,通过声明如`int *ptr`来使用。它们在动态内存分配、函数参数传递及数组操作中发挥重要作用。然而,误用指针可能导致错误,如空指针引用和内存泄漏。理解指针的运算、与数组和函数的关系,以及在结构体中的应用,是成为熟练 C 语言程序员的必经之路。虽然挑战重重,但掌握指针将增强编程效率和灵活性。不断实践和学习,我们将驾驭指针,探索更广阔的编程世界。
|
15天前
|
算法 搜索推荐 程序员
C语言中的函数指针和回调函数
C语言中的函数指针和回调函数
10 2
|
18天前
|
存储 编译器 C语言
【C语言】初步解决指针疑惑
【C语言】初步解决指针疑惑
7 0
|
19天前
|
C语言
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)
C语言:数组和指针笔试题解析(包括一些容易混淆的指针题目)