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

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

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

目录
相关文章
|
19天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
39 10
|
19天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
42 9
|
19天前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
31 8
|
19天前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
40 6
|
19天前
|
存储 C语言
【C语言】输入/输出函数详解
在C语言中,输入/输出操作是通过标准库函数来实现的。这些函数分为两类:标准输入输出函数和文件输入输出函数。
106 6
|
19天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
48 6
|
19天前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
27 5
|
29天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
101 13
|
2月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
36 0
|
3月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
128 4