【C++ Prime Plus】第八章学习笔记
内联函数 inline
可以用来修饰简短的函数,在编译时建议编译器不作为函数,而展开编译。
内联函数无法递归。
引用变量
引用用来为一个其他变量起一个别名。
创建引用变量
不同于指针,在生命引用时必须对其进行初始化,指明是哪一个变量的别名。
int num = 5;
int &rnum1 = num;
int &rnum2;//报错
随后,rnum1永远指向num,所有对rnum的修改将作用于num。
rnum1 = 10;
cout<<rnum1<<endl;//10
cout<<num<<endl;//10
将引用用作函数参数
对于引用传参与返回引用我们可以用这样一种简单的原则来理解:
参数是引用类型,相当于声明一个引用,使用传入的变量初始化
void func(int& arg);
int a = 5;
func(a);
//传递参数的过程等价于
int& arg = a;
返回也类似
int& func(int& arg1){
...
return arg1;
}
int a = 5;
int& b = func(a);
//等价于
int& b = a;
我们可以将引用作为参数传递给函数,这样函数就可以对引用的内容进行修改。
void setNum(int& num){
num = 15;
return;
}
...
setNum(num);
cout<<num<<endl;//15
引用的属性与特别之处
非const引用不支持类型转换
按值传递的函数在接受参数时能够进行类型转换,例如
void func1(float arg);
int arg1 = 1;
func1(arg1);//不报错
但是非const引用则会报错,例如
void func1(float& arg);
int arg1 = 1;
func1(arg1);//报错
设计的思路是,如果我们对非const的参数进行修改,则会按照float的方式修改int变量,一定会导致不可预期的错误(float与int编码方式不同),所以无法编译。
const引用
以非const引用作为参数的函数无法接受const引用,因为const int& 无法用来初始化int& ,数值会被修改。
void func1(int& arg);
int num = 1;
const int& cr_num = num;
func1(cr_num);//报错
int num = 1;
const int& cr_num = num;
int& r_num = cr_num;//报错
左值与非左值
左值指的是可以被引用的对象,例如变量(const也可以),数组元素,被解引用的指针,非左值包括字面量,符号运算的结果,函数运算的结果(返回值非引用)。
前面我们提及了,以非const引用作为参数的函数无法进行类型转换,但是把const类型引用作为参数的函数可以在这两种程度上进行类型转换,并创建一个临时变量:
1.参数类型正确,但不是左值。
void func1(const int &arg);
...
int num = 5;
func1(num+5);//通过
很显然,num+5的类型是int,但是是右值。因为函数中不会对arg进行修改,所以可以进行类型转换。
2.参数的类型不正确,但可以转换为正确的类型。
void func1(const int &arg);
...
float f_num = 5.0f;
func1(num+2.0);//通过
总的来说,如果一个函数的目的是修改作为参数的引用,那么它就无法创建临时变量。
在编写函数的时候,应该尽可能地多使用const参数,避免对引用的修改,还能够兼容const与非const参数。
将引用用于结构与类
用引用传递结构与类能够提高效率。
一个函数能够返回引用,引用作为右值时有两种用法,一种是初始化其他引用,另一种是单独作为数值。
int& func1(int &arg){
return arg;
};
...
int num = 5;
int& r_num = func1(num);
r_num = 10;
cout<<num<<endl;//10
我们可以观察到,返回的引用赋值给了r_num,等价于
int& r_num = num;
函数返回引用是有要求的,必须返回一个参数中的引用,或者是全局变量中的引用或者是在函数中new的对象的引用,总的来说,被引用的对象不能在函数结束后被销毁。
int& func1(int &arg){
int result = 5;
return result;//运行后崩溃,因为result在函数结束后销毁。
};
函数返回值类型也可以声明为const引用
const int& func1(int &arg);
...
int& r_num = func1(...);//报错
const int& rc_num = func1(...);//通过
因为const引用不能被赋值给非const引用,反之可以。
对象,继承与引用
对于一般变量来讲,引用无法进行类型转换,但是衍生类的引用可以直接复制给基类的引用。
函数传参,结果返回的本质都是赋值
默认参数
比较简单 不过多赘述。
函数重载
参数类型不同的函数之间构成重载,但参数名字不同不行,返回值类型不同也不行。
//构成重载
int func1(int arg1);
int func1(float arg1);
//不构成重载
float func1(int arg1);
int func1(int arg1);
//不构成重载
int func1(int arg1);
int func1(int arg2);
判断的原理是,编译器需要能够根据参数的类型来分别调用哪个函数,如果编译器不能分辨就会报错。
这条原理可以用于解释这种现象
//不构成重载
int func1(int arg1);
int func1(int& arg1);
因为当传入一个int型变量时候,编译器也不知道要使用哪个函数。
当然也有特殊的情况,编译器能够根据引用是否是const来决定选择哪个函数,例如
int func1(const int& arg1);
int func1(int& arg1);
...
int num = ;
const int& cr_num = num;
int& r_num = num;
func1(cr_num);//int func1(const int& arg1);
func2(r_num);//int func1(int& arg1);
同样的例子是否适用于指针,可能需要因编译器而议。
函数模板
函数模板允许使用泛型来定义函数。
重载的模板
当同一个算法不能同时满足多种参数类型时,我们可以对模板也进行重载
template<class T>
void func1(T a){
cout << a << endl;
}
template<class T>
void func1(T a[],int n){
for (int i = ;i<n;i++){
cout << a[i] << endl;
}
}
模板的局限性
以下情况模板无法处理
template <class T>
void func1(T a, T b);
int arg1 = 1;
float arg2 = 1.0f;
func1(arg1, arg2);
也就是说,只有模板是无法处理类型转换的并对特殊的类型执行特殊的算法的,需要用接下来介绍的具体化,实例化等方法解决。
显式具体化
template <class T>
void func1(T a, T b){
cout << "template" << endl;
};
template <>
void func1<int>(int a, int b){
cout << "explicit specialization" << endl;
};
int arg1 = 1,arg2 = 2;
func1(arg1,arg2);//explicit specialization
可以观察到,显式具体化后优先级高于模板。
值得一提的是,显示具体化后仍然不支持float到int的类型转换。
具体化与实例化
首先声明,模板本身并不是函数的定义,他只是一个生成函数定义的方案。
编译器在编译时,检测到了需要用的funct1的int类型的实例化,就把函数实例化在代码区。
这就可以解释只有单纯的模板不能处理类型转换的问题,因为编译器不知道应该生成int类的代码还是float类的代码。
显式具体化也不支持类型转化,因为编译器仍然不知道是需要在最初的模板上生成float函数还是使用具体化的int函数。
除了显示具体化之外,我们还可以显示实例化
template
void func1<int>(int a, int b);
与显式具体化的区别是,显式实例化不能声明自己的函数内容。
我们也可以这样使用显式实例化。
int num_i = 5;
float num_f = 5.0;
func1<int>(num_i,num_f);
因为我们指定了需要编译器生成int类型的参数,所以这次可以支持类型转换,通过编译。
编译器选择使用哪个版本的函数
细节比较麻烦,这里只说一些细节。
1.完全匹配,但常规函数优先于模板,具体化的模板优先于隐式生成的模板。
2.提升转换 short->int,float->double
3.标准转换 int->char,long->double
4.用户自定义的转换
在转换的过程中,例如像TYPE 到const TYPE,TYPE 到TYPE&这样的转化被视为无关紧要的转换,不会影响优先级。
int可以同时作为Type与Type,编译器倾向于后者,因为做的转换更少。
更细节的内容见书。
绝大多数情况下,我们需要自己选择重载函数。
func1<>(arg1,arg2);//强制使用模板函数
func1<int>(arg1,arg2);//显式实例化int
函数模板的发展
我们需要一些更加自定义的功能。
template <class T1,class T2>
void func1(T1 a, T2 b){
? type ? xpy = x + y;
};
我们需要指明一种更加细化的类型,例如int+long long时应该选定long long,double+float时选的double,c++11的decltype提供了这样的功能。
template <class T1,class T2>
void func1(T1 a, T2 b){
...
decltype(x+y) xpy = x + y;
...
};
那么如何判断decltype(express)
的类型呢?
1.如果express不是被括号包括的标识符,那么就等于表达式本身的类型,即使带有const,引用,指针等。
2.如果express是一个函数调用,则等于函数的返回类型,但函数并不会被执行。
3.如果express被函数括起来的左值,则等于括起来的类型的引用。
double xx = 5.0;
decltype((xx)) r_d = xx; //double &
decltype((xx+2)) r_d = xx; //double 因为不是左值,见第四种情况
4.如果不是以上三种情况,则等于express的类型
int j = 3;
int &k = j;
int &n = j;
decltype(j + 6) i1;//int7
decltype(100L) i2;//long
decltype(k + n) i3;//int 因为k+n不是zuo值
为了简化,可以使用typedef
typedef decltype(x+y) xytype;
仍然有一个问题是decltype本身无法解决的,就是模板函数的返回值问题
template<class T1,class T2>
?type? func1(T1 A,T2 B){
...
return A+B;
}
因为函数类型的声明要早于参数A与B,所以无法使用decltype(A+B)
C++11标准中引入了箭头函数,使用方法如下
double func1(int A,float B);
//等价于下方
auto func1(int A,float B) -> double;
我们可以这样指定函数类型
template<class T1,class T2>
auto func1(T1 A,T2 B) -> decltype(A+B);