C++11是由C++标准委员会指定的语言规范。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140 个新特性,以及对C++03标准中约600个缺陷的修正,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅 功能更强大,而且能提升程序员的开发效率。
列表初始化{ }
C++11扩大了{ }的适用范围,使得{ }可以用于所有内置类型和用户自定义类型。
具体例子:
#include<iostream>
#include <vector>
#define NUM 2
class Base
{
public:Base(int left, int right):_left(left),_right(right){}private:int _left;int _right;
};
int main()
{int array[2] = { 1, 2 };std::vector<int> v = { 1, 2, 3 };Base b = { 1, 2 };return 0;
}
变量类型推导auto
应用场景:变量类型太长或者创建变量不清楚类型时。
具体例子:
#include <iostream>
#include <unordered_map>
int main()
{std::unordered_map<std::string, std::string> hash = { {"apple", "苹果"}};std::unordered_map<std::string, std::string>::iterator it = hash.begin();auto cur = it;return 0;
}
注意事项:
1、auto类型不可做函数形参,auto声明的变量必须由编译器在编译时期推导而得。auto推导的类型都是已经被初始化的。
2、当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
3、用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
decltype类型推导
应用场景:解决需要根据表达式运行完成之后所得结果的类型推导问题。
具体例子:
#include <iostream>
int Func()
{return -1;
}
int main()
{int a = 10;int b = 20;decltype(a + b) c;decltype(Func()) funcType;std::cout << typeid(funcType).name() << std::endl;//intreturn 0;
}
范围for
C++11中提供的一种遍历容器的方法
具体例子:
#include <iostream>
#include <vector>
#include <list>
int main()
{std::list<int> _list = { 1,2,3 };std::vector<int> _v = { 1,2,3 };for (int e : _list)std::cout << e << std::endl;for (int e : _v)std::cout << e << std::endl;return 0;
}
可以结合auto来进行类型推导,也可以在类型后加&,表示以引用的方式来依此提取右边容器内的元素。
final与override
final和override在C++的多态中使用。
1、final:修饰虚函数,表示该虚函数不能被重写
2、override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写则编译报错。
具体使用:
class Car
{
public:virtual void Drive() final {}
};
class Benz : public Car
{
public:virtual void Drive() {cout << "Benz-舒适" << endl;}
};class Car
{
public:virtual void Drive(){}
};
class Benz : public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};
默认成员函数控制
默认成员函数控制又包括显示缺省函数和删除默认函数。
在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构 函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带 参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程序员可以控制是否需要编译器生成。
显示缺省函数
C++11中,可以在函数声明或定义时加上 =default 显式的指示编译器生成该函数的默认版本,用=default 修饰的函数称为显式缺省函数。
具体用法:
class Base
{
public:Base(int left, int right):_left(left), _right(right){}Base() = default;
private:int _left;int _right;
};
删除默认函数
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他 人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对 应函数的默认版本,称 =delete 修饰的函数为删除函数。
具体使用:
class Base
{
public:Base(int left, int right):_left(left), _right(right){}Base() = default;Base(const Base&) = delete;Base& operator=(const Base&) = delete;
private:int _left;int _right;
};
右值引用
为了提高程序运行效率,C++11引入右值引用,顾名思义,右值引用只能引用右值。形似int&&
左值与右值
1、 普通类型的变量,因为有名字,可以取地址,都认为是左值。
2.、const修饰的常量,不可修改,只读类型的,理论应该按照右值对待,但因为其可以取地址(如果只是 const类型常量的定义,编译器不给其开辟空间,如果对该常量取地址时,编译器才为其开辟空间), C++11认为其是左值。
3、如果表达式的运行结果是一个临时变量或者临时对象,认为是右值。
4、 如果表达式运行结果或单个变量是一个引用则认为是左值。
左值引用和右值引用
左值引用只能引用左值,但const左值引用既可以引用左值,也可以引用右值。
右值引用一般只能引用右值,但可以引用move后的左值。
具体例子:
int main()
{int a = 10;int& pa = a;int& pb = 10;//报错,左值引用无法引用右值const int& ppa = a;const int& ppb = 10;int&& pa2 = 10;int&& pb2 = a;//报错,右值引用无法引用左值int&& ppa2 = std::move(a);return 0;
}
引入右值引用的具体原因
本质是为了减少拷贝次数,提高效率。例如一个类中的拷贝构造和操作符重载,传值返回时,必须拷贝构造一个临时对象,再通过返回的临时对象来构造接受结果的对象。
看下面一段代码:
class Base
{
public:Base(int left, int right):_left(left), _right(right){}Base operator+(const Base& b){Base tmp (_left + b._left, _right + b._right);return tmp;}
private:int _left;int _right;
};
int main()
{Base a(1, 2);Base b(3, 4);Base c(a + b);return 0;
}
在operator+中:tmp在按照值返回时,必须创建一个临时对象,临时对象创建好之后,tmp就被销毁了,最后使用返回的临时对象构造c,c构造好之后,临时对象就被销毁了。仔细观察会发现:tmp和所形成临时对象,以及c,每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大。
移动语义
C++11提出了移动语义,将一个对象中的资源转移到另一个对象中。依然是上面的代码,通过移动语义,会将tmp返回时构造的临时对象识别为”将亡值“,C++11认为临时对象为右值,将返回对象的资源直接转移给该临时对象,而再构造c时,又会调用移动语义,直接将返回的临时对象资源转移给对象c。
注意:
1、 移动构造函数的参数不能设置成const类型的右值引用,因为资源无法转移而导致移动语义失效。
2.、在C++11中,编译器会为类默认生成一个移动构造,该移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造。
当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中std::move()函数位于 头文件中,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。被转化的左值,其生命周期并没有随着左值的转化而改变,即std::move转化的左值变量value不会被销毁,但会变的无效。
完美转发
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。所谓完美:函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相 应实参是右值,它就应该被转发为右值。
万能引用,既可以接收左值,也可以接收右值,但万能引用虽然都能接收,但是统一都退化为左值,所以要通过完美转发来保持实参原有的属性。
具体例子:
#include <iostream>
void Fun(int& x)
{std::cout << "left_value ref" << std::endl;
}
void Fun(int&& x)
{std::cout << "right_value ref" << std::endl;
}
template<typename T>
void PerfectForward(T&& t)
{ Fun(std::forward<T>(t));
}
int main()
{int a = 10;PerfectForward(a);PerfectForward(std::move(a));return 0;
}
移动语义具体例子:
#include <iostream>
class Base
{
public:Base(Base&& b):_left(std::move(b._left)),_right(std::move(b._right)){}Base operator=(Base&& b){_left = b._left;_right = b._right;}
private:int _left;int _right;
};
lambda表达式
本质是一种匿名函数。
应用场景:使用algorithm中的sort时,对自定义类型对象进行排序,需要给出对应的比较规则,往往都是通过仿函数来实现,但每出现一个类别,就要定义一个类,写一个用于比较的仿函数,比较繁琐,而lambda表达式可以很好的解决这个问题。
具体例子:
sort(goods, goods + sizeof(goods) / sizeof(goods[0]), [](const Goods& l, const Goods& r)->bool{return l._price < r._price;});
lambda表达式语法
[capture-list] (parameters) mutable -> return-type { statement }
1、[ ]捕捉列表,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
包括
父作用域:指包含lambda函数的语句块。
[ = ]:对父作用域进行传值捕捉
[ & ]:对父作用域进行传引用捕捉
[ val ]:对val变量进行传值捕捉
[ &val ]:对val变量进行传引用捕捉
[ this ]:对当前this指针进行传值捕捉
2、( )参数列表,和普通函数的参数列表相同,可以不传参数。
3、mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
4、->return-type,表示该lambda表达式最终返回值的类型,可以省略。
5、{ }函数体,除了可以使用其参数外,还可以使用所有捕获到的变量。
lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
注意:. lambda表达式之间不能相互赋值,即使看起来类型相同。
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一 个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。