0基础C语言保姆教程——第4节 函数

简介: 我们还需要知道递归的价值:函数递归会使用大量的空间,因为它会不停地调用某一个函数,但是在递归的时候,内存中为上一级函数开辟的空间并不会被销毁,因为该函数还没有结束。所以,在递归下一级函数的时候,内存就必须要为其在新的地方重新开辟一块空间来。

0基础C语言保姆教程——第4节 函数


目录


C语言中函数可以分为两大类,分别是:库函数,自定义函数。


库函数


什么叫库函数


为什么会有库函数


自定义函数


传值调用与传址调用


函数的嵌套调用和链式访问


函数的嵌套调用


函数的链式访问


函数的声明与定义


函数的声明


函数的定义


函数的递归


我们总结出递归的两个必要条件:


C语言中函数可以分为两大类,分别是:库函数,自定义函数。

库函数

什么叫库函数

库函数,其实也叫内置函数,就是其他人已经写好、为你封装好、你可以直接拿来用的函数。例如,我们之前的printf函数、scanf函数、strlen函数等等都是库函数(注意,这里特别强调,sizeof是关键字,不是函数!)


为什么会有库函数

原因很简单,对于一个项目在开发的过程中,那些常见的、经常被程序员拿来使用的函数,我们可以将其提供出来,让每一个程序员都能方便地用到,支持代码的可执行性,提高代码的执行效率。所以C语言提供了一系列库函数。


这样的话,比如当一个程序员在想使用printf的时候,就不需要频繁的对其定义,不需要每个人都哐哐写一堆让它怎么实现了。


那么,我们可以怎么学习库函数呢?


我们可以通过登录相关网站以及查阅相关文献来学习。


比如:我们可以点击下面的网站:


www.cplusplus.com


点开一看,咵~~~(图一)

image.png



(图一)


好家伙,这么多。


都要记住么?


no。


我们可以简单的总结一下,常见的库函数有:


IO函数(输入输出函数)


字符串操作函数


内存操作函数


时间/日期函数


数学函数


其他库函数。


别急,这些函数,我们在后面的章节中都会介绍到。


其实,还有一些其它的网站,也是很好的学习资源。


我们在也这里给出来:


MSDN (Microsoft Developer Network)(这个大家可以上网搜,下载安装包)


我们以MSDN为例,来具体的操作一下,看看怎么借助这些资源、网站来学习这样的库函数。(其他的网站也是大同小异)(如图二)

image.png



(图二)


我们在查找栏里面随便输入一个我们要找的库函数,我们这里就以strlen为例。

image.png



(图三)


如上图(图三)所示:


那么,我们具体来分析一下,如下图 (图四):

image.png



 (图四)


下面的实际上正是函数的具体的调用方法(图五):


(图五)


自定义函数

如果库函数能干所有的事情,还要自定义函数干嘛?


所以,库函数显然是不能把所有的事情都包圆的。我怎么知道你要实现的具体功能到底是什么,并且不同的人的需求区别也都大了去了。所以这个时候,自定义函数登场了——它相较于库函数而言更加灵活,可以实现自己想要实现的功能。与此同时,这也给了程序员很大的发挥空间。


自定义函数和库函数一样,有函数名,函数返回类型以及参数的类型。


关于自定义函数,我们在第二节实际已经提到过了。我们在这里再来说一遍:


具体的格式是这样的(如图六) :


image.png


(图六)


在花括号里面便可以实现自己想要实现的代码内容。


还是那个Add函数的例子(如图七):

image.png



(图七)  


(注:这个例子十分典型,我当时一次性把最终版做出来了,我比较懒,


直接弄过来了。不想再重画一遍这个例子了,还请见谅哈哈。当然我们下面会有新的例子~~~~)


ok。我们再来说说函数的调用的方法:


传值调用与传址调用

掌握了函数的基本使用方法后(也就是说你可以咋你的电脑上正确地敲出add函数来的时候),我们需要对于函数的调用作更深层次地讨论:


我们先来看这么一个例子:


我们的目标是要实现一个函数来交换两个变量的值:(即交换变量a,b的值)(如图八)

(图八)  

我们先来分析一下这个swap函数(如图九)

(图九)


那结果是不是我们想要的那样,把值给交换过来了呢?

