C++回炉之_C++PrimerPlus_第八章 函数探幽

内联函数

  • 内联函数的编译代码与其他的程序代码“内联”到一块了
    • 即编译器使用相应的函数代码来替代函数调用,从而不需要像函数调用那样跳来跳去
  • 内联函数的运行速度比常规函数快,但需要更多的内存
  • 在处理函数调用机制所占时间比执行函数代码的时间还长时,使用内联可节约大量的时间
    • 即对代码执行很短,但调用非常频繁的函数,最好使用内联
  • 使函数变为内联的方法

    • 在函数声明前加关键字 inline
    • 在函数定义前加关键字 inline
    • 一般内联函数在声明的同时定义,比较省事
    inline int max(int a, int b) { return a > b; }
  • 内联函数使用按值传递参数的方式实现
    • 即如果参数为表达式,则函数将传递表达式的值
    • 而宏定义,则是通过文本替换来实现的,即使传递的是表达式,也为原封不动地进行文本替换

引用 – C十十 新增的复合对象

  • 引用是已定义的变量的别名(另一种名称)
  • 但通过将引用作为参数,函数将使用该参数的原始数据,而非其副本
  • 引用变量的创建

    int a;
    int& r_a = a;
    • 此时a和r_a指向相同的值和内存单元
    • 引用必须在声明的同时初始化,且一旦初始化,便不能修改其指向的对象(类似于const)
  • 引用常用于作为函数的参数

    • 使用实参初始化形参 – 引用将创建实参的别名,而非实参的副本 – 类似于解除引用的指针(*p)
    • 使用引用将直接修改原数据
    • 如果既想使用引用(可能为了节约内存和时间),又不想使其所指内存被修改,可在声明前加const关键字
    • 当实参的类型不是左值,或者实参的类型不正确,但可转换为正确的类型时,C++将创建临时变量,而引用类型的形参将作为临时变量的别名
      • 前提是引用类型为const型,这也是显而易见的(非const型的话可能会修改临时变量值,但此举无效)
    • 应尽量使用const型的引用
    • C++11新增了右值引用,即可以指向右值的引用,这将在第18章详细介绍

      double&& rref = 2.0 * x + 5.0; // 右值引用`
    void swap(int& a, int& b) {
        int tmp = a;
        a = b; 
        b = tmp;
    }
  • 将引用用于结构体 – 与将引用用于基本变量相同
  • 将引用作为返回类型

    • 将直接返回左值 – 若不想让其返回左值,但还想使用它,可在返回值前加const关键字
    • 应避免返回函数终止时不再存在的内存单元(如局部变量)
      • 可以返回一个作为参数传递给函数的引用
      • 也可以返回使用new分配的内存
    
    #include 
    
    using namespace std;
    
    struct point {
        int x, y;
    };
    
    point& add(point& a, const point& b) {
        a.x += b.x;
        a.y += b.y;
        return a;
    }
    
    int main() {
        point a {1, 2};
        point b {3, 4};
    
        add(add(a, b), b);
        add(a, b).x = 1;                    // 意味着函数的返回值为左值
    
        cout << a.x << " " << a.y << endl;
    
        return 0;
    
    }
    
    
  • 将引用用于类对象
    • 基类引用可用于派生类对象
    • 即可以定义一个接受基类引用作为参数的函数,可以将基类作为参数,也可以将子类作为参数

默认参数

  • 在函数原型处给参数指定默认值
  • 必须从右向左添加默认值 – 即要为某个参数设置默认值,则必须为它右边所有参数设置默认值
  • 调用函数时,实参应从左向右依次赋值给相应的形参
  • 通常将最不经常改动的放在最右边
void func(int a, int b = 1, int c = 0);
func(1);
func(1, 2);
func(1, 2, 3);

函数重载

  • 仅当函数执行相同的任务,但使用不同形式的数据时,才应采用重载, 还要考虑是否可用默认参数完成
  • 函数重载的关键是函数的参数列表 – 函数特征标
  • C++允许定义名称相同的函数,前提是特征标不同
    • 参数数目或参数类型不同
    • 而不考虑返回值的类型
  • 其实质是在调用这些同名函数时,编译器可以轻而易举地匹配到某个函数
    • 编译器将自动匹配正确的函数原型
    • 当没有原型匹配时,C++将尝试使用强制类型转换进行匹配
      • 但是当有多种可以强制转换的情况时,C++将拒绝这种转换而认为其是错误的(比较代傲娇)
  • 编译器将类型引用和类型本身视为同一特征标
  • 对于不同引用类型的重载,编译器的做法是调用最匹配的那个版本
void fun(int& x);           // 函数1
void fun(const int& x);     // 函数2
void fun(const int&& x);    // 函数3

