【C++基础】第6章:函数

函数

1 函数基础

1.1 函数:封装了一段代码,可以在一次执行过程中被反复调用。

4~7行定义了一个函数Add,在11行调用函数Add:
在这里插入图片描述
在这里插入图片描述

1.1.1 函数头(如上图第4行)

  1. 函数名称(如上图的Add)——标识符,用于后续的调用
  2. 形式参数(上图的x和y)——代表函数的输入参数
  3. 返回类型(如上图的int)——函数执行完成后所返回的结果类型

1.1.2 函数体(如第一张图的5~7行)

函数体是一个语句块( block )(需要带有{ }),包含了具体的计算逻辑。
在这里插入图片描述

1.2 函数声明与定义

  1. 函数声明只包含函数头,不包含函数体,通常置于头文件中
    在这里插入图片描述
    上图橙色,既包含函数头也包含函数体,就是函数的定义。

函数的声明:
在这里插入图片描述

  1. 为什么要区分函数的声明和定义?

函数声明可出现多次,但函数定义通常只能出现一次(存在例外,内联函数可以在不同的翻译单元里出现多次,我们只要保证每个翻译单元内出现一次就行)。

下图4~6行是函数声明,可以出现多个函数声明。
在这里插入图片描述
但函数定义不能出现多次:
在这里插入图片描述
声明一般放在头文件(.h)里面:

  1. 把声明放入头文件里:
  2. 在main.cpp里面,我们可以通过#include "xxx.h"引入xxx.h头文件声明
    在这里插入图片描述

1.3 函数调用

  1. 需要提供函数名与实际参数

实际参数用在函数调用;形参用在函数定义里。

  1. 实际参数拷贝初始化形式参数

如下图,x会使用2来拷贝初始化,y会用3来拷贝初始化
在这里插入图片描述

  1. 返回值会被拷贝给函数的调用者
  2. 栈帧结构
    如下图7行,我们在调用函数的过程中,函数可能包含一些参数,变量,所有的这些东西都会放在内存当中,这些东西在内存中是通过栈帧(Frame)结构来组织的。
    在这里插入图片描述
    下图每个方框都叫栈,栈的特点是后进先出(往里面放东西,拿出来时是最后放进去的最先拿出来)。

funcA Frame是一帧,这一帧里面可能包含了funcA所调用需要的一些信息,包括它的形参、变量等。接下来,funcA可能会调用funcB,此时系统会在funA上再开辟一块内存(新的一帧),funB可能会调用funC。。。以此类推

当funC调用结束后,funC这一帧会被扔出去(后进先出),此时funB又活了,系统又funcB这个状态,funcB执行完之后,系统又回到funcA这个状态:
在这里插入图片描述

对于下图代码:main算一帧,main函数里面又调用Add函数(又算一帧),当Add这一帧执行完后出栈了,再回到mian这一帧
在这里插入图片描述
在这里插入图片描述

1.4 拷贝过程的(强制)省略

  1. 返回值优化
  2. C++17 强制省略拷贝临时对象

1.5 函数的外部链接

将c++函数的外部链接转换成c语言的函数外部链接:(但这样就不支持函数重载了)
在这里插入图片描述
在这里插入图片描述

2 函数详解

2.1 参数

2.1.1 函数可以在函数头的小括号中包含零到多个形参

0个形参:
在这里插入图片描述
2个形参:
在这里插入图片描述

  1. 包含零个形参时,可以使用 void 标记
    在这里插入图片描述
    等价于:
    在这里插入图片描述
  2. 对于非模板函数来说,其每个形参都有确定的类型,但形参可以没有名称

形参名称的作用:在函数内我们可以使用这个形参名称来去访问实参所对应的数字,如下图,我们可以使用x来访问fun(1)中的实参1。
在这里插入图片描述
不写形参名称也可以通过编译:(一般这么写是留出接口以作备用)
在这里插入图片描述

  1. 形参名称的变化并不会引入函数的不同版本

这样不算引入不同函数版本,依旧算函数重复定义:
在这里插入图片描述

  1. 实参到形参的拷贝求值顺序不定
    如下图,是先用1来初始化z,还是先用2来初始化y,这个初始化的顺序是不确定的。
    在这里插入图片描述
    由于这种不确定性,如果我们写出一下这样的代码会很危险:(不同编译器下y输出结果可能不同)
    在这里插入图片描述
  2. C++17 强制省略复制临时对象

我们在调用10行的1和2时,会把1拷贝给z,2拷贝给y,
在这里插入图片描述
但有一种情况:
在这里插入图片描述
橙色相当于建立了一个临时对象。当我们将这个临时对象拷贝给y时,c++17标准而言,我们会把这个拷贝的过程强制省略掉,也即并不会把int{}拷贝给y。

2.1.2 函数传值、传址、传引用

  1. 传值
    下图在fun调用后,arg的值不会发生改变。这样的行为叫传值:(只是把arg的值传给了par)
    在这里插入图片描述
    因为,上图等价于:
    在这里插入图片描述

  2. 传址
    在这里插入图片描述
    在这里插入图片描述
    等价于:
    在这里插入图片描述

  3. 传引用
    在这里插入图片描述
    在这里插入图片描述
    上图,par被绑定到arg上,那么接下来对par的任何修改,都会影响arg的值。

2.1.3 函数传参过程中的类型退化

