文章目录
【一】explicit、volatile、mutable关键字
【1. explicit:】
禁止隐式生成临时对象;在内置类型转换为自定义类型进行赋值时,当没有明确指出对象时,会隐式产生临时对象,这个是编译器推演产生的那么就存在不可控因素,假如我们不想它转换,但是它还是转换了,那么就有安全隐患的存在。
将其加在构造函数前面,可以禁止隐式生成临时对象
【2. volatile:】
禁止编译器优化。
编译器可能会对多个文件进行代码顺序调正,那么在多线程的情况下,代码顺序变了,结果就不一样了,是一种不可控的因素。所以可以用此关键字修饰,禁止编译器优化。
【3. mutable:】
去除常量的常性。
可以修改常量,如下面,test为常对象,不允许修改其成员变量,但是我现在必须要修改ma,那么就可以去除ma的常性,让其可以被修改。在智能指针时会用到。
class Test
{
private:
mutable int ma;//去除常性
int mb;
};
int main()
{
const Test test(10,20);
test.ma=100;//必须要修改ma,加上mutable就可以修改了
}
【二】函数模板
一、函数模板基本概念
(一)基本概念
想要写出一个可以适用于任何类型的函数,C用宏或泛型处理,但都有缺陷,前面我们说C++可以适用重载实现,但是当类型很多时,代码冗余度过高,不建议适用。C++还提供了另外一种方式,模板。基本概念有:
【1. 模板函数】
含有模板类型参数的泛化函数,不可以直接使用。
- 在定义点,只编译模板的头部,告诉编译器,这是一个函数模板,在调用点编译模板实例化后的模板函数。所以模板体在整个过程中不处理,函数模板中有错误时,只要不实例化为函数模板调用就不会报错。
- 格式:
template<typename T,……> //关键字<模板类型参数,……> void Test(T a)//函数模板
【2. 模板的实例化】
在主函数中,利用
Test<int>(10)//函数名<类型>(参数列表)
对函数进行实例化。即调用点通过具体类型替换模板列表参数的过程
【3. 函数模板】
将模板函数实例化后的函数,此时的函数已经拿实例化的类型替换了模板类型参数,生成了一份代码,在程序运行,编译模板函数。
用一张图表示三者的关系
注意:
- 根据是否上方紧跟着template关键字来判断是否为一个模板,存在就是模板。
- 函数模板是一个壳子,不编译实体部分。
- 模板函数是用函数模板刻画出来的产物,所以逻辑语法必须和函数模板一致。
(二)实现两个数求和函数模板
我们看一个具体的例子
/*1.两个数求和*/
template <typename T1,typename T2>
T1 TwoSum(T1 num1,T2 num2)
{
return num1+num2;
}
int main()
{
std::cout<<TwoSum<int,int>(1,2)<<std::endl;
}
可以处理任意两个类型的数值相加,如int,double等。我们通过这个函数看一下函数模板,模板函数,模板的实例化这三个概念:
二、函数模板的参数演绎
当实例化函数模板时,会用对应的类型替换模板类型参数,那么这个替换的过程,是简单的文本替换#define,还是类型重定义typedef呢,如TwoSum<int,int>(10,20)用int替换T是怎么的呢?
模板的处理,在编译阶段,那么就是在编译阶段进行类型重定义得到模板参数。就是做了typedef int T 。在调用点设计者传入类型进行模板实例化,具体类型会替换T,生成模板函数。
现在尝试这样的调用:TwoSum(10,20)在调用点设计者没有给出类型,但是程序却调用成功,表示生成了模板函数,存在具体类型替换T的过程,原因是:系统通过实参推演给出了具体类型,这就是模板的实参推演。
模板的实参推演:调用点没有给出类型的情况下,由系统通过实参推演出来我们需要的类型。
可以这样理解:模板头部已经编译了,这时编译器知道模板头部需要的是两个相同的类型,然后根据实参10,20是两个相同类型的整型int,由此推演出来模板实例化用整型int来替换,生成了一份int类型的模板函数
模板的实参推演需要注意:
- 不能让编译器产生二义性。 如函数模板的头部需要两个同一类型的参数,但是调用点传入10,10.1数值,那么编译器就会推演出int,double两种类型,这时它不知道应该拿那个类型去实例化,这就让它产生了二义性,它就报错。
- 一趟调用过程中,一个模板类型参数只能用一个类型来替换
- 存在实参时,才可以进行模板实参的推演。
三、函数模板的特例化
我们现在尝试写一个比较大小的函数模板:
/*3.比较大小*/
template <