C++ Primer Plus学习(八)——函数进阶


C++还提供了许多新的函数特性,使之有别于C语言。新特性包括内联函数、按引用传递变量、默认的参数值、函数重载(多态)以及模板函数。

内联函数

  • 内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。应有选择地使用内联函数。如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程的很小一部分。如果代码执行时间很短,则内联调用就可以节省非内联调用使用的大部分时间。比如说,如果函数定义占用多行(假定没有使用冗长的标识符),则将其作为内联函数就不太合适。
  • 另外,内联函数不能递归。
  • 要使用这项特性,必须采取下述措施之一:
    1. 在函数声明前加上关键字inline;
    2. 在函数定义前加上关键字inline

以下这一段对内联函数的解释比较容易理解,码在这里帮大家再理解一遍:

使用函数能够避免将相同代码重写多次的麻烦,还能减少可执行程序的体积,但也会带来程序运行时间上的开销。函数调用在执行时,首先要在栈中为形参和局部变量分配存储空间,然后还要将实参的值复制给形参,接下来还要将函数的返回地址(该地址指明了函数执行结束后,程序应该回到哪里继续执行)放入栈中,最后才跳转到函数内部执行。这个过程是要耗费时间的。另外,函数执行return语句返回时,需要从栈中回收形参和局部变量占用的存储空间,然后从栈中取出返回地址,再跳转到该地址继续执行,这个过程也要耗费时间。总之,使用函数调用语句和直接把函数中的代码重新抄写一遍相比,节省了人力,但是带来了程序运行时间上的额外开销。一般情况下,这个开销可以忽略不计。但是,如果一个函数内部没有几条语句,执行时间本来就非常短,那么这个函数调用产生的额外开销和函数本身执行的时间相比,就显得不能忽略了。假如这样的函数在一个循环中被上千万次地执行,函数调用导致的时间开销可能就会使得程序运行明显变慢。

作为特别注重程序执行效率,适合编写底层系统软件的高级程序设计语言,C++用inline关键字较好地解决了函数调用开销的问题。

在C++中,可以在定义函数时,在返回值类型前面加上inline关键字。增加了inline关键字的函数称为“内联函数”。内联函数和普通函数的区别在于:当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插入调用语句处,就像整个函数体在调用处被重写了一遍一样。

有了内联函数,就能像调用一个函数那样方便地重复使用一段代码,而不需要付出执行函数调用的额外开销。很显然,使用内联函数会使最终可执行程序的体积增加。以时间换取空间,或增加空间消耗来节省时间,这是计算机学科中常用的方法。

内联函数中的代码应该只是很简单、执行很快的几条语句。如果一个函数较为复杂,它执行的时间可能上万倍于函数调用的额外开销,那么将其作为内联函数处理的结果是付出让代码体积增加不少的代价,却只使速度提高了万分之一,这显然是不划算的。有时函数看上去很简单,例如只有一个包含一两条语句的循环,但该循环的执行次数可能很多,要消耗大量时间,那么这种情况也不适合将其实现为内联函数。另外,需要注意的是,调用内联函数的语句前必须已经出现内联函数的定义(即整个函数体),而不能只出现内联函数的声明。

