C++11语法之右值引用的示例讲解_第1页
C++11语法之右值引用的示例讲解_第2页
C++11语法之右值引用的示例讲解_第3页
C++11语法之右值引用的示例讲解_第4页
C++11语法之右值引用的示例讲解_第5页
已阅读5页,还剩13页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第C++11语法之右值引用的示例讲解目录一、{}的扩展initializer_list的讲解:二、C++11一些小的更新decltypenullptr范围for新容器三、右值引用右值真正的用法完美转发默认成员函数总结

一、{}的扩展

在原先c++的基础上,C++11扩展了很多初始化的方法。

#includeiostream

usingnamespacestd;

structA

int_x;

int_y;

intmain()

inta[]={1,2,3,4,5};

inta1[]{1,2,3,4,5};

int*p=newint[5]{1,2,3,4,5};

Ab={1,2};//初始化

Ab2[5]{{1,1},{2,2},{3,3},{4,4},{5,5}};

A*pb=newA{1,2};

A*pb2=newA[5]{{1,1},{2,2},{3,3},{4,4},{5,5}};

return0;

结果:

全部初始化正常,vs下指针后面跟数字可以表示显示多少个。

除了上面的new[]{}我认为是比较有意义的,很好的解决了new的对象没有构造函数又需要同时创建多个对象的场景。

除了上面的,下面的这种方式底层实现不相同。

initializer_list的讲解:

vectorintv{1,2,3,4};

跳转initializer_list实现

实际上上面就是通过传参给initializer_list对象,这个对象相当于浅拷贝了外部的{1,2,3,4}的头指针和尾指针,这样vector的构造函数就可以通过迭代器遍历的方式一个个的push_back到自己的容器当中。上述过程initializer_list是很高效的,因为他只涉及浅拷贝指针和一个整形。

#includeiostream

templateclassT

classinitializer_list

public:

typedefTvalue_type;

typedefconstTreference;//注意说明该对象永远为const,不能被外部修改!

typedefconstTconst_reference;

typedefsize_tsize_type;

typedefconstT*iterator;//永远为const类型

typedefconstT*const_iterator;

private:

iterator_M_array;//用于存放用{}初始化列表中的元素

size_type_M_len;//元素的个数

//编译器可以调用private的构造函数!!!

//构造函数,在调用之前,编译会先在外部准备好一个array,同时把array的地址传入模板

//并保存在_M_array中

constexprinitializer_list(const_iterator__a,size_type__l)

:_M_array(__a),_M_len(__l){};//注意构造函数被放到private中!

constexprinitializer_list():_M_array(0),_M_len(0){}//emptylist,无参构造函数

//size()函数,用于获取元素的个数

constexprsize_typesize()constnoexcept{return_M_len;}

//获取第一个元素

constexprconst_iteratorbegin()constnoexcept{return_M_array;}

//最后一个元素的下一个位置

constexprconst_iteratorend()constnoexcept

returnbegin()+_M_len;

而{}初始化,和{}调用initializer_list组合起来是可以让初始化变得方便起来的,下面的m0用了initializer_list进行初始化,但还是比较麻烦。但m1用了{}进行单个对象初始化加initializer_list的组合之后变得方便快捷起来。

#includemap

intmain()

mapint,intm0={pairint,int(1,1),pairint,int(2,2),pairint,int(3,3)};

mapint,intm1={{1,1},{2,2},{3,3}};

return0;

小总结:

一个自定义类型调用{}初始化,本质是调用对应的构造函数;自定义类型对象可以使用{}初始化,必须要有对应的参数类型和个数;STL容器支持{}初始化,则容器必须有一个initializer_list作为参数的构造函数。

二、C++11一些小的更新

auto:

定义变量前加auto表示自动存储,表示不用管对象的销毁,但是默认定义的就是自动类型,所以这个关键字后面就不这样用了,C++11中改成了自动推导类型。

#includecstring

intmain()

inti=10;

autop=

autopf=strcmp;

couttypeid(p).name()endl;

couttypeid(pf).name()endl;

return0;

结果:

int*

int(__cdecl*)(charconst*,charconst*)

decltype

auto只能推导类型,但推导出来的类型不能用来定义对象,decltype解决了这点,推导类型后可以用来定义对象。

decltype(表达式,变量),不能放类型!

#includecstring

intmain()

inti=10;

autop=

autopf=strcmp;

decltype(p)pi;//int*

pi=

cout*piendl;//10

return0;

nullptr

NULL在C中是0,是int类型。C++11添加nullptr表示((void*)0),避免匹配错参数。

范围for

支持迭代器就支持范围for

