C++ Primer Plus: 第八章 函数探幽

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/budding0828/article/details/82597948

C++ Primer Plus: 第八章 函数探幽

8.1 C++内联函数

  • 内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数之间的主要不在于编写方式,而在于C++编译器如何将它们组合到程序中。
  • 编译过程的最终产品是可执行程序——由一组机器语言指令组成。
  • 执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数赋值到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。
  • 内联函数,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
  • 如果代码执行时间很短,则内联函数调用就可以节省非内联调用使用的大部分时间。
  • 程序员请求将函数作为内联函数时,编译器并不一定会满足这种需求。它可能认为该函数过大或注意到函数自己调用自己(内联函数不能递归)。因此不能将其作为内联函数。

8.2 引用变量

  • 通过将引用变量用作参数,函数将使用原始数据,而不是其副本。
  • 必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值。
  • 引用经常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。
  • 传递引用的限制更加严格。func(x+3.0);是不合理的。不允许有表达式。(因为会创建临时变量)
什么时候创建临时变量

如果引用参数是const,(如:func(const double &ra))则编译器将在下面两种情况下生产临时变量:
1. 实参的类型正确,但不是左值。(如:7.0、temp+7等)
2. 实参的类型不正确,但可以转换为正确的类型。(如:int,long等)

  • 使用const引用使函数能够正确生成并使用临时变量。因此,应尽可能引用形参声明const.
右值引用

C++11新增了另一种引用——右值引用。这种引用可以指向右值,是使用&&声明的:

double && rref = sqrt(36.0);
double && jref = 2.0*j + 18.5;
返回引用
  • 返回引用效率更高。计算关键字return后面的表达式,并将结果返回给调用函数。从概念上说,这个值被复制到一个临时位置,而调用程序将使用这个值。
double m = sqrt(16.0);
cout << sqrt(25.0);

第一条语句中,值4.0被复制到一个临时位置,然后被复制给m。
第二条语句中,值5.0被复制到一个临时位置,然后被传递给cout。
若返回引用,则直接把值复制到m中,更加高效。

  • 返回引用时需要注意不能返回临时变量的引用,因为函数结束后,该变量的内存将被释放。可以使用new,或者使用引用参数。
C风格字符串作string对象引用参数
string version(const string& s1, const string& s2){...} //函数声明

string input = "absc";
version(input, "***");  //调用
  • string 类定义了一种char* 到string的转换功能,这使得可以使用C风格字符串来初始化string对象。
  • 使用const引用形参的一个属性。假设实参的类型与引用参数类型不匹配,但可以被转换为引用类型,程序将创建一个正确类型的临时变量,使用转换后的实参值来初始化它,然后传递一个指向该临时变量的引用。
  • 所以,如果形参类型为const string&, 在调用函数时,使用的实参可以是string对象或C风格字符串,如果引用括起的字符串字面量、以空字符结尾的char数组或指向char的指针变量
何时使用引用参数
  1. 程序员能够修改调用函数中的数据对象。
  2. 当数据对象太大时,提高程序的运行速度。
  3. 数组等

8.3 默认参数

  • 默认参数指的是当函数调用中省略了实参时自动使用的一个值。
  • 对于带参数列表的函数,必须从右向左添加默认值。实参按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数。
  • 只有函数原型指定了默认值。函数定义与没有默认参数时完全相同
//函数原型
char * left(const char* str, int n=1);
//函数定义
char * left(const char* str, int n)
{...}

8.4 函数重载

  • 函数多态(函数重载)让您能够使用多个同名的函数。
函数特征标
  • 如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则他们的特征标相同。
  • C++不允许只有返回类型不同的重载。
  • 编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标。
  • 匹配函数时,并不区分const和非const变量。
重载引用参数
void func(double &r1);
void func(const double &r2);
void func(double && r3);

double x = 55.5;
const double y = 12.2;
func(x);        //调用void func(double &r1);
func(y);        //调用void func(const double &r2);
func(x+y);      //调用void func(double && r3);

若没有void func(double && r3);那么func(x+y)将会调用void func(const double &r2);

