一、内联函数
常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。
内联函数的编译代码与其他程序的代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内敛代码,程序无须跳到另一个位置处执行代码,再跳回来。
因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。
要使用这项特性,
在函数定以前加上关键字inline;
通常的做法是省略原型,将整个定义(即函数头和所有函数代码)放在本应提供原型的地方。
内联函数不能递归
内联与宏
inline是C++新增的特性。C语言使用预处理器语句#define来提供宏——内联代码的原始实现。
二、引用变量
C++新增了一种复合类型——引用变量,引用是已定义的变量的别名(另一个名称)。但引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。
C和C++使用&符号来指示变量的地址。C++给&符号赋予了另一个含义,将其用来声明引用。
举例:
int a;
int & a = b;
这里&不是取地址运算符,而是类型标识符的一部分。
引用看上去很像伪装表示的指针(其中,*解除引用运算符被隐式理解)。实际上,引用还是不通与指针的。除了表示法不同外,还有其他的差别。例如,差别之一是,必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值。
注意:必须在声明引用变量时进行初始化。
引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。
也就是说:
int & a = b;
//它实际上是下面代码的伪装表达式
int * const ptr = &a;
引用经常被用作函数参数,使得函数中的变量名称为调用程序中的变量的别名。
这种传递参数的方法成为按引用从传递。按引用传递允许被调用的函数能够访问调用函数中的变量。C++新增的这项特性是对C语言的超越,C语言只能按值传递。
当然,C语言也可以避开按值传递的限制,采用按指针传递的方式。
如果程序员的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应该使用常量引用。
临时变量,引用参数和const
如果实参与引用参数不匹配,C++将生成临时变量。当前仅当参数为const引用时,C++才允许这样做,但以前不是这样的。下面看看这种情况下,C++将生成临时变量,以及为何对const引用的限制是合理的。
首先,什么时候创建临时变量呢?如果引用参数是const,则编译器将在下面两种情况下生成临时变量:
实参的类型正确,但不是左值。
实参的类型不正确,但可以转换为正确的类型。
左值是什么呢?
左值参数是可被引用的的数据对象,例如,变量,数组元素,结构成员,引用和解除引用的指针都是左值。
非左值包括字面常量(用括号括起的字符串除外,它们由其地址表示)和包含多项的表达式。
注意:如果函数调用的参数不是左值或与之相应的const引用参数的类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。
应尽可能使用const
将引用参数声明为常量数据的引用的理由有三个:
使用const可以避免无意中修改数据的编程错误;
使用const是函数能够处理const和非const实参,否则将只能接受非const数据;
使用const引用使函数能够正确生成并使用临时变量;
C++11新增了另一种引用——右值引用。这种引用可指向右值,是使用&&声明的。
后面我会教你们怎么使用。
引用非常适合用于结构和类,确实,引入引用主要是为了用于这些类型的,而不是基本的内置类型。
注意:返回引用的函数实际上是被引用的变量的别名。
返回引用最重要的一点是,应避免返回函数终止时不再存在的内存单元引用。
将引用用于类对象
将类对象传递给函数时,C++通常的做法是使用引用。
对象、继承和引用
何时使用引用参数
使用引用参数的主要原因有两个,
程序员能够修改调用函数中的数据对象。
通过传递引用而不是整个数据对象,可以提高程序的运行速度。
当数据对那个较大时(如结构和类对象),第二个原因最重要。这些也是使用指针参数的原因。这是有道理的,因为引用参数实际上是基于指针的代码的另一个接口。那么什么时候应该使用引用,什么时候应该使用指针呢?
下面是一些指导原则:
对于使用传递的值而不做修改的函数。
如果数据对象很小,如内置数据类型或小型结构,则按值传递。
如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。
如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间。
如果数据对那个是类对象,则使用const引用。类设计的语义尝尝要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。
对于修改调用函数中数据的函数:
如果数据对象是内置数据类型,则使用指针。如果看到注入fixit(&x)这样的代码(其中x是int),则很明显,该函数将修改x。
如果数据对象是数组,则只能使用指针。
如果数据对象是结构,则使用引用或指针。
如果数据对象是类对象,则使用引用。
默认参数
默认参数指的是当函数调用中省略了实参时自动使用的一个值。
函数重载
函数多态是C++在C语言的基础上新增的功能,默认参数让您能够使用不同数目的参数调用同一个函数。而函数多态(函数重载)让您能够使用多个同名的函数。
术语“多态”指的是有多种形式,因此函数多态允许函数可以有多种形式。
类似的,术语“函数重载”指的是可以有多个同名的函数,因此对名称进行了重载。
这两个术语指的是一回事。
函数重载的关键是函数的参数列表——也称为函数特征标。
如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。C++允许定义名称相同的函数,条件是它们的特征标不同。
重载引用参数
类设计和 STL经常使用引用参数,因此直到不同引用类型的重载很有用。
何时使用函数重载
虽然函数重载很吸引人,但也不要滥用,进当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载。
什么是名称修饰
C++如何跟踪每一个重载函数呢?它给这些函数指定了私密身份。使用C++开发工具中的编辑器编写和编译程序时,C++编译器将执行一些神奇的操作——名称修饰或名称矫正。它根据函数原型中指定的形参来对每个函数名进行加密。
五、函数模板
现在的C++编译器实现了C++新增的一项特性——函数模板。函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型,(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式便携程序,因此有时也被称为通用编程。
提示:
如果需要多个将同一种算法用于不同类型的函数,请使用模板。如果不考虑向后金融的问题,并愿意键入较长的单词,则声明变量类型参数时,应使用关键字typename而不是用class。
重载的模板
需要多个对不同类型使用同一种算法的函数时,可使用模板。
然而并非所有的函数类型都使用相同的算法,为满足这种需求,可以像重载常规函数定义那样重载模板定义。和常规重载一样,被重载的模板的函数特征标必须不同。
模板的局限性
编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。一种解决方案是,C++允许重载运算符,以便能够将其用于特定的结构或类。(运算符重载,我会在后面介绍)。另一种解决防范是,为特定类型提供具体化的模板定义。
显式具体化。
C++98规定。
对于给定的函数名,可以有 非模板函数,模板函数和显式具体化模板函数以及它们的重载版本。
显式具体化的运行和定义应以template<>打头,并通过名称来指出类型。
具体化优先于常规模板,而非模板函数优先于具体化和常规函数。
实例化和具体化
在代码中包含函数模板本身并不会生成函数定义,它只是一个用于声明函数定义的方案
编译器使用模板为特定类型生成函数定义时,得到的是模板实例。
模板并非函数定义,但使用int的模板实例是函数定义,这种实例化方式被称为隐式实例化。
最初编译器只能通过隐式实例化,来使用模板生成函数定义,但现在C++海允许显式实例化。这意味着可以直接命令编译器穿件特定的实例,如Swap<int>()。其语法是,声明所需的种类——用<>指示类型,并在声明前加上关键字template:
警告:试图在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化将出错。
隐式实例化,显式实例化,显示具体化,统称为具体化。它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。