前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >第七章 函数

第七章 函数

作者头像
小飞侠xp
发布2023-10-15 13:41:42
1690
发布2023-10-15 13:41:42
举报

第七章 函数

函数基础

  • 函数:封装了一段代码,可以在一次执行过程中被反复调用。
    • 函数头
      • 函数名称——标识符,用于后续的调用
      • 形式参数——代表函数的输入参数
      • 返回类型——函数执行完成后所返回的结果类型
    • 函数体
      • 为一个语句块(block),包含了具体的计算逻辑
  • 函数声明与定义
    • 函数声明只包含函数头,不包含函数体,通常至于头文件中
    • 函数声明可出现多次,但函数定义通常只能出现一次(存在例外)
  • 函数调用
    • 需要提供函数名与实际参数
    • 实际参数拷贝初始化形式参数
      • argument——>实参
      • parameter——>形参
    • 返回值会被拷贝给函数的调用者
    • 栈帧结构
      • Frame(帧),每一个function按栈帧在memory中堆放,先入后出;
代码语言:javascript
复制
int Add(int x, int y)
{
    int x1 = x + 1; // 局部变量
    return x + y;
}

int Sub(int x, int y)
{
    return x - y;
}

int main()
{
    int z = Add(2, 3);
    std::cout << z << std::endl;
    
    z = Sub(2, 3);
    std::cout << z << std::endl;
}
  • 拷贝过程的(强制)省略
    • 返回值优化
    • C++17强制省略拷贝临时对象
  • 函数的外部链接——mangle&demangle
代码语言:javascript
复制
// ---------------------------------------------------------------------------------------------------------
extern "C"  //C类 型函数
int Add(int x, int y)
{
    return x + y;
}

函数详解

参数

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

包含零个形参时,可以使用void标记

对于非模板函数来说,其每个形参都有确定的类型,但形参可以没有名称

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

实参到形参的<u>拷贝求值顺序不定</u>,C++17强制<u>忽略复制临时对象</u>

代码语言:javascript
复制
#include <iostream>

void fun(int x, int y)
{
    std::cout << y;
}

int main()
{
    fun(1, 2);       // 拷贝求值顺序不定
    fun(1, int{});   // 临时变量会被C++17标准强制忽略,C++17标准之前由编译器决定
}
  • -fno-elide-constructors忽略C++11(C++17标准之前)中对复制临时对象强制忽略的约束

函数传值、传址、传引用

代码语言:javascript
复制
#include <iostream>

void fun(int par)
{
    ++par;
}

int main()
{
    int arg = 3;
    fun(arg);
    std::cout << arg << '\n';   // 传值
}
代码语言:javascript
复制
#include <iostream>

void fun(int* par)
{
    ++(*par);
}

int main()
{
    int arg = 3;
    fun(&arg);
    std::cout << arg << '\n';   // 传址
}
代码语言:javascript
复制
#include <iostream>

void fun(int& par)
{
    ++par;
}

int main()
{
    int arg = 3;
    fun(arg);
    std::cout << arg << '\n';   // 传引用
}

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

代码语言:javascript
复制
#include <iostream>

void fun(int par[])   // void fun(int* par)
{
    // ...
}

int main()
{
    int a[3];
    fun(a);
}

变长参数

initializer_list

代码语言:javascript
复制
#include <iostream>
#include <initializer_list>

void fun(std::initializer_list<int> par)
{
    // ...
}

int main()
{
    fun({1, 2, 3, 4, 5});      // right
    fun({1, 2, 3, "123", 5});  // wrong
}

可变长度模板参数

使用省略号表示形式参数

函数可以定义缺省实参

  • 如果某个形参具有缺省实参,那么它右侧的形参都必须具有缺省实参
  • 在一个翻译单元中,每个形参的缺省实参只能定义一次
  • 具有缺省实参的函数调用时,传入的实参会按照从左到右的顺序匹配形参
  • 缺省实参为对象时 ,传入的缺省值会随对象值的变化而变化

main函数的两个版本

  • 无形参版本
  • 带两个形参的版本
代码语言:javascript
复制
int main(){
}

int main(int argc, char *argv[]){
  if(argc != 3){
    std::cerr<<"Usage: "<<argv[0]<<" param1 param2\n";
    return -1;
  }
  std::cout<<"argc = "<<argc<<std::endl;
  for(int i = 0; i < argc ; ++i){
    std::cout<<argv[i]<<"\n";
  }
}

函数体

函数体形成域

  • 其中包含了自动对象(内部声明的对象以及形参对象)
  • 也可包含局部静态对象

函数体执行完成时的返回

