一、类的新功能
1、默认成员函数
原来C++类中,有6个默认成员函数:
1、 构造函数
2、 析构函数
3、 拷贝构造函数
4、拷贝赋值重载
5、取地址重载
6、const 取地址重载
默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
而在下面的情况中,我们需要自己写移动构造和移动赋值:
1、拷贝对象需要深拷贝时,自己写移动构造和移动赋值,比如:string,vector,list。
2、针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
* 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
* 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
* 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
注:一般来说,要写析构函数的类那么就需要自己写移动构造和拷贝构造。
2、类成员变量初始化
C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量缺省值。
class B { public: B(int b = 0) :_b(b) {} int _b; }; class A { public: void Print() { cout << a << endl; cout << b._b << endl; } private: // 非静态成员变量,可以在成员声明时给缺省值。 int a = 10; B b = 20; };
3、关键字default
强制生成默认函数的关键字。
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
class Person { public: Person(const char* name = "", int age = 0) :_name(name) , _age(age) {} //拷贝构造函数 Person(const Person& p) :_name(p._name) ,_age(p._age) {} Person(Person&& p) = default; private: string _name; int _age; };
4、关键字delete
禁止生成默认函数的关键字。
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
5、final与override关键字
final : 修饰一个类,使它不能够被继承。final既可以修饰类也可以修饰虚函数。修饰虚函数,表示该虚函数不能再被重写。
override : 在继承中,检查子类是否重写了父类的虚函数,没有重写就报错。
二、可变参数模板
C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,而在C++98/03中,类模版和函数模版中只能含固定数量的模版参数。
下面就是一个基本可变参数的函数模板:
Args是一个模板参数包,args是一个函数形参参数包
声明一个参数包 Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args> void ShowList(Args... args) {}
我们在使用时可以传入不同个数的参数,如下:
int main() { string s("hello"); ShowList(); ShowList(1); ShowList(1, 'A'); ShowList(1, 'A', s); return 0; }
语法不支持使用args[i]这样方式获取可变参数,那么我们要如何一一取出参数包里的参数呢? 取出方法如下:只能通过展开参数包的方式来获取参数包中的每个参数。
1、递归函数方式展开参数包
// 递归终止函数 void ShowList() { cout << endl; } // 展开函数 template<class T, class ...Args> void ShowList(const T& val, Args... args) { cout<<val<<" "; ShowList(args...); }
2、逗号表达式展开参数包
这种展开参数包的方式,不需要通过递归终止函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
template <class T> void PrintArg(const T& t) { cout << t << " "; } //展开函数 template <class ...Args> void ShowList(Args... args) { int arr[] = { (PrintArg(args), 0)... }; cout << endl; }
三、empalce_back函数
我们看vector中的empalce_back 就使用了上面所讲到的参数包。我们来看个关于push_back 和 emplace_back用法的例子:
vector<pair<string, int>> v; v.push_back(make_pair("sort", 1)); v.emplace_back(make_pair("sort", 1)); v.emplace_back("sort", 1));
push_back 和 emplace_back都可以插入数据,但是后者的第二种插入方式是不是更简便一些呢?两者又有什么区别呢?
首先,push_back 的插入需要先构造一个 pair 的变量,然后再拷贝构造。emplace_back 的插入则是既可以像 push_back那样用,也可以用参数包的形式,依次取出参数,第一次取出的参数去初始化 pair的first,第二次取出的参数去初始化 pair 的 second。所以在这种情况下 emplace_back 的效率比push_back的高。只有模板的可变参数包能够实现 emplace_back 的这种用法。