第八章目录
8.1 C++内联函数
P253
常规函数调用和内联函数的执行过程
编译过程的最终产品是可执行程序——由一组机器语言组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。有时(如有循环或分支语句时),将跳过一些指令,向前或者向后跳到指定地址。常规函数调用也使程序跳到另一个地址(函数的地址),并执行结束时返回。下面更详细地介绍这一过程的典型实现。
执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数的内存单元,执行函数代码(也许还需将返回值放入到寄存器中),然后跳回到地址被保存的指令处。来回跳跃并记录跳跃位置意味着以前使用函数时,需要一定的开销。
C++内联函数提供了另一种选择。内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。如果程序在10个不同的地方调用同一个内联函数,则该程序将包含代码的10个副本。如果代码执行时间很短,则内联调用就可以节约非内联调用使用的大部分时间。
内联函数和常规函数调用示意图
内联与宏
内联函数和常规函数一样,按值传递参数的。宏函数只是简单通过文本替换来实现。
技巧:用C++内联函数代替宏定义。
代码如下:
// a inline function definition
inline double square(double x){return x*x;}
#define SQUARE(X) X*X
a = square(4.5 + 7.5); // is replaced by (4.5+7.5)^2
b = SQUARE(4.5 + 7.5); // is replaced by 4.5 + 7.5*4.5 + 7.5
8.2 引用变量(重点)
P255
引用变量的主要用途是用作函数的形参。
必须在声明引用变量时进行初始化。引用更接近于const指针。
c++ 引用相比于指针能够实现运算符重载。
int &rodents = rats;
// 伪装表示如下,const变量必须初始化
int* const pr = &rats;
//引用 rodent 等价于 *pr
按值传递的函数,可使用多种类型的实参;传递引用限制更严格,非常量引用的实参应是左值。
左值
- 左值(可以通过地址访问)参数是可被引用的数据对象。例如:变量、数组元素、结构成员、引用和解引用的指针都是左值。
- 非左值(无确定的地址)包括字面常量(用引号括起来的字符串除外)和包含多项的表达式。
const引用
如果实参与引用参数不匹配,仅当参数为const引用时,C++将生成临时匿名变量。
相比值传递,const引用可以减少将实参拷贝给形参的开销。
如果引用参数作为实参的目的是修改为参数传递的变量,则禁止创建临时变量。相反,目的只是使用传递的值,而不修改它们,应将声明为const引用。
代码如下:
double cube(double a);
double refcube1(double &ra);
double refcube2(const double &ra){return ra*ra*ra};
//...
double z = cube(x + 2.0); //evaluate x+2.0, pass value
double z = refcube1(x + 2.0); //no, should not compile
double z = refcube2(x + 2.0); //yes, ra is temporary variable
技巧:尽可能使用const引用
- const引用可以避免无意中修改数据的编程错误。
- const引用可以使函数能够处理const和非const实参。
- const引用使函数能够正确生成并使用临时变量。
返回引用
int& reftarget(int& rt) {
rt +=rt;
return rt;
}
//...
int target1 = 10;
int target2 = reftarget(target1);
如果ref返回int,而不是指向引用,则需要将 rt 复制到一个临时变量,再将这个拷贝复制给target2。如果ref返回int引用,则直接将rt复制到target2,效率更高。
【注意】:不能返回一个指向临时变量的引用,函数运行完毕后它将被销毁。
// 返回const引用,creftarget变为不可修改的左值
const int& creftarget(int& rt);
creftarget = 4; //not allowed for const reference return
技巧:当需要返回引用,但又不希望作为左值。可以返回const引用,可以避免返回值作为左值,避免模糊特性。
何时使用引用参数
对于使用值而不修改值的函数
- 内置数据类型,按值传递
- 数组,则只能使用指针
- 较大的结构,则使用指针或者引用
- 类对象,使用引用
对于内置数据类型,数据的字节数小(32位指针4个字节),而且按值传递为直接寻址,效率较高。
对于数组作为实参时,只需要将数组的首地址传递给函数,函数将使用原来的函数,只能使用指针传递。
对于类对象,类设计的语义常常要求使用引用;同时引用传递具有参数检查,比指针传递安全;传递类对象参数的标准方式是按引用传递。
8.3 默认参数
P274
通过函数原型设置默认参数,形参必须从右向左添加默认值。
void harpo(int n, int m = 4, int j = 5); //valid
void chico(int n, int m = 6, int j); //invalid
实参按从左到右的顺序依次被赋给相应的形参,不能跳过任何参数。
harpo(2); // same as harpo(2, 4, 5)
harpo(1, 8); // same as harpo(1, 8, 5)
在设计类时,通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量。
默认参数代码如下:
//left.cpp -- string function with a default argument
#include<iostream>
const int ArSize{ 80 };
char* left(char* str, int n = 1); //function prototype
int main()
{
using namespace std;
char sample[ArSize]{};
cout << "Enter a string: ";
cin.get(sample, ArSize);
char* pr = left(sample, 10);
cout << pr << endl;
delete[] pr;
pr = left(sample);
cout << pr << endl;
delete[] pr;
system("pause");
return 0;
}
//consisting of the first n characters in the str string
char* left(char* str, int n)
{
if (n < 0)
n = 0;
char* p = new char[n + 1]{};
int i{};
for (; i < n && str[i]; i++)
p[i] = str[i];
while (i<=n)
{
p[i++] = '\0';
}
return p;
}
8.4 函数重载
P296
函数重载的关键是阐述特征标不同,即为参数数目或参数类型不同。
引用重载
编译器会把类型引用和类型本身视为同一个特征标,他们实参类型相同,因此不会发生重载。值得注意:常引用会发生函数重载。
double cube(double x); // not overloaded
double cube(double& x); // not oberloaded
//...
//const reference
double cube(double& x); // overloaded
double cube(const double& x); // oberloaded
const重载
下面的两个函数是重定义,不是函数重载,两个函数都不会改变a的值,本质上是一样的,所以会报错,提示函数重定义。
void func(int len); //not overloaded
void func(const int len); //not overloaded
//...
func(a);
而对于下面的两个函数会发生重载,第一个函数用于常规指针,另一个函数用于const指针,编译器会根据实参是否为const来决定使用哪个原型。
void func(char* str); //overloaded
void func(const char* str); //overloaded
//...
func(p1);
返回类型不同
【注意】:是特征标不同,而不是函数类型不同。
long gronk(int n); // same signatures,
double gronk(int n); // not overloaded
因此, 返回类型可以不同,但必须特征标不同才可以发生函数重载。
long gronk(int n); // overloaded
double gronk(double n); // overloaded
8.5 函数模板
如果需要多个将同一种算法用于不同类型的函数,请使用函数模板。
//function template prototype
template <typename/class T>
void Swap(T& a, T& b);
//标准C++98,添加关键字typename
注意:函数模板不能缩短可执行程序。最终的代码不包含任何模板,而只包含了为程序生成的实际函数。
函数模板的好处是:它使生成多个函数定义更简单、更可靠。
显式具体化
模板函数很可能无法处理某些数据类型(例如结构体)。因此一种解决办法为运算符重载,另一种为特定类型提供具体化的模板函数定义。
显式具体化如下:
template<> void Swap<job>(job &, job &);
template<> void Swap(job &, job &); // <job>可以省略
实例化和具体化
模板并非函数定义,但使用int的模板实例是函数定义-----隐式实例化
显式实例化如下:
template void Swap<job>(job &, job &);
同一个文件中使用同一种类型的显示实例和显式具体化将出错。
显示实例化,无论是否程序有用,编译器都会生成一个实例函数。
调用优先级:非模板函数>显式具体化>显式实例化>模版函数
关键字decltype(C++11)
作用:选择并返回操作数的数据类型
int x;
decltype(x) y; //make y the same type as x
C++后置返回类型
template <typename T1, typename T2>
auto gt(T1 x, T2 y) ->decltype(x + y) // 后置返回类型
{
//...
return x + y;
}