类的新增功能
默认构造
在原有的C++类中,会有6个默认的成员函数,分别是构造函数、析构函数、拷贝构造、赋值重载、取地址重载、const取地址重载
最重要并且经常用的是前四个,后两个用处不大
所谓默认成员函数是当构建一个类的时候,我们不写这些函数,编译器会帮我们生成一个默认的
在C++11中,又新增了移动构造函数和移动赋值重载,这两个我们在上一篇中有详细解释
但是生成的规则会有不同
当你没有显式的写析构、拷贝构造和赋值重载时,编译器会自动生成默认的移动构造和移动赋值重载
默认生成的移动构造函数,对于内置类型会执行按字节拷贝,对于自定义类型,就需要看这个类型是否定义移动构造,有的话就直接调用,没有的话就调用拷贝构造
移动赋值重载和上面的移动构造完全类似
但是当我们自己写了移动构造或者移动赋值,编译器就不会生成拷贝构造和赋值重载了
类成员变量初始化
C++11允许在类定义的时候给成员变量缺省值,默认生成的构造函数会使用这些缺省值进行构造,例如
1 | class Date |
强制生成默认函数的关键字default
这里就是强制生成我们想要的默认函数,我认为这个关键字一是为了方便我们懒得写的时候,并且默认构造函数已经足够使用了,另一方面可以当作一个占位的作用,具体使用形式如下
1 | class Date |
禁止生成默认函数的关键字delete
在C++98中,我们可以将默认函数放在private中就可以实现限制这个函数的生成和使用,在C++11中和default的使用类似
1 | class Date |
继承和多态中的final和override关键字
这里我们在继承和多态中已经讲的非常详细,final可以加在类后面表示该类不能被继承,final和override也可以加在成员函数中,前者表示该函数不能被重写,后者表示必须重写该函数,否则报错
可变参数模板
C++11中的可变参数模板是可以接收可变参数的函数模板和类模板
在原先的模板中,我们只能接受一定数量的模板参数,在可变参数模板这里比较抽象,使用也有一定技巧,需要在实践中才能真正理解
这里我们主要简单介绍
一个简单的可变参数模板如下
1 | template<class ...Args> |
上面的参数args前面有省略号,表示一个可变参数模板,带省略号的参数我们称之为参数包,里面包含了有限多个参数,我们无法直接从参数包args中获取每个参数,只能通过递归的方式来打开这个包,一步步获取
递归打开参数包
1 | // 递归终止函数 |
逗号表达式打开参数包
这种方式其实并不复杂,主要是利用了初始化列表和逗号表达式来进行展开的过程,其实原理非常简单,看代码就能看懂
1 | template<class T> |
用途
我们在各个容器的C++11版本接口里面可以看到emplace,支持模板的可变参数,万能引用,那么inset和emplace有什么用呢
emplace支持可变参数,那么在我们需要一次性传入pair变量的时候,就不需要make_pair函数直接构造了,他会自动拿收到的两个参数进行构造
lambda表达式
当我们在使用仿函数的时候,为了一个简单的功能就要写一个类,这么做还是觉得稍微有点子麻烦
但是诶,他们发现这个lambda还挺好用的,用一句话就能表示出一个简单的函数
例如我们想要表示一个比较大小的函数,或者说是表示升序一个排序仿函数
1 | [] (int x, int y) -> bool {return x<y;}; |
诶这么一写,就是一个函数
语法
这里的方括号是lambda表达式的标志,用于告诉编译器,接下来这是一个lambda表达式,你要准备好接收了
接下来圆括号里面的是参数列表,箭头指向的就是返回值和函数主体了
一般lambda表达式不要写的太长了,否则可维护性就会变道很差
但是有人要说了,那万一我要用全局的变量呢,之前我们讲过,一对大括号其实就是一个小环境,那么这个小环境里面是不能随便用外面的变量的,因为函数和类本身的设计还是需要高内聚低耦合的
那其实这个方括号里面就可以用来写“全局的变量”,表示,这些变量我要用,别给我禁了
这个方括号里面的内容我们称之为捕捉列表,就好像是从全局中捕获来的变量一样
参数列表和普通的函数列表一样,如果这个函数不需要参数列表,就可以把小括号一起省略
返回值类型在没有返回值的时候可以省略,但其实有返回值的时候也可以省略,编译器会自动进行推导
实际上lambda的本质还是一个类,也就是类似于范围for,写出来是一个样,最后还是还原成他最本来的样子,那么lambda最后还原回去其实就是一个仿函数
那么被捕捉或者定义的变量就是类中的成员变量,但是lambda的一个特性就是,他默认所有的成员变量都是const的,如果想要修改就需要在参数列表之后加上一个mutable
我们甚至可以定义一个变量接收lambda表达式对其进行复用
例如
1 | int x = 3; |
因为我们其实说了,lambda表达式的本质其实就是一个仿函数,一个类,拿我们也就可以使用typeid.name来读取这个类的名称
实际上他的名称几乎是完全随机不重复的,根据时间,mac地址等内容随机生成
lambda的捕捉列表
但是有的人又要说了,诶我不一定希望所有的成员变量都可以被改呀,我要是想要使用全部的参数怎么办
基本规则如下
语法 | 说明 |
---|---|
[变量名称] | 传值某一个变量 |
[=] | 传值捕捉父作用域的所有变量,包括类中的this |
[&变量名称] | 传引用某一个变量 |
[&] | 传引用捕捉父作用域的所有变量,包括this |
[this] | 捕捉当前的this指针 |
这些语法可以组合使用,用逗号分割即可,但是不允许重复传递,并且lambda表达式之间不能互相赋值,因为我们讲过他们的类名称实际上是不同的,也就是不同的类
function包装器
出现这个东西的原因主要是因为我们可以调用的东西太多了,而且形式非常相似,函数调用,函数指针调用,仿函数对象,lambda对象都是一模一样的调用方式
那我们可不可以把这些东西全部都放到一起去,包装起来,这样就能使用统一的调用方式来使用上述提到的内容,甚至达到意想不到的效果
例如
1 | template<class T1, class T2> |
这里我们发现其实类型都是没有问题的,但是一旦我们想要使用这些内容,将其作为类似于回调函数的部分来使用,就会出现一些问题
1 | template<class Func, class T1, class T2> |
这里是我们设置的函数,调用如下
1 | int main() |
这里其实就会出现一些性能损失,因为在实例化模板的时候就会重复实例化
但是使用包装器就能解决这个问题,例如
1 |
|
这样子我们在传入回调时,其实就只会实例化一份function对象的类型了
回调函数使用比较多的场景主要还是命令行之类的解释器,还有之后我们实现的线程池,创建线程的时候会用到
目前只需要了解大致的原理即可,function的本质还是一个类