隐式返回

代码语言:javascript
复制
#include <iostream>

void fun()
{
    std::cout << "Hello" << std::endl;
}

int main()
{
    fun();
}

显式返回关键字:return

  • return;语句
  • return 表达式;
  • return 初始化列表;

小心返回自动对象的引用或指针(容易返回已经销毁的对象)

代码语言:javascript
复制
#include <iostream>
#include <initializer_list>

std::initializer_list<int> fun()
{
    std::cout << "Hello" << std::endl;
    return {1, 2, 3, 4, 5};
    std::cout << "World" << std::endl;
}

int main()
{
    auto x = fun();
}
代码语言:javascript
复制
#include <iostream>

int& fun()
{
  int x = 3;
  return x;
}

int main{}
{
  int& ref = fun();
}

返回值优化(Return Value Optimization, RVO)—— C++17对返回临时对象的强制优化

  • 具名返回值优化
  • 非具名返回值优化
代码语言:javascript
复制
#include <iostream>

struct Str
{
    Str() = default;
    Str(const Str&)
    {
        std::cout << "Copy constructor is called\n";
    }
};

Str fun()
{
    Str x;
    return x;
}

int main()
{
    Str res = fun(); //两次拷贝构造,因此会输出两次"Copy constructor is called"
}

返回类型

  • 返回类型表示了函数计算结果的类型,可以为void
  • 返回类型的几种书写方法
    • 经典方法:位于函数头的前部
    • C++11引入的方式:位于函数头的后部(泛型编程和类的成员函数编写可能会简化编写)
    • C++14引入的方式:返回类型的自动推导
    • 使用constexpr if构造“具有不同返回类型”的函数,接收常量表达式
  • 返回类型与结构化绑定(C++17)语法糖
  • [[nodiscard]]属性(C++17) 表明返回值很重要需要保留

函数重载与重载解析

  • 函数重载:使用相同的函数名定义多个函数,每个函数具有不同的参数列表(参数个数或者参数类型不同)
  • 不能基于不同的返回类型进行重载
  • 函数重载与name mangling
  • 编译器如何选择正确的版本完成函数调用?
    • 参考资源:Calling Functions: A Tutorial
  • 名称查找
    • 限定查找(qualified lookup)与非限定查找(unqualified lookup)

    限定查找

    • 非限定查找会进行域的逐级查找——名称隐藏(hiding)
    • 查找通常只会在已声明的名称集合中进行
    • 实参依赖查找(Argument Dependent Lookup: ADL)
      • 只对自定义类型生效

      因为obj在是Str对象,所以会去MyNS域中查找

  • 重载解析:在名称查找的基础上进一步选择合适的调用函数
    • 过滤不能被调用的版本(non-viable candidates)
      • 参数个数不对
      • 无法将实参转换为形参
      • 实参不满足形参的限制条件
    • 在剩余版本中查找与调用表达式最匹配的版本,匹配级别越低越好(有特殊规则)
      • 级别1:完美匹配 或 平凡转换(比如加一个const
      • 级别2:promotion 或 promotion加平凡转换
      • 级别3:标准转换 或 标准转换加平凡转换
      • 级别4*:自定义转换 或 自定义转换加平凡转换或 自定义转换加标准转换
      • 级别5*:形参为省略号的版本
      • 函数包含多个形参时,所选函数的所有形参的匹配级别都要优于或等于其他函数

    [站外图片上传中...(image-37f2f5-1690910469933)]

函数相关的其他内容

  • 递归函数:在函数体中调用其自身的函数
    • 通常用于描述复杂的迭代过程(示例)比如二分查找
  • 内联函数/constexpr函数(表示在编译器执行,也可以在运行期执行)(C++11起)/consteval函数(C++20起)(只能在编译期执行) 内联函数的展开: 1.逻辑简单的函数可能会被展开 2.内联函数展开并不是简单的替换 3. inline void fun() ,inline关键字保证在多个翻译单元定义后只选择一次 ,由程序一次定义原则转为翻译单元一次定义原则。4.inline关键字声明一定要有函数定义。

constexpr函数

  • 函数指针
    • 函数类型与函数指针类型

    函数指针

    高阶函数

    • 函数指针与重载
    • 将函数指针作为函数参数
    • 将函数指针作为函数返回值
    • 小心:Most vexing parse,尝试使用大括号替换小括号,明确表示我们要构造一个对象而不是声明一个函数。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第七章 函数
    • 函数基础
      • 函数详解
        • 参数
        • 函数体
        • 返回类型
      • 函数重载与重载解析
        • 函数相关的其他内容
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档