int x = 1;
const int y = 2;
fun(x);                     // 将调用函数1
fun(y);                     // 将调用函数2
fun(x+y);                   // 将调用函数3 -- 若没有函数3, 将调用函数2

函数模板

template T>
void swap_diy(T& a, T& b) {
    T tmp = a;
    a = b;
    b = tmp;
}
  • 使用template关键字和尖括号定义函数模板
  • 其中typename关键字表示定义泛型类型,旧版也用class关键字
  • typename后面的T是用户自定义的
  • 模板并不创建任何函数,只是告诉编译器如何定义函数
    • 即会用具体的类型去替换T类型
  • 模板也可以重载
  • 模板可能无法处理某些数据类型(结构体,类等)
    • 可以重载运算符(第11章)
    • 显式具体化
      • 对于给定的函数名,可以有非模板函数,模板函数,显式具体化模板函数 及其重载
      • 显式具体化的原型和定义应以template<> 开头,并通过名称来指明类型
      • 具体化优先于常规模板,非模板函数优先于具体化模板
      • 具体化的意思是不使用模板来创建函数定义,而使用自己的函数定义
#include 
using namespace std;

struct point {
    int x, y;
};

template <typename T>
void swap_diy(T& a, T& b) {
    T tmp = a;
    a = b;
    b = tmp;
} // 模板

template <> void swap_diy (point& a, point& b) {
    int tmp = a.x;
    a.x = b.x;
    b.x = tmp;
} // 显式具体化 -- 是可选的 -- 参数也指定是point了

int main() {

    point a { 1, 2 };
    point b { 3, 4 };

    swap_diy(a, b);
    cout << a.x << " " << a.y << endl;

    return 0;

}
  • 实例化
    • 函数模板本身并不会生成函数定义 – 而是生成定义的方案
    • 在调用模板函数时,编译器自动生成函数定义,得到模板实例 – 隐式实例化
    • 显式实例化 – 前加template关键字 (不加尖括号)
      • template void swap_diy(char&, char&); // 只需要声明即可
      • 此声明令编译器使用模板生成一个使用char类型的函数定义 – 即生成模板实例
      • 不能在同一个文件中同时使用同一种类型的显式具体化和显式实例化
      • 也可在函数调用时显式指定类型
        • swap_diy(a, b);
  • 重载解析 – 决定使用重载,模板,模板重载中的哪个函数定义
    • 创建候选函数表 – 函数名符合
    • 据候选函数表创建可行函数表 – 参数符合
    • 确定最佳可行函数 – 若没有则函数调用出错
      • 完全匹配 – 常规函数优于模板
      • 提升转换
      • 标准转换
      • 强制转换
    • 模板的部分排序规则 – 选出最具体的模板函数定义

decltype关键字(C十十11)

  • 为了解决某些类型是由模板(或其组合)决定,而难以表示的问题

    template<typename T1, typename T2>
    void fun(T1& a, T1& b) {
        some_type apb = a + b;
    }
  • decltype(expression) var; – 使变量 var 与 expression 有相同的类型

    • 若expression是一个没有括号括起来的标识符,则var的类型与该标识符相同

      const int* x;
      decltype(x) w;  // w的类型为const int*
    • 若expression是一个函数调用,则var的类型与该函数的返回值相同(并不会实际调用)

      long fun(int);
      decltype(fun(3)) m; // m的类型为long
    • 若expression是一个左值,且用括号括起来了,则var的类型为指向其类型的引用

      double x = 3.3;
      decltype((x)) r = x;        // r为double&类型
      decltype(x) v = x;          // v为double类型
    • 若前面的条件都不符合,则var的类型与expression的类型相同
  • 若需要多次声明此类型,可结合typedef

    template<class T1, class T2>
    void fun(T1 x, T2 y) {
        typedef decltype(x + y) xpytype; // 将其定义为另一种类型名
        xpytype xpy = x + y; 
        xpytype arr[10];
        xpytype& rxy = arr[2];
    }
  • 后置返回类型(C++11)

    template<class T1, class T2>
    some_type fun(T1 x, T2 y) {     // 不知道返回值类型
        return x + y;
    }
    • 而如果将some_type写成decltype(x+y) 也是不对的 – 因为此时x和y 尚未声明
    • 新的语法 – double f(int x, float y);可表达为

      auto f(int x, float y) -> double;
      auto f(int x, int y) -> double {
          return x + y + 0.5;
      }
      cout << f(1, 2) << endl;
    • 最终的解决方案即为

      template<class T1, class T2>
      auto fun(T1 x, T2 y) -> decltype(x + y) {       // 不知道返回值类型
          return x + y;
      }

你可能感兴趣的:(C/C++)