本章内容概览
- 内联函数
- 引用变量
- 按引用传递函数参数
- 默认参数
- 函数重载
- 函数模板
内联函数
内联函数是C++为提高运行速度的改进,和常规函数编写一样,只是编译器组合到程序的方式不同。常规函数调用时,编译器会根据函数地址跳转,执行代码,函数结束后跳转回来,而内联函数是编译器使用相应的函数代码替换函数调用,程序不会跳到另一处执行代码,所以内联函数运行速度快,但相比之下占用内存大。
要使用内联函数,应在声明和定义前加上inline关键字,不过通常会省略原型。注意,内联函数不能递归。
如计算平方的函数:
inline double square(double x) {return x * x;}
引用变量
引用是已定义的变量的别名。引用变量的主要用途是用作函数的形参,通过将引用变量用作参数,函数将使用原始数据,而不是其副本。
C++使用&运算符来声明引用:
int rats;
int& rodents = rats;
上述引用声明允许将rats和rodents互换,它们指向相同的值和内存单元。
引用和指针看起来很像,但还是有差别的,其中之一是声明引用时必须初始化:
int rat;
int& rodent;
rodent = rat; //invalid
引用更像是const指针,都必须在声明时初始化。
函数可以将引用用作函数参数:
void swap(int& a, int& b);
调用后,函数体中使用的相当于是真实的值,而不是拷贝值,这和指针,按地址传递参数很像。
同样,还可以使用const限定符修饰形参,表明不能修改引用:
void refcube(const double& ra);
如果实参和引用参数不匹配,C++将生成临时变量,如使用表达式作为实参传入。当前,仅当参数为const引用时,C++才能这么做。
如果引用参数是const,编译器将在下面两种情况下生成临时变量:
- 实参的类型正确,但不是左值
- 实参的类型不正确,但可以转换为正确的类型。
左值是可被引用的数据对象,如变量、数组元素、结构成员、引用和解除引用的指针都是左值,非左值包含字面常量和包含多项式的表达式。但常规变量和const变量都可以视为左值。
C++11还新增了右值引用,使用&&声明:
double&& rref = std::sqrt(36.00);
double j = 15.0;
double&& jref = 2.0 * j + 18.5;
引用的引入其实是为了方便将结构和类用作参数:
struct free_throws
{
std::string name;
int made;
int attempts;
float percent;
};
void set_pc(free_throw& ft);
同时返回值可以返回一个引用,如果不是的话,就是一个拷贝:
free_throw& set_pc(free_throw& ft);
默认参数
默认参数位于非默认参数后定义,调用函数时,要么不设置默认值,要么全部设置:
int harpo(int n, int m = 4, int j = 5);
函数重载
函数重载的关键是函数的参数列表——也成为函数特征标,如果两个函数的参数的数目和类型相同,同时参数的排列顺序相同,那么它们的特征标相同,变量名无关紧要。C++允许定义名称相同的函数,但需要特征标不同:
void print(const char* str, int width);
void print(double d, int width);
void print(long l, int width);
void print(int i, int width);
void print(const char* str);
不过这种时候如果传入了不正确的类型作为参数,C++会拒绝进行强制转换。
同时,下面两个函数原型的特征标相同,所以不是函数重载:
double cube(double x);
double cube(double& x);
类型引用和类型本身是同一个特征标。
同时,匹配函数时并不区分const和非const变量:
void dribble(char* bits); // 可以传入const和非const
void dribble(const char* bits); //只能传入const
总之注意,函数重载必须是特征标不同,如果要返回类型不同的话,特征标也必须不同。
函数模板
比如定义一个交换模板函数:
template <typename AntType>
void Swap(AntType& a, AnyType& b)
{
AnyType temp;
temp = a;
a = b;
b = temp;
}
模板并不创建函数,只是告诉编译器如何定义函数。比如:
int a = 1;
int b = 2;
Swap(a,b);
编译器会用int替换AnyTyoe,创建一个int版本的函数。
模板重载和常规函数重载类似,只要特征标不同即可。
显式具体化,对于常规模板函数定义,对于某些操作可能无意义,比如,整型可以直接在if语句中比较,但数组不行,同样结构体不能单独交换成员变量的值。
我们可以这么定义:
template <typename T>
void Swap(T&, T&);
template <> void Swap<job> (job&, job&);
job是一个结构体。如果传入非job类型值,则会使用普通版本模板函数,如果是job类型值,就会使用特定的模板。
实例化和具体化
在代码中包含函数模板本身并不会生成函数定义,只是一个用于生成函数定义的方案,编译器使用模板为特定类型生成函数定义时,得到的是模板实例,这种实例化方式是隐式实例化,C++还允许显示实例化,比如int类型:
template void Swap<int>(int, int);
这可以直接让编译器创建特定的实例。
而显示具体化:
template <> void Swap<int> (int&, int&);
区别在于<>
,同时显示具体化是要求编译器遇到对应参数时使用该模板创建定义,而显示实例化是直接创建对应类型的实例。
还可以通过使用函数来创建显示实例化:
template <typename T>
T Add(T a, T b)
{
return a + b;
}
...
int m = 6;
double x = 10.2;
cout << Add<double>(x,m) << endl;
<double>表明将非double参数转换为double参数。不过对于Swap不行:
int m =5;
double x = 14.3;
Swap<double>(m,x);
第一个参数是double&,不能指向int m。
对于函数重载、函数模板和函数模板重载,C++确定使用哪一个函数定义的策略是重载解析:
- 创建候选函数列表
- 使用候选函数列表创建可行函数列表
- 确定是否有最佳的可行函数
有时候我们可以自定义引导编译器使用哪种函数,比如可以:
lesser<>(m,n)
<>
表明使用模板函数。
模板函数发展
比如下面的模板函数:
template<typename T1, typename T2>
void ft(T1 x, T2, y)
{
...
xpy = x + y;
...
}
我们不知道xpy是什么类型的,因为x和y可以是任何类型。
C++提供了关键字decltype:
int x;
decltype(x) y;
这样,y的类型就和x类型一致,decltype的参数可以是表达式,上述模板函数可以变为:
template<typename T1, typename T2>
void ft(T1 x, T2, y)
{
...
decltype(x + y) xpy = x + y;
...
}
不过decltype不能处理返回语句的值的类型:
template<typename T1, typename T2>
?type? ft(T1 x, T2, y)
{
...
return x + y;
}
编译器无法预知x+y的类型,所以无法使用decltype,我们可以使用auto:
template<typename T1, typename T2>
auto ft(T1 x, T2, y)
{
...
return x + y;
}
网友评论