新容器

array,没啥用,静态的数组,不支持push_back,支持方括号,里面有assert断言防止越界,保证了安全。

foward_list,没啥用,单链表,只能在节点的后面插入。

unordered_map,很有用,后面讲

unordered_set,很有用,后面讲

三、右值引用

左值

作指示一个数据表达式(变量名或解引用的指针)。

左值可以在赋值符号左右边,右值不能出现在赋值符号的左边。

const修饰符后的左值,不能给他赋值,但是可以取他的地址。左值引用就是给左值的引用,给左值取别名。

左值都是可以获取地址,基本都可以可以赋值

但const修饰的左值,只能获取地址,不能赋值。

右值?

右值也是一个数据的表达式,如字面常量,表达式返回值,传值返回的函数的返回值(不能是左值引用返回)。右值不能取地址,不能出现在赋值符号的左边。

关键看能不能取地址

给右值取别名就用右值引用,右值引用是左值了,放在赋值符号的左边了。

右值不能取地址,但是给右值引用后,引用的变量是可以取地址的,并且可以修改!

右值引用存放的地方在栈的附近。

intmain()

intrra=10;

//不想被修改constintrra

coutrraendl;

rra=100;

return0;

左值引用总结:

左值引用只能引用左值,不能引用右值。但是const修饰的左值引用既可以引用左值,又可以引用右值。在没有右值引用的时候就必须采用这种方式了。左值最重要的特征是都可以取地址,即使自定义类型也有默认的取地址重载。

右值引用总结:

右值引用只能引用右值,不能引用左值。

右值引用可以引用move以后的左值。

左值引用可以接着引用左值引用,右值引用不可以。

原因:右值引用放到左边表示他已经是一个左值了,右值引用不能引用左值!

intmain()

inta=10;

intra=a;

intrb=ra;

intrra=10;

intrrb=rra;//err:无法从“int”转换为“int"

return0;

匹配问题:

voidfunc(constinta)

cout"voidfunc(constinta)"endl;

voidfunc(inta)

cout"voidfunc(inta)"endl;

intmain()

inta=10;

func(10);

func(a);

return0;

右值在有右值引用会去匹配右值引用版本!

右值真正的用法

本质上引用都是为了减少拷贝,提高效率。而左值引用解决了大部分的场景,但是左值引用在传值返回的时候比较吃力,由右值引用来间接解决。

左值引用在引用传参可以减少拷贝构造,但是返回值的时候避免不了要调用拷贝构造。

传参用左值拷贝和右值拷贝都一样,但是返回值如果用右值引用效率会高,并且通常左值引用面临着对象出了作用域销毁的问题。所以这就是右值引用的一个比较厉害的用法。

返回对象若出了作用域不存在,则用左值引用返回和右值引用返回都是错误的。

std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝,所以可以提高利用效率,改善性能。所以当作函数返回值的时候如果对象不存在左值引用和右值引用都会报错!

场景:返回的对象在局部域中栈上存在,返回该对象必须用传值返回,并且有返回对象接受,这个时候编译器优化,将两次拷贝构造优化成一次拷贝构造。

测试用的string类

#includeassert.h

namespaceljh

classstring

public:

typedefchar*iterator;

iteratorbegin()

return_str;

iteratorend()

return_str+_size;

string(constchar*str="")

:_size(strlen(str))

,_capacity(_size)

//cout"string(char*str)"endl;

_str=newchar[_capacity+1];

strcpy(_str,str);

//s1.swap(s2)

voidswap(strings)

::swap(_str,s._str);

::swap(_size,s._size);

::swap(_capacity,s._capacity);

//拷贝构造

string(conststrings)

:_str(nullptr)

cout"string(conststrings)--深拷贝"endl;

stringtmp(s._str);

swap(tmp);

//赋值重载

stringoperator=(conststrings)

cout"stringoperator=(strings)--深拷贝"endl;

stringtmp(s);

swap(tmp);

return*this;

//移动构造