引用变量

  • C++新增了一种复合类型——引用变量。引用是已定义的变量的别名(另一个名称)。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。

  • 必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值。

  • 将引用参数声明为常量数据的引用的理由有三个:

    1. 使用const可以避免无意中修改数据的编程错误;
    2. 使用const使函数能够处理const和非const实参,否则将只能接受非const数据;
    3. 使用const引用使函数能够正确生成并使用临时变量。
      因此,应尽可能将引用形参声明为const。
  • 使用&声明的引用称为左值引用,C++11新增了另一种引用——右值引用(rvalue reference).

  • 返回引用时需要注意的问题:

    1. 应避免返回函数终止时不再存在的内存单元引用;
    2. 也应避免返回指向临时变量的指针(不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。);
    3. 为了避免上述问题,最简单的方法是,返回一个作为参数传递给函数的引用;
    4. 另一种方法是,用new来分配新的存储空间,并返回指向该内存空间的指针。这种方法存在一个问题:在不再需要new分配的的内存时,应使用delete来释放它们。但是函数调用隐藏了对new的调用,这使得以后很容易忘记使用delete来释放内存。后面要讨论的auto_ptr模板以及C++11新增的unique_ptr可帮助程序员自动完成释放工作。
  • 继承的另一个特征是,基类引用可以指向派生类对象,而无需进行强制类型转换。这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。

  • 方法调用self(ios_base::fixed)将对象置于使用定点表示法的模式;self(ios_base::showpoint)将对象置于显示小数点的模式,即使小数部分为零。方法precision()指定显示多少位小数(假定对象处于定点模式下)。所有这些设置都将一直保持不变,直到再次调用相应的方法重新设置它们。方法width()设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后将恢复到默认设置。默认的字段宽度为零,这意味着刚好能容纳下要显示的内容。

  • 使用引用参数的主要原因有两个:

    1. 程序员能够修改调用函数中的数据对象;
    2. 通过传递引用而不是整个数据对象,可以提高程序的运行速度。
  • 引用、指针、按值传递的选用指导原则:
    对于使用传递的值而不做修改的函数:

    1. 如果数据对象很小,如内置数据类型或小型结构,则按值传递;
    2. 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针;
    3. 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间;
    4. 如果数据对象是类对象,则使用const引用。传递类对象参数的标准方式是按引用传递;

    对于修改调用函数中数据的函数:
    5. 如果数据对象是内置数据类型,则使用指针;
    6. 如果数据对象是数组,则只能使用指针;
    7. 如果数据对象是结构,则使用引用或指针;
    8. 如果数据对象是类对象,则使用引用。

默认参数

  • 对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。实参按从左到右的顺序依次被赋给相应的形参,而不能跳过任何参数。

函数重载

  • 默认参数让您能够使用不同数目的参数调用同一个函数,而函数多态(重载)让您能够使用多个同名的函数。
  • 函数重载的关键是函数的参数列表——也称为函数特征标(function signature)。
  • 编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标。
  • 仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应使用函数重载。
  • C++如何跟踪每一个重载函数呢?它给这些函数指定了秘密身份。使用C++开发工具中的编译器编写和编译程序时,C++编译器将执行一些神奇的操作——名称修饰(name decoration)或名称矫正(name mangling),它根据函数原型中指定的形参类型对每个函数名进行加密。

函数模板

  • 如果需要多个将同一种算法用于不同类型的函数,请使用模板。如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数时,应使用关键字typename而不使用class.
  • 模板的使用包括三种:隐式实例化、显式实例化、显式具体化。
  • 对于函数重载、函数模板和函数模板重载,C++需要(且有)一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析(overloading resolution)。大致过程如下:
    1. 创建候选函数列表;
    2. 使用候选函数列表创建可行函数列表;
    3. 确定是否有最佳的可行函数。

总结

C++扩展了C语言的函数功能。通过将inline关键字用于函数定义,并在首次调用该函数前提供其函数定义,可以使得C++编译器将该函数视为内联函数。也就是说,编译器不是让程序跳到独立的代码段,以执行函数,而是用相应的代码替换函数调用。只有在函数很短时才能采用内联方式。

引用变量是一种伪装指针,它允许为变量创建别名(另一个名称)。引用变量主要被用作处理结构和类对象的函数的参数。通常,被声明为特定类型引用的标识符只能指向这种类型的数据;然而,如果一个类(如ofstream)是从另一个类(如ostream)派生出来的,则基类引用可以指向派生类对象。

C++原型让您能够定义参数的默认值。如果函数调用省略了相应的参数,则程序将使用默认值;如果函数调用提供了参数值,则程序将使用这个值(而不是默认值)。只能在参数列表中从右到左提供默认参数,因此,如果为某个参数提供了默认值,则必须为该参数右边所有的参数提供默认值。

函数的特征标是其参数列表。程序员可以定义两个同名函数,只要其特征标不同。这被称为函数多态或函数重载。通常,通过重载函数来为不同的数据类型提供相同的服务。

函数模板自动完成重载函数的过程。只需使用泛型和具体算法来定义函数,编译器将为程序中使用的特定参数类型生成正确的函数定义。


版权声明:本文为weixin_41480034原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。