C++11 新特性总结

类型推导

auto & decltype

  • auto:让编译器在编译器就推导出变量的类型,可以通过=右边的类型推导出变量的类型。

    auto a = 10; // 10是int型,可以自动推导出a是int

  • decltype:相对于auto用于推导变量类型,而decltype则用于推导表达式类型,这里只用于编译器分析表达式的类型,表达式实际不会进行运算。

    cont int &i = 1;
    int a = 2;
    decltype(i) b = 2; // b是const int&

简单总结:auto用于推导变量类型,而decltype则用于推导表达式类型

auto

  • auto的使用必须马上初始化,否则无法推导出类型
  • auto在一行定义多个变量时,各个变量的推导不能产生二义性,否则编译失败
  • auto不能用作函数参数
  • 在类中auto不能用作非静态成员变量
  • auto不能定义数组,可以定义指针
  • auto无法推导出模板参数

decltype

对于decltype(exp)有

  • exp是表达式,decltype(exp)和exp类型相同
  • exp是函数调用,decltype(exp)和函数返回值类型相同
  • 其它情况,若exp是左值,decltype(exp)是exp类型的左值引用

左值右值

概念1

左值:可以放到等号左边的东西叫左值。

右值:不可以放到等号左边的东西就叫右值。

概念2

左值:可以取地址并且有名字的东西就是左值。

右值:不能取地址的没有名字的东西就是右值。

int a = b + c; 

a是左值,a有变量名,也可以取地址,可以放到等号左边, 表达式b+c的返回值是右值,没有名字且不能取地址,&(b+c)不能通过编译,而且也不能放到等号左边。

int a = 4; // a是左值,4作为普通字面量是右值

左值一般有:

  • 函数名和变量名
  • 返回左值引用的函数调用
  • 前置自增自减表达式++i、--i
  • 由赋值表达式或赋值运算符连接的表达式(a=b, a += b等)
  • 解引用表达式*p

纯右值

运算表达式产生的临时变量、不和对象关联的原始字面量、非引用返回的临时变量、lambda表达式等都是纯右值。

  • 除字符串字面值外的字面值
  • 返回非引用类型的函数调用
  • 后置自增自减表达式i++、i--
  • 算术表达式(a+b, a*b, a&&b, a==b等)
  • 取地址表达式等(&a)

将亡值

将亡值是指C++11新增的和右值引用相关的表达式,通常指将要被移动的对象、T&&函数的返回值、std::move函数的返回值、转换为T&&类型转换函数的返回值,将亡值可以理解为即将要销毁的值,通过"盗取"其它变量内存空间方式获取的值,在确保其它变量不再被使用或者即将被销毁时,可以避免内存空间的释放和分配,延长变量值的生命周期,常用来完成移动构造或者移动赋值的特殊任务。

左值引用、右值引用

左值引用就是对左值进行引用的类型,右值引用就是对右值进行引用的类型,他们都是引用,都是对象的一个别名,并不拥有所绑定对象的堆存,所以都必须立即初始化。

type &name = exp; // 左值引用
type &&name = exp; // 右值引用

深拷贝、浅拷贝

浅拷贝

  • 对于基本数据类型的成员变量,浅拷贝直接进行值传递,也就是将属性值复制了一份给新的成员变量
  • 对于引用数据类型的成员变量,比如成员变量是数组、某个类的对象等,浅拷贝就是引用的传递,也就是将成员变量的引用(内存地址)复制了一份给新的成员变量,他们指向的是同一个事例。在一个对象修改成员变量的值,会影响到另一个对象中成员变量的值。

深拷贝

  • 对于基本数据类型,深拷贝复制所有基本数据类型的成员变量的值
  • 对于引用数据类型的成员变量,深拷贝申请新的存储空间,并复制该引用对象所引用的对象,也就是将整个对象复制下来。所以在一个对象修改成员变量的值,不会影响到另一个对象成员变量的值。
    注意:

a和b的data_指针指向了同一块内存,这就是浅拷贝,只是数据的简单赋值,那再析构时data_内存会被释放两次,导致程序出问题。

移动语义

拷贝是对于别人的资源,自己重新分配一块内存存储复制过来的资源,而对于移动语义,类似于转让或者资源窃取的意思,对于那块资源,转为自己所拥有,别人不再拥有也不会再使用

注意:移动语义仅针对于那些实现了移动构造函数的类的对象,对于那种基本类型int、float等没有任何优化作用,还是会拷贝,因为它们实现没有对应的移动构造函数。

