C++11语言扩展常规特性.docx

上传人:b****5 文档编号:11622950 上传时间:2023-03-29 格式:DOCX 页数:20 大小:30.10KB
下载 相关 举报
C++11语言扩展常规特性.docx_第1页
第1页 / 共20页
C++11语言扩展常规特性.docx_第2页
第2页 / 共20页
C++11语言扩展常规特性.docx_第3页
第3页 / 共20页
C++11语言扩展常规特性.docx_第4页
第4页 / 共20页
C++11语言扩展常规特性.docx_第5页
第5页 / 共20页
点击查看更多>>
下载资源
资源描述

C++11语言扩展常规特性.docx

《C++11语言扩展常规特性.docx》由会员分享,可在线阅读,更多相关《C++11语言扩展常规特性.docx(20页珍藏版)》请在冰豆网上搜索。

C++11语言扩展常规特性.docx

C++11语言扩展常规特性

C++11语言扩展:

常规特性

本节内容:

auto、decltype、基于范围的for语句、初始化列表、统一初始化语法和语义、右值引用和移动语义、Lambdas、noexcept防止抛出异常、constexpr、nullptr——一个指针空值常量、复制并再抛出异常、内联命名空间、用户自定义数据标识。

auto

推导

1

autox=7;

在这里因为它的初始化类型我们将得到x的int类型。

一般来说,我们可以写

1

autox=expression;

x的类型我们将会根据初始化表达式“expression”的类型来自动推导。

当一个变量的类型很难准确的知道或者写出的时候,用atuo通过初始化表达式的类型进行推导显然是非常有用的。

参考:

1

2

3

4

5

templatevoidprintall(constvector&v)

{

    for(autop=v.begin();p!

=v.end();++p)

        cout<<*p<<"\n";

}

在C++98里我们必须这样写

1

2

3

4

5

templatevoidprintall(constvector&v)

   {

       for(typenamevector:

:

const_iteratorp=v.begin();p!

=v.end();++p)

           cout<<*p<<"\n";

   }

当一个变量的类型取决于模板实参的时候不用auto真的很难定义,例如:

1

2

3

4

5

6

templatevoidmultiply(constvector&vt,constvector&vu)

    {

        //...

        autotmp=vt[i]*vu[i];

        //...

    }

 

从T*U的表达式,我们人类的思维是很难理清tmp的类型的,但是编译器肯定知道T和U经过了什么特殊处理。

auto的特性在最早提出和应用时是有区别的:

1984年初,Stroustrup在他的Cfront实现里用到了它,但是是由于C的兼容问题被迫拿来用的,这些兼容的问题已经在C++98和C++99接受了隐性int时候消失了。

也就是说,现在这两种语言要求每一个变量和函数都要有一个明确的类型定义。

auto旧的含义(即这是一个局部变量)现在是违法的。

标准委员会的成员们在数百万行代码中仅仅只找到几百个用到auto关键字的地方,并且大多数出现在测试代码中,有的甚至就是一个bug。

auto在代码中主要是作为简化工具,并不会影响标准库规范。

参考:

∙theC++draftsection7.1.6.2,7.1.6.4,8.3.5(forreturntypes)

∙[N1984=06-0054]JaakkoJarvi,BjarneStroustrup,andGabrielDosReis:

 

∙HerbSutter:

 

∙HerbSutter:

 

∙HerbSutter:

 

decltype

decltype(E)的类型(“声明类型”)可用名称或表达E来声明。

例如:

1

2

3

4

5

6

7

8

9

voidf(constvector&a,vector&b)

  {

      typedefdecltype(a[0]*b[0])Tmp;

      for(inti=0;i

          Tmp*p=newTmp(a[i]*b[i]);

          //...

      }

      //...

  }

这个概念已经流行在泛型编程的标签“typeof”很长一段时间,但实际使用的“领域”的实现是不完整和不兼容,所以标准版命名了decltype。

注意:

喜欢使用auto时,你只是需要一个变量的类型初始化。

你真的需要decltype如果你需要一个类型的东西不是一个变量,例如返回类型。

参考:

∙theC++draft7.1.6.2Simpletypespecifiers

∙[Str02]BjarneStroustrup.Draftproposalfor“typeof”.C++reflectormessagec++std-ext-5364,October2002.(originalsuggestion).

∙[N1478=03-0061]JaakkoJarvi,BjarneStroustrup,DouglasGregor,andJeremySiek:

 (originalproposal).

∙[N2343=07-0203]JaakkoJarvi,BjarneStroustrup,andGabrielDosReis:

 

基于范围的for循环

声明的范围像是STL-sequence定义的begin()和end(),允许你在这个范围内循环迭代。

所有标准容器可以作为一个范围来使用,比如可以是std:

:

string,初始化器列表,一个数组,和任何你可以定义begin()和end()的,比如istream。

例如:

1

2

3

4

5

voidf(vector&v)

{

    for(autox:

v)cout<

    for(auto&x:

v)++x; //usingareferencetoallowustochangethevalue

}

