第八章. 函数探幽
1. C++ 内联函数
正常创建函数后,程序调用该函数时,程序跳转到函数地址。使用内联函数时,编译器在编译时,会直接将函数代码替换函数调用,而省去程序跳转到函数地址的时间,加快运行速度。但是内联函数每次调用时,都会创建函数副本,调用十次就创建十个。因此要创建内联函数,代码行数不宜多,而且内联函数无法递归。使用内联函数,要在函数声明和定义前加上关键字 inline。相比于宏定义,内联函数可以更好的实现按值传递参数的目的。内联函数与宏定义类似的事,它可以定义在头文件中,被多个源文件包含时,不会违反单定义规则,它的作用域好像是内部链接的。
inline double squar(double x) { return x * x; } // 内联函数
#define SQUAR(X) X * X // 宏
int a = aquar(3+2); // a = 5 * 5 = 25
int b = SAUAR(3+2); // b = 3 + 2 * 3 + 2 = 11
2. 引用变量
引用相当于给变量起别名,引用和原变量名都指同一个变量。引用定义为:
int rats;
int & rodents = rats; // rodents是rats的引用,&不是取地址符,而是指向int的引用
引用必须在声明时就初始化,不能先声明再赋值,就像声明 const 常量一样。
C++ 中,引用的重要作用是作为函数参数,引用作为参数,可以改变原始变量(给原始变量起别名,函数直接对原始变量操作),函数调用用实参初始化形参:
void swapr(int & a, int & b);
理解引用可以把它想成指针,直接对内存上的原始数据进行修改。
引用是变量的别名,因此在形参是引用的函数比形参是一般变量的函数,实参要求更严格:
int cube(int a);
int recube(int &b);
int x = 2;
int m = cube(x + 2); // 合法
int z1 = cube(4.13); // 合法
int n = recube(x + 2); // 不合法
int z2 = recube(4.13); // 不合法
而当形参是 const 的引用时,
int con_recube(const int& c);
int num1 = con_recube(x + 2); // 合法
int num2 = con_recube(4.13); // 合法
函数实参的类型正确,但不是左值;或者类型与形参不匹配但可以转换成匹配类型,函数将创建临时变量,将其初始化为形参对应的类型的值,然后函数形参为该临时变量的引用,函数调用结束后释放该临时变量。形参是指针时,与引用的效果相同。也就是说,如果接受引用或指针的函数的意图是修改参数,那么创建临时变量就会修改临时变量而不是参数,则在创建临时变量时会遭到编译器阻止报错,对于不修改参数却想使用引用或指针的函数,参数可由 const 限定,会消除这种副作用而创建临时变量,不修改形参。
3. 默认参数
声明默认参数时,只能从右向左添加默认值,函数定义时,省略默认值。
int harpo(int n, int m = 4, int j = 5); // 合法
int harpo(int n, int m, int j) // 定义时省略默认值
{
...
}
int chico(int n, int m = 6, int j); // 非法
调用默认函数时,不能跳过参数
int beeps = harpo(3, , 8) // 非法
4. 函数重载
函数重载只跟参数的特征标,即参数的数目、类型或者顺序有关,与函数返回值无关。
在调用重载的函数时,没有完全匹配实参的函数时,将使用标准类型强制转换,若有多个重载都可以强制转换匹配时会报错。引用并不会重载,而 const 会。
void dribble(const char*)
void dribble(char*) // 重载
重载函数的参数是左值、const及右值时,将调用最匹配的函数。
void stove(double & r1);
void stove(const double & r2);
void stove(double && r3);
double x = 55.3;
const double y = 32.2;
stove(x); // 调用void stove(double & r1)
stove(y); // 调用void stove(const double & r2)
stove(x+y); // 调用void stove(double && r3)
5. 函数模板
函数模板的格式:
template <typename T> // typename可以用class替换
void Swap(T & a, T & b); // 函数声明
...
template <typename T>
void Swap(T & a, T & b) // 函数定义
{
T temp;
temp = a;
a = b;
b = temp;
}
T 表示任意标准类型,关键字 template、typename以及尖括号不能省略,在声明和定义时都要有。函数模板在每次调用模板函数时自动生成相应的函数定义,并不缩短程序。
函数模板也可以重载,且重载函数的形参也可以有其他类型,并不只有T类型。
template <typename T>
void Swap(T a[], T b[], int n);
对于模板函数定义中,不适用的类型,如 a = b 中,T 是数组形式,或者 if (a > b),而 T 是结构等。可以用显示具体化处理这种问题。
struct job
{
...
};
template <> void Swap<job>(job &, job &); // 函数模板显示具体化声明,第一个尖括号里没有参数
... // Swap<job>的<job>是可选的
template <> void Swap<job>(job &, job &) // 函数定义
{
...
}
编译器使用模板生成的函数定义,即为模板实例,这种实例的方式称为隐式实例化。c++ 允许显示实例化,在调用模板函数时,采用:
int main()
{
...
Swap<int>(m, n); // 显示实例化,告诉编译器生成T是int的函数定义,并将
// m, n的类型强制转换成int
std::cout << m << n;
}
也可以在主函数中声明一个显示实例化的模板函数
int main()
{
template void swap<char, char>(char& a, char& b);
char g, h;
swap(g, h); // 使用声明的显示实例化模板
}
显示实例化声明时, template 后面没有尖括号,而显示具体化函数声明和定义时有。在同一文件中,同时使用同一类型的显示实例化和显示具体化,将会报错。
int m = 5;
double x = 14.3;
Swap<double>(m, x); // 将会报错,m,x 都是非const引用,不会创建副本。
// m的引用不能强制转换成double
如果重载或模板等有多个匹配的原型,将会报错。匹配原型的函数有优先级,形参是指针或引用时,实参是非const的,那么形参是非const的优先于形参是const的函数。非模板函数优先于模板函数。显式具体化优先于隐式实例化。多个匹配的函数,会选择更具体的函数。
在函数调用时,可以用尖括号告诉编译器使用模板:
template <typename T>
T lesser(T a, T b);
int lesser(int a, int b);
int main()
{
int m = 4;
int n = 8;
double x = 15.3;
double y = 21.4;
lesser(m, n); // 调用非模板函数
lesser(x, y); // 调用模板函数
lesser<>(m, n); // 显示告诉编译器使用模板函数,尖括号里面为空
lesser<int>(x, y) // 显示实例化
return 0;
}
decltype关键字可以推断变量类型:
//decltype(exp) var;
// exp没括号,var的类型与exp相同
double x = 5.5;
double y = 7.9;
double & rx = x;
const double *pd;
decltype(x) w; // w是double类型
decltype(rx) u = y; // rx是double &类型
decltype(pd) v; // pd是const double *类型
// exp是函数调用,var的类型与函数返回类型相同
long indeed(int);
decltype (indeed(3)) m; // m是long类型,不会实际调用函数,编译器只查看
// 函数返回类型
// exp有括号,var的类型是指向exp的引用
double xx = 4.4;
decltype((xx)) r2 = xx; // r2的类型是double &
decltype(xx) w = xx; // w的类型是double
对于下面的情况:
template<class T1, class T2>
decltype(x+y) gt(T1 x, T2 y); // 不可以这样推断返回类型
x 和 y并没有声明,编译器无法得知 x y 是什么类型。
可以采用返回类型后置的方式:
template <class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y); // 声明
// 定义
template <class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y)
{
...
}
这样 decltype 在参数 x y 的声明后面。