C++如何跟踪每一个重载函数
  • C++编译器对每个函数进行名称修饰,根据函数原型中指定的形参类型对每个函数名进行加密。
  • 例如:
long func(int,float);
//内部表示来描述该接口
?func@@YAXH

对原始名称进行的表面看来无意义的修饰将对参数数目和类型进行编码。添加的一组符号随函数特征标而异,而修饰时使用的约定随编译器而异。

8.5 函数模板

  • 交换模板
template<typename T>
void Swap(T &a, T &b)
{
    T temp;
    temp = a;
    a = b;  
    b = temp;
}
  • 关键字template和typename是必须的,可以使用class代替使用关键字typename。
  • 必须使用尖括号,类型名可以任意选择。
  • 模板并不创建函数,而只是告诉编译器如何定义函数。
显示具体化
  • 为特定类型提供具体化的模板定义。提供一个具体化函数定义——称为显示具体化,其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
  • 示例:
//模板函数
template <typename T>
void Swap(T&, T&){}
//显示具体化函数
template<> void Swap<job>(job&, job&){}

调用顺序:非模板函数 > 具体化模板函数 > 模板函数

区别实例化和具体化
  • 隐式实例化:模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化。因为编译器之所以知道需要进行定义,是由于程序调用Swap()函数时提供了int参数。
  • 显示实例化:template void Swap<int>(int, int);实现了这种特性的编译器看到上述声明后,将使用Swap()模板生成一个使用int类型的实例。或者直接Swap<double>(d1, d2);
  • 显示具体化:template<> void Swap<job>(job&, job&){...}
  • 隐式实例化、显示实例化和显示具体化统称为具体化
  • 示例
template <class T>
void Swap(T&, T&);
template <> void Swap<job>(job&, job&)  //显示具体化
{
    ...
}
int main()
{
    template void Swap<char>(char &, char &);   //显示实例化
    short a,b;
    ...
    Swap(a,b);  //使用模板,隐式实例化
    ...
    job n,m;
    ...
    Swap(n,m);  //使用显示具体化
    char g,h;
    ...
    Swap(g,h);  //使用显示实例化
}
重载解析

决定函数调用哪个函数定义,尤其是有多个参数时,称为重载解析。
从最佳到最差的顺序如下:
1. 完全匹配:常规函数优先于模板。
2. 提升转换:(例如char和shorts自动转换成为int, float自动转换为double)
3. 标准转换:(例如int转换成为char,long 转换为double)
4. 用户定义转换。如类声明中定义的转换。

  • 无关紧要的转换:

    1. 引用的互相转换
    2. 从非const到const
    3. 从T[]到*T
    4. 等等
  • 有时候即使两个函数都完全匹配,有可以完成重载解析。首先,指向非const数据的指针和引用优先于非const指针和引用参数匹配。其次,非模板优先于模板。

  • 编译器会自动推断使用哪种类型时执行的转换最少。
template <class Type>
void recycle(Type t);
//
template <class Type>
void recycle(Type* t);
//
int bolt = 12;
recycle(&bolt); //虽然两个模板都可以,但将调用第二个模板,因为更具体。
  • 简而言之:重载解析将寻找最匹配的函数。
    1. 如果只存在一个这样子的函数,则选择它;
    2. 如果存在多个这样子的函数,但其中只有一个是非模板函数,则选择该函数;
    3. 如果存在多个适合的函数,且它们都是模板函数,但其中有一个函数比其他函数更具体,则选择该函数;
    4. 如果有多个同样合适的非模板函数或者模板函数,但没有一个函数比其他函数更具体,则函数调用是不确定的,因此是错误的。
模板函数的发展
  • 问题:不清楚类型
template <class T1, class T2>
void ft(T1 x, T2 y)
{
    ...
    ?Type? xpy = x + y;
    ...
}
  • 解决1:C++11新增关键字decltype提供解决方案
int x;
decltype(x) y;  //make y the same type as x
//解决问题
decltype(x+y) xpy;
xpy = x + y;
//合二为一
decltype(x+y) xpt = X + y;S
  • 解决2:auto
auto res = x + y;

猜你喜欢

转载自blog.csdn.net/budding0828/article/details/82597948
今日推荐