《C++ Primer Plus》读书笔记之六—函数探幽

第八章 函数探幽

  1、常规函数与内联函数的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。

  2、常规函数调用使程序跳到另外一个地址(函数地址),并在函数结束时返回,更详细的的实现过程:执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值i放入寄存器)然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。

  3、内联函数:编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,然后再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。

  4、要使用内联函数,必须采取下述措施之一:①再函数声明前加上关键字inline②再函数定义前加上关键字inline。通常的做法是省略原型,将整个定义放在本应提供原型的地方。

  5、程序员请求将函数作为内联函数时,编译器并不一定满足这种要求。而且。内联函数不能递归

  6、引用变量的主要作用是用作函数的形参

  7、可以创建指向rats的引

用和指针(如下),这样,表达式rodents和*prats都可以和rats互换,而表达式&rodents和prats都可以同&rats互换。

  int  rats=101;

  int &rodents=rats;

  int *prats=&rats;

  8、必须在声明引用或者const指针时进行初始化,指针可以先声明再赋值。

  9、不能通过赋值来设置引用!!

  int  rats=101;

  int &rodents=rats;// rodents引用的是rats。

  int b=50;

  rodents=b;// 这句话仅仅是将b的值赋给rodents!!rodents和rats的值都变成了50,同时, rodents和rats的地址相同,而与b的地址不同。

  10、如果想用引用作为函数形参,而又不想改变变量的值,则应使用常量引用(const int &a).

  11、如果实参与常量(const)引用参数不匹配,C++将生成临时变量。两种情况:①实参的类型正确,但不是可修改的左值(非左值)②实参的类型不正确,但可以转换为正确的类型。注:非左值包括字面常量和包含多项的表达式。

  12、如果声明将引用指定为const,C++将在必要时生成临时变量。这些临时变量只在函数调用期间存在,此后编译器便可以随意将其删除。实际上,对于形参为const引用的C++函数,如果实参不匹配,则其行为类似于按值传递,为确保原始数据不被修改,将使用临时变量来存储值。注:如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是禁止创建临时变量。

  13、如果函数调用的参数不是左值或与相应的const引用参数的类型不匹配,则C++将创建类型正确的临时匿名变量,将函数调用的参数的值传给该匿名变量,并让参数来引用该变量。

  14、应尽可能将引用形参声明为const。

  15、通常将返回的引用声明为const是最好的选择。

  16、通常,返回机制将返回值复制到临时存储区域中,随后调用程序将访问该区域的临时拷贝值。然而,返回引用意味着调用程序将直接访问返回值,而不需要拷贝。通常,返回的引用将指向传递给函数的引用,因此调用函数实际上是直接访问自己的一个变量。注:返回引用的函数实际上是被引用的变量的别名。

  17、应避免返回一个指向临时变量的引用或者指针!因为函数运行完后,临时变量将不再存在。为避免这种问题,最简单的方法是,返回一个作为参数传递给函数的引用。作为参数的引用将指向调用函数使用的数据,因此返回的引用也将指向这些数据。另一种方法是用new来分配新的存储空间,然后返回指向该内存空间的指针。

  18、将const用于引用返回类型,意味着:不能使用返回的引用来直接修改它指向的结构。

  19、可以将C-风格字符串,包括引号括起的字符串字面量、以空字符结尾的char数组或者指向char的指针变量,赋给string引用!!因为string类定义了一种char*到string的转换功能。

  20、使用引用参数(指针参数)的主要原因有两个:①可以修改调用函数中的数据对象。②通过传递引用而不是整个数据对象,可以提高程序的运行速度。

  21、对于形参如何使用的几点指导原则:①如果数据对象是数组,则只能使用指针。②如果数据对象是结构,使用引用或指针。③如果数据对象是类对象,则使用引用。

  22、如何设置默认值?必须通过函数原型!!对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,必须为它右边的所有参数提供默认值。

  23、关于函数重载,编译器在检查函数特征标时,将把类型引用和类型本身视为同一个特征标。例如:int a(int x);与int a(int &x);cout<<a(x);参数x与这两个原型都匹配!!

  24、将非const值赋给const变量是合法的,反之则是非法的。

  25、仅当函数基本执行相同的任务,但使用不同形式的数据时,才应采用函数重载

  26、模板并不创建任何函数,而只是告诉编译器如何定义函数。

  27、如果需要多个将同一种算法用于不同类型的函数,请使用模板。注:函数模板不能缩短执行程序!最终的代码不包含任何模板,而只是包含为程序生成的实际 函数。

  28、需要多个对不同类型使用同一种算法的函数时,可使用模板。不过,并非所有的类型都使用相同的算法。为满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规函数重载语一样,被重载的模板的函数特征标必须不同。

  29、模板函数的具体化函数定义——显示具体化。当编译器找到与函数调用匹配的具体化定义后,将使用该定义,而不再寻找模板。

  ①对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本。

  ②显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。

  ③具体化将覆盖常规模板,而非模板函数将覆盖具体化和常规函数。即:编译器在选择原型时,如果有多个原型,非模板版本将优先于显式具体化和模板版本,而显式具体化将优先于使用模板生成的版本。

  例子:

  ①非模板void swap(int &,int &);

  ②模板 template<class T>

     void swap(T &,T &);

  ③显式具体化 template<> void swap<int>(int &,int &);或者 template<> void swap(int &,int &);

  30、实例化:编译器使用模板为特定类型生成函数定义时,得到的是模板实例。比如有一个模板函数 template<class T>void swap(T &,T &);函数调用swap(i,j)导致编译器生成swap()的一个实例,该实例使用int类型。模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化显示实例化的格式为:template void swap<int>(int,int);编译器看到这种声明后,将使用swap模板生成一个使用int类型的实例。注:显示具体化和显示实例化不同。试图在同一个编程单元中使用同一个类型的显式实例和显式具体化将出错。

  31、隐式实例化、显式实例化和显式具体化统称为具体化。相同之处是:它们表示的都是使用具体类型的函数定义,而不是通用描述。

  32、对于函数重载、函数模板和函数模板重载,C++来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析

  33、重载解析将寻找最匹配的函数。如果只存在一个这样的函数,则选择它。如果存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;如果存在多个合适的函数,并且它们都为模板函数,但其中有一个函数比其他函数更具体,则选择该函数。如果有多个同样合适的非模板函数或者模板函数,但没有一个函数比其他函数更具体,则函数调用是不确定的,因此是错误的。

   

posted @ 2018-04-11 15:53  ~君莫笑~  阅读(187)  评论(0编辑  收藏  举报