你可以看到,V中所有的元素都从begin()开始迭代循环到了end()。

另一个例子:

1

for(constautox:

{1,2,3,5,8,13,21,34})cout<

begin()(和end())可以被做为x.begin()的成员或一个独立的函数被称为开始(x)。

成员版本优先。

参考:

∙theC++draftsection6.5.4(note:

changednottouseconcepts)

∙[N2243==07-0103]ThorstenOttosen:

 

∙[N3257=11-0027]JonathanWakelyandBjarneStroustrup:

  (Option5waschosen).

初始化列表

推导

1

2

3

4

5

6

7

8

9

vectorv={1,2,3.456,99.99};

   list>languages={

       {"Nygaard","Simula"},{"Richards","BCPL"},{"Ritchie","C"}

   };

   map,vector>years={

       {{"Maurice","Vincent","Wilkes"},{1913,1945,1951,1967,2000}},

       {{"Martin","Ritchards"},{1982,2003,2007}},

       {{"David","John","Wheeler"},{1927,1947,1951,2004}}

   };

初始化列表不再只针对于数组了。

定义一个接受{}初始化列表的函数(通常是初始化函数)接受一个std:

:

initializer_list的参数类型,例如:

1

2

3

4

5

6

voidf(initializer_list);

f({1,2});

f({23,345,4567,56789});

f({}); //theemptylist

f{1,2};//error:

functioncall()missing

years.insert({{"Bjarne","Stroustrup"},{1950,1975,1985}});

初始化器列表可以是任意长度的,但必须同种类型的(所有元素必须的模板参数类型,T,或可转换T)。

一个容器可能实现一个初始化列表构造函数如下:

1

2

3

4

5

6

7

8

9

10

templateclassvector{

  public:

      vector(std:

:

initializer_lists)//initializer-listconstructor

      {

              reserve(s.size()); //gettherightamountofspace

              uninitialized_copy(s.begin(),s.end(),elem);  //initializeelements(inelem[0:

s.size()))

          sz=s.size(); //setvectorsize

      }

      //...asbefore...

  };

直接初始化和复制初始化的区别是对初始化列表的维护,但是因为初始化列表的相关联的频率就降低了。

例如std:

:

vector有一个int类型显示构造函数和initializer_list构造函数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

vectorv1(7);  //ok:

v1has7elements

   v1=9;                //error:

noconversionfrominttovector

   vectorv2=9; //error:

noconversionfrominttovector

   voidf(constvector&);

   f(9);                          //error:

noconversionfrominttovector

   vectorv1{7};          //ok:

v1has1element(withitsvalue7.0)

   v1={9};                      //okv1nowhas1element(withitsvalue9.0)

   vectorv2={9};       //ok:

v2has1element(withitsvalue9.0)

   f({9});                        //ok:

fiscalledwiththelist{9}

   vector>vs={

       vector(10),        //ok:

explicitconstruction(10elements)

       vector{10},        //okexplicitconstruction(1elementwiththevalue10.0)

       10                         //error:

vector'sconstructorisexplicit

   };

函数可以作为一个不可变的序列访问initializer_list。

例如:

1

2

3

4

voidf(initializer_listargs)

    {

        for(autop=args.begin();p!

=args.end();++p)cout<<*p<<"\n";

    }

仅具有一个std:

:

initializer_list的单参数构造函数被称为初始化列表构造函数。

标准库容器,string类型及正则表达式均具有初始化列表构造函数,以及(初始化列表)赋值函数等。

一个初始化列表可被用作Range,例如,表达式Range。

初始化列表是一致泛化初始化解决方案的一部分。

他们还防止类型收窄。

一般来说,你应该通常更喜欢使用{}来代替()初始化,除非你想用c++98编译器来分享代码或(很少)需要使用()调用没initializer_list重载构造函数。

参考:

∙theC++draft8.5.4List-initialization[dcl.init.list]

∙[N1890=05-0150]BjarneStroustrupandGabrielDosReis:

 (anoverviewofinitialization-relatedproblemswithsuggestedsolutions).

∙[N1919=05-0179]BjarneStroustrupandGabrielDosReis:

 

∙[N2215=07-0075]BjarneStroustrupandGabrielDosReis:

 

∙[N2640=08-0150]JasonMerrillandDaveedVandevoorde:

  (finalproposal).

统一初始化语法和语义

c++98提供了几种方法初始化一个对象根据其类型和初始环境。

滥用时,会产生可以令人惊讶的错误和模糊的错误消息。

推导:

1

2

3

4

stringa[]={"foo","bar"};         //ok:

initializearrayvariable

  vectorv={"foo","bar"};   //error:

initializerlistfornon-aggregatevector

  voidf(stringa[]);

  f({"foo","bar"});                 //syntaxerror:

blockasargument

1

2

3

4

inta=2;             //"assignmentstyle"

intaa[]={2,3};   //assignmentstylewithlist

complexz(1,2);        //"functionalstyle"initialization

x=Ptr(y);            //"functionalstyle"forconversion/cast/construction

1

2

3

inta

(1);  //variabledefinition

  intb();   //functiondeclaration

  intb(foo);//variabledefinitionorfunctiondeclaration

要记得初始化的规则并选择最好的方法去初始化是比较难的。

C++11的解决方法是允许所有的初始化使用初始化列表

1

2

3

4

5

6

7

8

9

10

11

Xx1=X{1,2};

 Xx2={1,2};  //the=isoptional

 Xx3{1,2};

 X*p=newX{1,2};

 structD:

X{

     D(intx,inty):

X{x,y}{/*...*/};

 };

 structS{

     inta[3];

     S(intx,inty,intz):

a{x,y,z}{/*...*/};//solutiontooldproblem

 };

重点是,x{a}在在执行代码中都创建了一个相同的值,所以在使用“{}”进行初始化合法的情况下都产生了相同的结果。

例如:

1

2

3

4

5

Xx{a};

 X*p=newX{a};

 z=X{a};        //useascast

 f({a});          //functionargument(oftypeX)

 return{a};      //functionreturnvalue(functionreturningX)

参考:

∙theC++draftsection?

?

?

∙[N2215==07-0075]BjarneStroustrupandGabrielDosReis:

 

∙[N2640==08-0150]JasonMerrillandDaveedVandevoorde:

 (finalproposal).

右值引用和移动语义

左值(用在复制操作符左边)和右值(用在复制操作符右边)的区别可以追溯到ChristopherStrachey (C++遥远的祖先语言CPL和外延语义之父)的时代。

在C++中,非const引用可以绑定到左值,const引用既可以绑定到左值也可以绑定要右值。

但是右值却不可以被非const绑定。

这是为了防止人们改变那些被赋予新值之前就被销毁的临时变量。

例如:

1

2

3

4

voidincr(int&a){++a;}

    inti=0;

    incr(i);   //ibecomes1

    incr(0);   //error:

0innotanlvalue

如果incr(0)被允许,那么就会产生一个无法被人看到的临时变量被执行增加操作,或者更糟的0会变成1.后者听起来很傻,但实际上确实存在这样一个bug在Fortran编译器中:

为值为0的内存位置分配。

到目前为止还好,但考虑以下代码:

1

2

3

4

5

6

templateswap(T&a,T&b)     //"oldstyleswap"

   {

       Ttmp(a);  //nowwehavetwocopiesofa

       a=b;     //nowwehavetwocopiesofb

       b=tmp;   //nowwehavetwocopiesoftmp(akaa)

   }

如果T是一个复制元素要付出昂贵代价的类型,比如string和vector,swap将会变成一个十分昂贵的操作(对于标准库来说,我们有专门化的string和vector来处理)。

注意一下这些奇怪的现象:

我们并不想任何变量拷贝。

我们仅仅是想移动变量a,b和tmp的值。

在C++11中,我们可以定义“移动构造函数”和“移动赋值操作符”来移动,而不是复制他们的参数:

1

2

3

4

5

6

7

8

templateclassvector{

    //...

    vector(constvector&);         //copyconstructor

    vector(vector&&);          //moveconstructor

    vector&operator=(constvector&);  //copyassignment

    vector&operator=(vector&&);       //moveassignment

}; //note:

moveconstructorandmoveassignmenttakesnon-const&&

    //theycan,andusuallydo,writetotheirargument

&&表明“右值引用”。

一个右值引用可以绑定到一个右值(而不是一个左值):

1

2

3

4

5

6

Xa;

  Xf();

  X&r1=a;     //bindr1toa(anlvalue)

  X&r2=f();       //error:

f()isanrvalue;can'tbind

  X&&rr1=f(); //fine:

bindrr1totemporary

  X&&rr2=a;   //error:

bindaisanlvalue

赋值这个操作的背后思想,并不是拷贝,它只是构造一个源对象的代表然后再替换。

例如,strings1=s2的移动,它不是产生s2的拷贝,而是让s1把s2中字符变为自己的同时删除自己原有的字符串(也可以放在s2中,但是它也面临着被销毁)

我们如何知道是否可以简单的从源对象进行移动?

我们可以告诉编译器:

1

2

3

4

5

6

7

template

voidswap(T&a,T&b)  //"perfectswap"(almost)

{

    Ttmp=move(a);   //couldinvalidatea

    a=move(b);       //couldinvalidateb

    b=move(tmp);     //couldinvalidatetmp

}

move(x)只是意味着你“你可以把x当作一个右值”,

如果把move()称做eval()也许会更好,但是现在move()已经用了好多年了。

在c++11中,move()模板(参考简介)和右值引用都可以使用。

右值引用也可以用来提供完美的转发。

在C++0x的标准库中,所有的容器都提供了移动构造函数和移动赋值操作符,那些插入新元素的操作,如insert()和push_back(),也都有了可以接受右值引用的版本。

最终结果是,在无用户干预时,标准容器和算法的性能都提升了,因为复制操作的减少。

参考:

∙N1385

展开阅读全文
相关搜索

当前位置:首页 > 经管营销

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1