通过std::move()实现,移动语义可以避免很多无用的拷贝,提供程序性能,C++所有的STL都实现了移动语义

完美转发

完美转发指可以写一个接受任意实参的函数模板,并转发到其它函数,目标函数会收到与转发函数完全相同的实参,转发函数实参是左值那目标函数实参也是左值,转发函数实参是右值那目标函数实参也是右值。

通过std::forward()实现。

列表初始化

直接在变量名后面加上初始化列表来进行对象的初始化。

使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的

以下几种情况时必须使用初始化列表

1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面

2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面

  1. 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化

lambda表达式

定义了一个匿名函数,可以捕获一定范围的变量在函数内部使用

auto func = [capture] (params) opt -> ret { func_body; };

其中func是可以当作lambda表达式的名字,作为一个函数使用,capture是捕获列表,params是参数表,opt是函数选项(mutable之类), ret是返回值类型,func_body是函数体。

一个完整的lambda表达式:

auto func1 = [](int a) -> int { return a + 1; };
auto func2 = [](int a) { return a + 2; };
cout << func1(1) << " " << func2(2) << endl;

如上代码,很多时候lambda表达式返回值是很明显的,c++11允许省略表达式的返回值定义。

lambda表达式允许捕获一定范围内的变量:

  • []不捕获任何变量
  • [&]引用捕获,捕获外部作用域所有变量,在函数体内当作引用使用
  • [=]值捕获,捕获外部作用域所有变量,在函数内内有个副本使用
  • [=, &a]值捕获外部作用域所有变量,按引用捕获a变量
  • [a]只值捕获a变量,不捕获其它变量
  • [this]捕获当前类中的this指针

lambda表达式示例代码:

int a = 0;
auto f1 = [=](){ return a; }; // 值捕获a
cout << f1() << endl;

auto f2 = [=]() { return a++; }; // 修改按值捕获的外部变量,error
auto f3 = [=]() mutable { return a++; };

代码中的f2是编译不过的,因为我们修改了按值捕获的外部变量,其实lambda表达式就相当于是一个仿函数,仿函数是一个有operator()成员函数的类对象,这个operator()默认是const的,所以不能修改成员变量,而加了mutable,就是去掉const属性。

智能指针

c++11引入了三种智能指针:

  • std::shared_ptr
  • std::weak_ptr
  • std::unique_ptr

shared_ptr

shared_ptr使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。

  • 不要用一个裸指针初始化多个shared_ptr,会出现double_free导致程序崩溃

  • 通过shared_from_this()返回this指针,不要把this指针作为shared_ptr返回出来,因为this指针本质就是裸指针,通过this返回可能 会导致重复析构,不能把this指针交给智能指针管理。

  • 尽量使用make_shared,少用new。

  • 不要delete get()返回来的裸指针。

  • 不是new出来的空间要自定义删除器。

  • 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。

weak_ptr

weak_ptr是用来监视shared_ptr的生命周期,它不管理shared_ptr内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。

  • 作用1:返回this指针,上面介绍的shared_from_this()其实就是通过weak_ptr返回的this指针

  • 作用2:解决循环引用问题。

unique_ptr

std::unique_ptr是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr的拷贝和赋值。使用方法和shared_ptr类似,区别是不可以拷贝:

nullptr

nullptr并非整型类别,甚至也不是指针类型,但是能转换成任意指针类型。nullptr的实际类型是std:nullptr_t。

NULL本质上是个int型的0,其实不是个指针

final & override

final用于修饰一个类,表示禁止该类进一步派生和虚函数的进一步重载,override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,编译报错,使用override关键字可以避免开发者在重写基类函数时无意产生的错误。

default

多数时候用于声明构造函数为默认构造函数,如果类中有了自定义的构造函数,编译器就不会隐式生成默认构造函数

delete

std::unique_ptr就是通过delete修饰来禁止对象的拷贝的

explicit

explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换

const

const字面意思为只读,可用于定义变量,表示变量是只读的,不可以更改

相关推荐
小技与小术3 分钟前
数据结构之树与二叉树
开发语言·数据结构·python
Beau_Will3 分钟前
数据结构-树状数组专题(1)
数据结构·c++·算法
hccee24 分钟前
C# IO文件操作
开发语言·c#
hummhumm29 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
hunandede35 分钟前
av_image_get_buffer_size 和 av_image_fill_arrays
c++
J老熊39 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
zmd-zk1 小时前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink
好奇的菜鸟1 小时前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript