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

编程入门 行业动态 更新时间:2024-10-24 07:30:55

【C++基础】第6章:<a href=https://www.elefans.com/category/jswz/34/1771370.html style=函数"/>

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

函数

  • 1 函数基础
    • 1.1 函数:封装了一段代码,可以在一次执行过程中被反复调用。
      • 1.1.1 函数头(如上图第4行)
      • 1.1.2 函数体(如第一张图的5~7行)
    • 1.2 函数声明与定义
    • 1.3 函数调用
    • 1.4 拷贝过程的(强制)省略
    • 1.5 函数的外部链接
  • 2 函数详解
    • 2.1 参数
      • 2.1.1 函数可以在函数头的小括号中包含零到多个形参
      • 2.1.2 函数传值、传址、传引用
      • 2.1.3 函数传参过程中的类型退化
      • 2.1.4 变长参数
      • 2.1.5 函数可以定义缺省实参
      • 2.1.6 main 函数的两个版本
    • 2.2 函数体
      • 2.2.1 函数体形成域
      • 2.2.2 函数体执行完成时的返回
        • 2.2.2.1 隐式返回
        • 2.2.2.2 显式返回关键字: return
        • 2.2.2.3 小心返回自动对象的引用或指针
        • 2.2.2.4 返回值优化( RVO )—— C++17 对返回临时对象的强制优化
    • 2.3 返回类型
      • 2.3.1 返回类型表示了函数计算结果的类型,可以为 void
      • 2.3.2 返回类型的几种书写方式
        • 2.3.2.1 经典方法:位于函数头的前部
        • 2.3.2.2 C++11 引入的方式:位于函数头的后部
        • 2.3.2.3 C++14 引入的方式:返回类型的自动推导
        • 2.3.2.4 扩展:使用 constexpr if “ 构造具有不同返回类型” 的函数
      • 2.3.3 返回类型与结构化绑定( C++ 17 )
      • 2.3.4 [[nodiscard]] 属性( C++ 17 )
  • 3 函数重载与重载解析
    • 3.1 函数重载:使用相同的函数名定义多个函数,每个函数具有不同的参数列表
    • 3.2 编译器如何选择正确的版本完成函数调用 ?
    • 3.3 名称查找(广义重载解析的第一步)
      • 3.3.1 限定查找( qualified lookup )
      • 3.3.2 非限定查找( unqualified lookup )
      • 3.3.3 实参依赖查找( Argument Dependent Lookup: ADL )
      • 3.3.4 函数模板、类模板的实参依赖查找(ADL)
    • 3.4 重载解析:在名称查找的基础上进一步选择合适的调用函数
      • 3.4.1 过滤不能被调用的版本 (non-viable candidates)
      • 3.4.2 在剩余版本中查找与调用表达式最匹配(实参和形参进行匹配)的版本,匹配级别越低越好(有特殊规则)
  • 4 函数相关的其它内容
    • 4.1 递归函数:在函数体中调用其自身的函数
    • 4.2 内联函数
    • 4.3 constexpr 函数 (C++11 起 )
    • 4.4 consteval 函数 (C++20 起 )
    • 4.5 constexpr 函数 / consteval 函数与内联函数的关系
    • 4.6 函数指针
    • 4.6.1 函数类型
      • 4.6.1.1 数组类型和函数类型的异同
    • 4.6.2 函数指针类型
      • 4.6.2.1 数组的指针类型
      • 4.6.2.2 函数的指针类型
      • 4.6.2.3 函数指针类型继续和数组比较
    • 4.6.3 函数指针与重载
    • 4.6.4 将函数指针作为函数参数
    • 4.6.5 将函数指针作为函数返回值
    • 4.6.6 小心: Most vexing parse最令人苦恼的解析

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. 可变长度模板参数

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

更多推荐

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

本文发布于:2024-03-09 15:27:14,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1725368.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:函数   基础

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!