第七章 函数

第七章 函数

函数基础

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

函数详解

参数

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

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

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

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

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

      #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标准之前)中对复制临时对象强制忽略的约束
  • 函数传值、传址、传引用

    #include <iostream>
    
    void fun(int par)
    {
        ++par;
    }
    
    int main()
    {
        int arg = 3;
        fun(arg);
        std::cout << arg << '\n';   // 传值
    }
    
    #include <iostream>
    
    void fun(int* par)
    {
        ++(*par);
    }
    
    int main()
    {
        int arg = 3;
        fun(&arg);
        std::cout << arg << '\n';   // 传址
    }
    
    #include <iostream>
    
    void fun(int& par)
    {
        ++par;
    }
    
    int main()
    {
        int arg = 3;
        fun(arg);
        std::cout << arg << '\n';   // 传引用
    }
    
  • 函数传参过程中的类型退化

    #include <iostream>
    
    void fun(int par[])   // void fun(int* par)
    {
        // ...
    }
    
    int main()
    {
        int a[3];
        fun(a);
    }
    
  • 变长参数

    • initializer_list

      #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函数的两个版本

    • 无形参版本
    • 带两个形参的版本
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";
  }
}

函数体

  • 函数体形成域

    • 其中包含了自动对象(内部声明的对象以及形参对象)
    • 也可包含局部静态对象
  • 函数体执行完成时的返回

    • 隐式返回

      #include <iostream>
      
      void fun()
      {
          std::cout << "Hello" << std::endl;
      }
      
      int main()
      {
          fun();
      }
      
    • 显式返回关键字:return

      • return;语句
      • return 表达式;
      • return 初始化列表;
    • 小心返回自动对象的引用或指针(容易返回已经销毁的对象)

      #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();
      }
      
      #include <iostream>
      
      int& fun()
      {
        int x = 3;
        return x;
      }
      
      int main{}
      {
        int& ref = fun();
      }
      
    • 返回值优化(Return Value Optimization, RVO)—— C++17对返回临时对象的强制优化

      • 具名返回值优化
      • 非具名返回值优化
      #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,尝试使用大括号替换小括号,明确表示我们要构造一个对象而不是声明一个函数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容