我们运行一下试试看:(如图十)

(图十)

我们神奇地发现,交换前后a,b的值并未发生任何改变。这是怎么回事?是我们交换变量的值的时候逻辑错了么?显然不是。这就和函数传参的特点有关系了。


实际上,函数在传参时,我们会为形参重新开辟一份新的内存空间。(别告诉我形参不知道是啥~~哈哈,如果不知道去看一下第二节,找到函数的部分)那么,函数中实参的值会赋给形参。0基础C保姆自学 第二节——初步认识C语言的全部知识框架_jxwd的博客-CSDN博客


具体来说,可以是这样的:


在创建函数的形参和实参时,我们可以理解成  为函数的参数  在栈区上开辟了空间。(如图十一)

(图十一)


所以,我们有着这样一句标准的说法:


函数的形参是实参的临时拷贝,改变函数的形参不会对实参产生影响。


函数的形参是实参的临时拷贝,改变函数的形参不会对实参产生影响。


也就是说,我们刚刚的交换操作只是对pa,pb进行的,完全不干a,b的事情。


不信?我们可以通过调试来看一下具体的情况:


’按F11(或者F10,但F10在调试的过程中不会进入你自己写的函数)打开调试->窗口->监视->监视1(2,3,4也行,随你便)我们在监视框内输入:a,b,pa,pb,不断按f11,让代码一步一步走,来观察这四个值的变化:(如下图(仅展示了关键的步骤))



(图十二)  


我们可以清晰地看到(图十二),当函数执行完毕后,pa,pb的值发生了交换,单数a,b的值没有发生任何改变。


这个实际上,就是函数的传值调用。


那么我们就是想要通过函数来交换怎么办呢?


我们可以采用传址调用的方法:


请看:(图十三)  

(图十三)  


这个时候,我们可以选择用指针来去实现(有关指针的初步介绍已经在第二节探讨过)


这样写具体的意思是什么呢?


我们可以这样来理解:(图十四)

(图十四)


首先, 我们分别输入&a和&b,获得到a和b的地址。在本次编译中,我们获得到


a的地址为0x00F3FDEC


b的地址为0x00F3FDE0


ok,我们用刚刚拿到的地址来接着分析(图十五):

image.png



(图十五)


这样,我们的pa,pb就拿到的是a,b的地址。


我们后面的*pa是通过拿着a的地址,找到a中存储的变量的值。(*pb同理)(指针相关的知识在这里不再赘述,详见第二节,当时已经做好了铺垫,在这里主要介绍传址调用)


这样,我们接下来进行的交换变量的值所做的操作,正是对a,b本身进行操作的。


因此,当我们再次打印a,b变量的值的时候,得到的便是交换后的值。


总结一下:


还是那句话,函数的形参是实参的临时拷贝,改变函数的形参不会对实参产生影响。


当我想要对实参进行操作的时候,我必须要把其地址传过去,这样在函数中,便可以通过指针解引用来找到我想要操作的变量。


函数的嵌套调用和链式访问

函数的嵌套调用

其实就是字面意思:在函数中调用函数。


举一个非常简单的例子:(图十六)

(图十六)


请问会打印什么?


答:三行hehe。原理很简单,和我们之前add函数的调用的思路是一样的。


微信图片_20221208150818.png


(图十七)


这样是完成了一次hehe的打印(图十七)。然后继续进入循环,总共完成3次hehe的打印。


再来看链式访问:


函数的链式访问

什么叫链式访问?我们可以通过下面一个例子来说明:

(图十八)  

像这样(图十八),把一个函数的返回值作为另外一个函数的参数的形式,叫做函数的链式访问。

那么请问,上面(图十八)的结果是什么?

我们可以按住ctrl(+fn)+f5让代码运行(图十九)。

(图十九)  

为什么?其实只要弄明白一点就很简单了:就是printf的返回值是什么?

不会有人会问:难道printf还有返回值?

问的好!我们就来看看printf到底有没有返回值。

我们选中printf,右击鼠标,点击转到定义,(图二十)

(图二十)

就会看到这么一坨东西:(图二十一)

(图二十一)


其它什么都不认识,就认识printf和int 就够了。看到没?返回类型为int(因为其它的又是一个又一个的定义)


还不服?我们打开msdn,输入printf(图二十二) :

image.png

(图二十二)  


看,上面清清楚楚地写着返回值为int。


那好,既然是int ,那应该返回多少呢?


在msdn刚刚的界面里往下拉,看到这一行(图二十三):

image.png



(图二十三)  


简单翻译一下,就是说它的返回值是“the number of characters printed",即打印的字符个数。(如果发生错误则返回一个负数)


那么我们再看刚刚的那个例子,应该就很明确了(图二十四) :


(图二十四)  


所以,在屏幕上打印的应该为4321。


函数的声明与定义

函数的声明

函数声明的作用是什么?


就是告诉编译器,我有这么一个函数。函数的名字叫什么,参数的类型是什么,返回的类型是什么……


但是,是否真的存在这么个函数,函数的声明决定不了。那是由什么决定?由马上我们所说的函数的定义决定。


函数的声明要放在函数使用之前。


函数的声明一般放在头文件中。满足先声明,后使用。


函数的定义

函数的功能的具体实现,就是函数的定义的过程。


我们把函数的声明和定义结合起来,举一个例子(图二十五):

(图二十五)

至于分文件操作,我们等到写扫雷的时候再来具体实现。


函数的递归

我们再来说最后一个——函数的递归


函数的递归是函数的嵌套调用的一种特殊形式:自己调用自己。俗称套娃。


来看一个例子:(图二十六)

(图二十六)

来思考一下,若我输入123,会输出什么呢?(图二十七)  

(图二十七)  

这就是函数的递归调用所实现的。

我们来具体的分析一下:(我们把print函数复制了两份来演示,便于读者理解)

(如下图) (图二十八)

(图二十八)

好。那么递归能不能这样呢?(图二十九)

(图二十九)

不断地调用main函数自己本身?

理论上这是可以的。但是这个程序永远都停不下来。因为其将会无限次地递归下去。


所以,为了避免这样死循环的局面出现,


我们总结出递归的两个必要条件:

1、存在限制条件,当满足这个限制条件的时候,递归便不再继续。


2、每次递归调用之后越来越接近这个限制条件。


上面的例子中,就是因为调用main函数没有条件限制,所以会导致死循环。


另外,我们还需要知道递归的价值:函数递归会使用大量的空间,因为它会不停地调用某一个函数,但是在递归的时候,内存中为上一级函数开辟的空间并不会被销毁,因为该函数还没有结束。所以,在递归下一级函数的时候,内存就必须要为其在新的地方重新开辟一块空间来。


但是,递归有利于将复杂的问题简单化。采用大事化小的思考方式,大大便捷了思考的过程。


所以,在日后的代码实现中,是否使用递归,还需要根据具体的情况来定。



目录
相关文章
|
6天前
|
存储 C语言
【C语言函数】static和extern关键字修饰
【C语言函数】static和extern关键字修饰
|
7天前
|
C语言 C++
|
15天前
|
机器学习/深度学习 C语言
九/十:《初学C语言》— 扫雷游戏实现和函数递归基础
【8月更文挑战第5天】本篇文章用C语言采用多文件编写实现了一个基础的扫雷游戏(附源码),并讲解了关于函数递归的基础概念及其相对应的习题练习(附源码)
29 1
九/十:《初学C语言》— 扫雷游戏实现和函数递归基础
|
17天前
|
存储 编译器 程序员
八:《初学C语言》— 函数的基本概念
【8月更文挑战第3天】本篇文章详细讲解了库函数与自定义函数的区别、函数的嵌套调用及链式访问、函数的声明和定义、static和extern等基础知识
17 1
八:《初学C语言》— 函数的基本概念
|
1天前
|
编译器 程序员 C语言
【C语言篇】从零带你全面了解函数(包括隐式声明等)(下篇)
⼀般情况下,企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个⽂件中。
|
1天前
|
C语言
【C语言】字符串及其函数速览
【C语言】字符串及其函数速览
11 4
|
4天前
|
测试技术 C语言
C语言中的void函数
C语言中的void函数
|
4天前
|
存储 安全 编译器
C语言中的scanf函数
C语言中的scanf函数
|
4天前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
4天前
|
C语言
C语言中的无参函数
C语言中的无参函数