【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);
posted @ 2021-07-12 00:02  Dhy2b  阅读(74)  评论(0编辑  收藏  举报