/*string(strings)

:_str(nullptr)

,_size(0)

,_capacity(0)

cout"string(strings)--移动语义"endl;

swap(s);

//移动赋值

stringoperator=(strings)

cout"stringoperator=(strings)--移动语义"endl;

swap(s);

return*this;

~string()

delete[]_str;

_str=nullptr;

charoperator[](size_tpos)

assert(pos_size);

return_str[pos];

voidreserve(size_tn)

if(n_capacity)

char*tmp=newchar[n+1];

strcpy(tmp,_str);

delete[]_str;

_str=tmp;

_capacity=n;

voidpush_back(charch)

if(_size=_capacity)

size_tnewcapacity=_capacity==04:_capacity*2;

reserve(newcapacity);

++_size;

_str[_size]='\0';

//stringoperator+=(charch)

stringoperator+=(charch)

push_back(ch);

return*this;

constchar*c_str()const

return_str;

private:

char*_str;

size_t_size;

size_t_capacity;//不包含最后做标识的\0

临时变量如果是4/8字节,通常在寄存器当中,但是如果是较大的内存,会在调用方的函数栈帧中开辟一块空间用于接受,这就是临时对象。

临时对象存在的必要性

当我们不需要接受返回值,而是对返回的对象进行直接使用,这个时候被调用的函数中的对象出了函数栈帧就销毁了,所以在栈帧销毁前会将对象拷贝到调用方栈帧的一块空间当中,我们可以用函数名对这个临时对象直接进行操作的(通常不能修改这个内存空间,临时变量具有常性)。

分析下面几组图片代码的效率

不可避免的,下面的这个过程必然要调用两次拷贝构造,编译器对于连续拷贝构造优化成不生成临时对象,由func::ss直接拷贝给main的str,我们如果只有前面所学的左值引用,func中的stringss在出了func后销毁,这个时候引用的空间被销毁会出现问题,这个时候显得特别无力。

在连续的构造+拷贝构造会被编译器进行优化,这个优化要看平台,但大部分平台都会做这个处理。

结果:

即使下面这种情况,在main接受没有引用的情况下,依旧会调用一次拷贝构造,跟上面并没有起到一个优化的作用。

结果:

解决方案:添加移动构造,添加一个右值引用版本的构造函数,构造函数内部讲s对象(将亡值)的内容直接跟要构造的对象交换,效率很高!!

string(strings):_str(nullptr),_size(0),_capacity(0){cout"string(strings)--移动语义"endl;swap(s);}

有了移动构造,对于上面的案例就变成了一次拷贝构造加一次移动构造。**编译器优化后将ss判断为将亡值,直接移动构造str对象,不产生临时对象了,就只是一次移动构造,效率升高!!**同理移动赋值!

结果:

下面情况是引用+移动构造,但是编译器优化就会判断ss出了作用域还存在,反而会拿ss拷贝构造str,这个时候起不到优化的作用!

结果:

以上采用将对象开辟在堆上或静态区都能够采用引用返回解决问题,但是有一个坏处?

引入多线程的概念,每个线程执行的函数当中若有大量的堆上的数据或者静态区的数据,相当于临界资源变多,要注意访问临界资源要加锁。而每个栈区私有栈,会相对好些

右值:

1、内置类型表达式的右值,纯右值。

2、自定义类型表达式的右值,将亡值。

将亡值:

string(strings)

:_str(nullptr)

,_size(0)

,_capacity(0)

cout"string(strings)--移动语义"endl;

swap(s);

intmain()

ljh::stringstr=func2();

vectorljh::string

v.push_back("1234656");//传进去的就是右值,是用"1234656"构造一个string对象传入,就是典型的将亡值

移动构造:

将亡值在出了生命周期就要销毁了,构造的时候可以将资源转移过要构造的对象,让将亡的对象指向NULL,相当于替它掌管资源。移动构造不能延续对象的生命周期,而是转移资源。且移动构造编译器不优化本质是一次拷贝构造+一次移动构造(从将亡值(此时返回值还是一个左值)给到临时变量),再有临时变量给到返回值接受对象(移动构造);

编译器优化做第一次优化,会将将亡值当作右值,此时要进行两次移动构造,编译器第二次优化,直接进行一次移动构造,去掉生成临时对象的环节。

只有需要深拷贝的场景,移动构造才有意义,跟拷贝构造一样,浅拷贝意义不大。

move的真正意义:

表示别人可以将这个资源进行转移走。

intmain()

//为了防止这种情况,也要添加移动赋值。

ljh::stringstr1;

str1="123456";

c++11的算法swap的效率跟容器提供的swap效率一样了。

vector提供的插入的右值引用版本,就是优化了传右值情况,如果C++98则需要拷贝放入,而有右值就可以直接移动构造。两个接口的效率差不多。

大多数容器的插入接口都做了右值引用版本!!

完美转发

模板函数或者模板类用的即万能引用。

模板中的不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力。而forward才能将这种右值特性保持下去。

但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,

此时右值在万能引用成为左值,可能会造成本身右值可以移动构造,却变成左值只能拷贝构造了。

Fun(std::forwardT(t));才能够保证转发的时候值的特性

voidFun(intx){cout"左值引用"endl;}

voidFun(constintx){cout"const左值

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论