之前我们定义一个数组(10行),然后11行中的b并不是指代数组,b的类型会发生退化,退化为int型指针,指向a数组中的第一个元素,这就是拷贝初始化中引入的自动类型退化:
在这里插入图片描述

  1. 实际上我们调用函数时传入参数,这也是拷贝初始化的过程。因此,如果我们这样写:
    在这里插入图片描述
    上图代码是合法的。(我们可以用a来拷贝初始化par)。

对于函数调用,我们还可以这么写:
在这里插入图片描述
或:
在这里插入图片描述
但实际上,上面这3种写法,编译器都会把par理解成指针。
在这里插入图片描述
在这里插入图片描述

  1. 多维数组
    在这里插入图片描述
    上图ptr的类型是一个指针,但这个多维数组只有最高维才被退化。

与之类似,如果想定义一个函数来接收二维数组,下图代码这么写肯定出错:
在这里插入图片描述
应改为:
在这里插入图片描述
在这里插入图片描述
由上图可知,我们可以使用a来拷贝初始化(*ptr)[4]类型的对象。

当然我们也可以按下图这么写,但是编译器会忽略[3],还是会把par设为一个指针,这个指针指向int[4]这样的数组。
在这里插入图片描述

  1. 如何阻止类型退化?
    在这里插入图片描述
    相应地,我们如果要防止类型退化,我们可以使用引用
    在这里插入图片描述

2.1.4 变长参数

  1. initializer_list(初始化列表)
    在这里插入图片描述
    在这里插入图片描述
    我们可以通过上述方式使得fun函数传入的参数个数发生改变。
    在这里插入图片描述
    关于initializer_list,有两点需要声明:
    (1)initializer_list中的int指initializer_list里面包含的元素类型,如果我们使用initializer_list传递一些变长参数,那么我们传递的这些类型的参数必须是完全相同的。如果传入的参数类型不同(如下图):报错("123"无法转换为int)。
    在这里插入图片描述
    (2)使用initializer_list,通常都如下图橙色这么写,我们不会把它改为initializer_list引用、initializer_list指针。
    在这里插入图片描述
    另外,下图这么写代码非常危险:
    在这里插入图片描述
  2. 可变长度模板参数

传入的参数的类型可以不同。讨论模板时再讲。

  1. 使用省略号表示形式参数(不建议)

2.1.5 函数可以定义缺省实参

可以通过缺省实参来简化函数的调用。
在这里插入图片描述
如上图,fun()里面可能包含很多形参,在调用时,需要为每个形参配一个实参,这个过程很麻烦。因此我们可以为这个函数赋予缺省实参(int x = 0):
在这里插入图片描述
在这里插入图片描述

  1. 如果某个形参具有缺省实参,那么它右侧的形参都必须具有缺省实参

下图代码不合法,因为3行左边的int x = 0为缺省实参,其右边不是缺省实参:
在这里插入图片描述
但这么写合法:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为什么会这样?

缺省值的目的是?缺省值的目的,如下图代码:
在这里插入图片描述
当我们使用fun(1)来调用fun函数时,形参和实参之间是要有匹配的,只有完成匹配之后,我们才能确定函数的具体行为。我们调用fun(1)时,一定要知道我们为x赋予什么值,为y赋予什么值,为z赋予什么值。上图中x没有缺省值,那么我们会把fun(1)中的1赋予给x,y对应其缺省值1,z对应其缺省值2,此时x,y,z都有对应的缺省值。

  1. 具有缺省实参的函数调用时,传入的实参会按照从左到右的顺序匹配形参

那么编译器是怎么完成刚才说的实参和形参的对应的呢?答:传入的实参会按照从左到右的顺序匹配形参

即,上图,1会和x匹配,然后fun(1)里面没有实参了,我们就拿3行的缺省值来匹配y和z。但如果按下图这么写,fun(1)中的1该匹配谁呢?实际上还是会从左到右的优先顺序,给x匹配,那么y就没有实参与之匹配了。故整个代码不合法。

  1. 在一个翻译单元中,每个形参的缺省实参只能定义一次

下图,3行为fun函数的声明,5行为fun函数的定义。但代码报错,这是因为3行和5行都定义了缺省实参。
在这里插入图片描述
应改为:
在这里插入图片描述
以下这样也是合法的:(缺省实参int z = 3int y = 2都只定义了一次)(注意,4行并没有违反“如果某个形参具有缺省实参,那么它右侧的形参都必须具有缺省实参”这个性质,因为z在3行已经缺省实参化了)
在这里插入图片描述
同理这样也没问题:
在这里插入图片描述

但下图违反了“如果某个形参具有缺省实参,那么它右侧的形参都必须具有缺省实参
在这里插入图片描述
但以上的前提,是在同一个翻译单元。

如果是不同翻译单元呢?

我们把mian.cpp中的fun函数放到另一个.cpp文件(fun.cpp)(翻译单元)中:
在这里插入图片描述
在这里插入图片描述

然后在main.cpp里,我们可以不用再去定义fun函数了,可以直接通过引入fun函数的声明,那么在main.cpp里面,我们就可以直接调用fun函数了:
在这里插入图片描述
我们再引入一个翻译单元Source.cpp,在Source.cpp我们还是引入fun函数定义,接下来定义另外一个函数source,该函数里面,我们调用fun函数:
在这里插入图片描述
此时main.cpp中相应加入source函数声明,然后在main函数里面调用source函数:
在这里插入图片描述
以上代码是合法的。但在

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

cashapxxx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值