1.统一初始化initializer_list
引入对数组,容器,对象的全新初始化方式:
int main()
{
int arr[]{ 1,2,3 };
vector<int> v{ 1,2,3,4 };
map<int, string> m{ {1,"a"},{2,"b"} };
string str{ "hello" };
int* p = new int[5]{ 1,2,3,4,5 };
}
为什么可以这样用,这是因为c++11引入了initializer_list,当编译器看到{ },就会将将其转换为initializer_list,当然这些容器,对象都必须提供一个参数为initializer_list的构造函数:
可以看到,我们初始化A对象时传递的{ 1,2,3,4 }会被转换为initializer_list对象丢给A的构造函数,并且initializer_list可以像普通容器一样遍历。
2.类型推导
在传统 C 和 C++中,参数的类型都必须明确定义,C++11 引入了 auto
和 decltype
这两个关键字实现了类型推导:
auto
例如传统C 和 C++中遍历vector你得这样写:
int main()
{
vector<int> v{1,2,3,4,5,6};
for (vector<int>::const_iterator itr = v.cbegin(); itr != v.cend(); ++itr) {
cout << *itr << endl;
}
}
使用auto
编译器可以自动推导出变量类型,就可以这样写:
int main()
{
vector<int> v{1,2,3,4,5,6};
for (auto itr = v.cbegin(); itr != v.cend(); ++itr) {
cout << *itr << endl;
}
}
auto
用在容器便利中真的非常好用,auto
常见其他用法:
auto add(int a, int b);
int main()
{
auto i = 10; //i为int类型
auto str = "hello"; //str为string类型
auto p = new int; //p为指针类型
}
需要注意auto
不能对函数参数,数组使用。
decltype
auto
关键字只能对类型进行推导,decltype
则可以对表达式进行推导,传统c++中我们可以写出下面的代码:
template<typename T, typename U,typename R>
R add(T t1,U u1) {
return t1 + u1;
}
定义了一个模板函数,这个模板中必须显示声明返回值类型R或者其他类型,但有时候我们并不知道一个函数返回值类型是什么,这是可以使用decltype
来对表达式t1 + u1
进行推导,但我们并不能这样写,因为编译器解析到decltype(t1 + u1)
时t1 ,u1还未被声明
template<typename T, typename U,typename R>
decltype(t1 + u1) add(T t1,U u1) {
return t1 + u1;
}
正确写法是这样,利用auto 关键字将返回类型后置:
template<typename T, typename U,typename R>
auto add(T t1,U u1)->decltype(t1 + u1) {
return t1 + u1;
}
在C++14中则可以直接写为这样了:
template<typename T, typename U,typename R>
auto add(T t1,U u1) {
return t1 + u1;
}
3.智能指针shared_ptr
shared_ptr
用来智能管理C++的对象的生命周期,不用再手动释放对象空间,shared_ptr
需要引入头文件 <memory>
,用法如下:
class A {
public:
A() {
cout << "A...." << endl;
}
~A() {
cout << "~A..." << endl;
}
void func() {
cout << "func..." << endl;
}
};
int main()
{
shared_ptr<A> a(new A);
a->func();
}
shared_ptr
虽然是一个对象,但是可以像普通指针一样的使用,即上面的a就等价于A*。
4.基于范围的 for 循环
C++11引入了新版for循环,如下:
int main()
{
vector<int> v{ 1,2,3,4,5 };
// 使用引用则可以对v中的数据继续修改
for (auto &i:v) {
cout << i << endl;
}
}
基于范围的 for 循环配合上auto
是真的好用。
5.右值引用和move语义
一般来说,可以对其取地址的变量为左值,不能取其地址的变量为右值,在通俗点就是左值可以出现在赋值符号的左边,右值只能出现在赋值号右边。
典型的左值:
int i = 2; // i 是左值
int *p = &i; // 可以对i取地址
class A;
A a; // 自定义类实例化的对象也是左值
典型右值:
int i = 1; // 1 是右值
int j = i + 1; // (i + 1) 是右值
int *p = &( 2 );//错误,不能对右值取地址
i + 2 = 4; // 错误,不能对右值赋值
class A;
A a = A(); // A() 是右值
左值引用:
int i = 2; // i 是左值
int &i1 = i; //对左值i取引用
int *p = &i; // 可以对i取地址
int &p1 = *p; //对左值p取引用
class A;
A a; // 自定义类实例化的对象也是左值
A &a1 = a; //对左值a取引用
在c++11之前的所有引用都是左值引用,不能用左值引用去引用右值,例如:
c++11中引入了右值引用,写法为&&
,使用右值引用就可以对右值进行引用了,你就可以写
右值引用的主要目的是为了配合c++11引入的另一重要概念move语义,在传统c++中经常会用到拷贝构造函数,拷贝构造函数通常会对对象进行深拷贝,深拷贝通常会很耗时,因为要重新分配内存,并把原有对象数据拷贝到新分配的内存中,而有时我们进行深拷贝的对象在深拷贝完成之后就被回收了(临时变量),这时如果可以不进行深拷贝而是直接复用此对象的资源则会大大提高性能,这其实就是c++11引入的移动构造函数做的事情,减少不必要的深拷贝
class A {
public:
A() :i(new int[1000]) {
std::cout << "A 构造函数..." << std::endl;
}
A(const A& a):i(new int[1000]) {
//将a对象的i数组中的所有数据拷贝到新对象中
memcpy(i, a.i, 1000 * sizeof(int));
std::cout << "A 拷贝构造函数..." << std::endl;
}
A(A&& a):i(a.i) { //浅拷贝,仅仅修改a.i指针指向的内存
a.i = nullptr;
std::cout << "A 移动构造函数..." << std::endl;
}
int* i;
~A() {
std::cout << "A 祈构函数..." << std::endl;
}
};
int main()
{
A a;
A a1 = a;
A a2(std::move(a));
}
可以看出来使用移动构造函数其实是浅拷贝,并且使用了移动构造函数来赋值的对象不能在使用了,它分配的内存已经被置为nullptr了,
为区别于拷贝构造函数,移动构造必须接收右值引用,再看一个移动构造函数的例子:
int main()
{
vector<int>v{ 1,2,3,4 };
vector<int>v1 = v;
for (auto i:v) {
cout << "v :" << i << endl;
}
cout << "------------" << endl;
for (auto i : v1) {
cout << "v1 :" << i << endl;
}
cout << "------------" << endl;
vector<int>v2 = std::move(v);
for (auto i : v2) {
cout << "v2 :" << i << endl;
}
cout << "------------" << endl;
cout << "v.size :" << v.size() << endl;
}
输出结果为:
可以发现使用移动构造函数来构造v2
的v
对象的size
已经为0了。
6.Lambda表达式
Lambda表达式主要用于创建匿名的函数对象。
形式:
[外部变量访问方式说明符](参数列表)->返回值类型
{
函数语句
}
外部变量访问方式说明符有以下几种类型:
[ ]:不使用任何外部变量
[=]:以传值的形式使用外部变量
[&]:以引用的形式使用外部变量
[x,&y]:x以值的形式,y以引用的形式使用外部变量
[=,&x,&y]:x,y以引用的形式,其余以传值的形式使用外部变量
[&,x,y]:x,y以传值的形式,其余以引用的形式使用外部变量
外部变量就是在Lambda
表达式函数语句之外定义的变量,以传值的形式无法修改外部变量的值,以引用的形式就可以修改。
接下来对以上几种外部变量访问方式说明符举例说明:
- [ ]:不使用任何外部变量:
在这种类型下不允许使用外部变量:
2. [=]:以传值的形式使用外部变量:
在这种类型下对外部变量只能使用无法修改:
3. [&]:以引用的形式使用外部变量:
可以看到以引用的形式使用外部变量是可以修改的
- [x,&y]:x以值的形式,y以引用的形式使用外部变量:
在这种类型下x的值无法修改,y的值可以修改:
剩下两种就很好理解了
[=,&x,&y]:x,y以引用的形式,其余以传值的形式使用外部变量(仅x,y的值可以修改,其余无法修改)
[&,x,y]:x,y以传值的形式,其余以引用的形式使用外部变量(仅x,y的值无法修改,其余可以修改)
需要注意一点,[]中的=,&其实都是包括了对象的this指针的,即对象的成员变量在[=]下可以修改,
不可修改的是局部变量:
如果这样写就无法在Lambda中使用this了: