C++11语言扩展:常规特性_第1页
C++11语言扩展:常规特性_第2页
C++11语言扩展:常规特性_第3页
C++11语言扩展:常规特性_第4页
C++11语言扩展:常规特性_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

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

文档简介

C++11语言扩展:常规特性本节内容:auto、decltype、基于范围的for语句、初始化列表、统一初始化语法和语义、右值引用和移动语义、Lambdas、noexcept防止抛出异常、constexpr、nullptr——一个指针空值常量、复制并再抛出异常、内联命名空间、用户自定义数据标识。auto推导1autox=7;在这里因为它的初始化类型我们将得到x的int类型。一般来说,我们可以写1autox=expression;x的类型我们将会根据初始化表达式“expression”的类型来自动推导。当一个变量的类型很难准确的知道或者写出的时候,用atuo通过初始化表达式的类型进行推导显然是非常有用的。参考:12345template<classT>voidprintall(constvector<T>&v){

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

cout<<*p<<"\n";}在C++98里我们必须这样写12345template<classT>voidprintall(constvector<T>&v)

{

for(typenamevector<T>::const_iteratorp=v.begin();p!=v.end();++p)

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

}当一个变量的类型取决于模板实参的时候不用auto真的很难定义,例如:123456template<classT,classU>voidmultiply(constvector<T>&vt,constvector<U>&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++draftsection,,8.3.5(forreturntypes)[N1984=06-0054]JaakkoJarvi,BjarneStroustrup,andGabrielDosReis:

HerbSutter:

HerbSutter:

HerbSutter:

decltypedecltype(E)的类型(“声明类型”)可用名称或表达E来声明。例如:123456789voidf(constvector<int>&a,vector<float>&b)

{

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

for(inti=0;i<b.size();++i){

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

//...

}

//...

}这个概念已经流行在泛型编程的标签“typeof”很长一段时间,但实际使用的“领域”的实现是不完整和不兼容,所以标准版命名了decltype。注意:喜欢使用auto时,你只是需要一个变量的类型初始化。你真的需要decltype如果你需要一个类型的东西不是一个变量,例如返回类型。参考:theC++draftSimpletypespecifiers[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。例如:12345voidf(vector<double>&v){

for(autox:v)cout<<x<<'\n';

for(auto&x:v)++x;

//usingareferencetoallowustochangethevalue}你可以看到,V中所有的元素都从begin()开始迭代循环到了end()。另一个例子:1for(constautox:{1,2,3,5,8,13,21,34})cout<<x<<'\n';begin()(和end())可以被做为x.begin()的成员或一个独立的函数被称为开始(x)。成员版本优先。参考:theC++draftsection6.5.4(note:changednottouseconcepts)[N2243==07-0103]ThorstenOttosen:

[N3257=11-0027]JonathanWakelyandBjarneStroustrup:

(Option5waschosen).初始化列表推导123456789vector<double>v={1,2,3.456,99.99};

list<pair<string,string>>languages={

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

};

map<vector<string>,vector<int>>years={

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

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

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

};初始化列表不再只针对于数组了。定义一个接受{}初始化列表的函数(通常是初始化函数)接受一个std::initializer_list<T>的参数类型,例如:123456voidf(initializer_list<int>);f({1,2});f({23,345,4567,56789});f({});

//theemptylistf{1,2};//error:functioncall()missingyears.insert({{"Bjarne","Stroustrup"},{1950,1975,1985}});初始化器列表可以是任意长度的,但必须同种类型的(所有元素必须的模板参数类型,T,或可转换T)。一个容器可能实现一个初始化列表构造函数如下:12345678910template<classE>classvector{

public:

vector(std::initializer_list<E>s)//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构造函数:1234567891011121314vector<double>v1(7);

//ok:v1has7elements

v1=9;

//error:noconversionfrominttovector

vector<double>v2=9;

//error:noconversionfrominttovector

voidf(constvector<double>&);

f(9);

//error:noconversionfrominttovector

vector<double>v1{7};

//ok:v1has1element(withitsvalue7.0)

v1={9};

//okv1nowhas1element(withitsvalue9.0)

vector<double>v2={9};

//ok:v2has1element(withitsvalue9.0)

f({9});

//ok:fiscalledwiththelist{9}

vector<vector<double>>vs={

vector<double>(10),

//ok:explicitconstruction(10elements)

vector<double>{10},

//okexplicitconstruction(1elementwiththevalue10.0)

10

//error:vector'sconstructorisexplicit

};函数可以作为一个不可变的序列访问initializer_list。例如:1234voidf(initializer_list<int>args)

{

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提供了几种方法初始化一个对象根据其类型和初始环境。滥用时,会产生可以令人惊讶的错误和模糊的错误消息。推导:1234stringa[]={"foo","bar"};

//ok:initializearrayvariable

vector<string>v={"foo","bar"};

//error:initializerlistfornon-aggregatevector

voidf(stringa[]);

f({"foo","bar"});

//syntaxerror:blockasargument和1234inta=2;

//"assignmentstyle"intaa[]={2,3};

//assignmentstylewithlistcomplexz(1,2);

//"functionalstyle"initializationx=Ptr(y);

//"functionalstyle"forconversion/cast/construction和123inta(1);

//variabledefinition

intb();

//functiondeclaration

intb(foo);//variabledefinitionorfunctiondeclaration要记得初始化的规则并选择最好的方法去初始化是比较难的。C++11的解决方法是允许所有的初始化使用初始化列表1234567891011Xx1=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}在在执行代码中都创建了一个相同的值,所以在使用“{}”进行初始化合法的情况下都产生了相同的结果。例如:12345Xx{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绑定。这是为了防止人们改变那些被赋予新值之前就被销毁的临时变量。例如:1234voidincr(int&a){++a;}

inti=0;

incr(i);

//ibecomes1

incr(0);

//error:0innotanlvalue如果incr(0)被允许,那么就会产生一个无法被人看到的临时变量被执行增加操作,或者更糟的0会变成1.后者听起来很傻,但实际上确实存在这样一个bug在Fortran编译器中:为值为0的内存位置分配。到目前为止还好,但考虑以下代码:123456template<classT>swap(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中,我们可以定义“移动构造函数”和“移动赋值操作符”来移动,而不是复制他们的参数:12345678template<classT>classvector{

//...

vector(constvector&);

//copyconstructor

vector(vector&&);

//moveconstructor

vector&operator=(constvector&);

//copyassignment

vector&operator=(vector&&);

//moveassignment};

//note:moveconstructorandmoveassignmenttakesnon-const&&

//theycan,andusuallydo,writetotheirargument&&表明“右值引用”。一个右值引用可以绑定到一个右值(而不是一个左值):123456Xa;

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中,但是它也面临着被销毁)我们如何知道是否可以简单的从源对象进行移动?我们可以告诉编译器:1234567template<classT>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(),也都有了可以接受右值引用的版本。最终结果是,在无用户干预时,标准容器和算法的性能都提升了,因为复制操作的减少。参考:N1385N1690N1770N1855N1952[N2027==06-0097]HowardHinnant,BjarneStroustrup,andBronekKozicki:[N1377=02-0035]HowardE.Hinnant,PeterDimov,andDaveAbrahams:

[N2118=06-0188]HowardHinnant:

(finalproposal).lambdasLambda表达式是一种描述函数对象的机制,它的主要应用是描述某些具有简单行为的函数(译注:Lambda表达式也可以称为匿名函数,具有复杂行为的函数可以采用命名函数对象,当然,简单和复杂之间的划分依赖于编程人员的选择)。例如:123456vector<int>v={50,-10,20,-30};std::sort(v.begin(),v.end());

//thedefaultsort//nowvshouldbe{-30,-10,20,50}//sortbyabsolutevalue:std::sort(v.begin(),v.end(),[](inta,intb){returnabs(a)<abs(b);});//nowvshouldbe{-10,20,-30,50}参数[&](inta,intb){returnabs(a)<abs(b);}是一个"lambda"(又称为"lambda函数"或者"lambda表达式"),它描述了这样一个函数操作:接受两个整形参数a和b,然后返回对它们的绝对值进行"<"比较的结果。(译注:为了保持与代码的一致性,此处应当为"[](inta,intb){returnabs(a)<abs(b);}",而且在这个lambda表达式内实际上未用到局部变量,所以[&]是无必要的)一个Lambda表达式可以存取在它被调用的作用域内的局部变量。例如:123456789voidf(vector<Record>&v)

{

vector<int>indices(v.size());

intcount=0;

generate(indices.begin(),indices.end(),[&count](){returncount++;});

//sortindicesintheorderdeterminedbythenamefieldoftherecords:

std::sort(indices.begin(),indices.end(),[&](inta,intb){returnv[a].name<v[b].name;});

//...

}有人认为这“相当简洁”,也有人认为这是一种可能产生危险且晦涩的代码的方式。我的看法是,两者都正确。[&]是一个“捕捉列表(capturelist)”,用于描述将要被lambda函数以引用传参方式使用的局部变量。如果我们仅想“捕捉"参数v,则可以写为:[&v]。而如果我们想以传值方式使用参数v,则可以写为:[=v]。如果什么都不捕捉,则为:[]。将所有的变量以引用传递方式使用时采用[&],[=]则相应地表示以传值方式使用所有变量(译注:“所有变量”即指lambda表达式在被调用处,所能见到的所有局部变量)。如果某一函数的行为既不通用也不简单,那么我建议采用命名函数对象或者函数。例如,如上示例可重写为:1234567891011121314voidf(vector<Record>&v)

{

vector<int>indices(v.size());

intcount=0;

generate(indices.begin(),indices.end(),[&](){return++count;});

structCmp_names{

constvector<Record>&vr;

Cmp_names(constvector<Record>&r):vr(r){}

booloperator()(inta,intb)const{returnvr[a].name<vr[b].name;}

};

//sortindicesintheorderdeterminedbythenamefieldoftherecords:

std::sort(indices.begin(),indices.end(),Cmp_names(v));

//...

}对于简单的函数功能,比如记录名称域的比较,采用函数对象就略显冗长,尽管它与lambda表达式生成的代码是一致的。在C++98中,这样的函数对象在被用作模板参数时必须是非本地的(译注:即你不能在函数对象中像此处的lambda表达式那样使用被调用处的局部变量),然而在C++中(译注:意指C++0x),这不再是必须的。为了描述一个lambda,你必须提供:它的捕捉列表:它可以使用的变量列表(除了形参之外),如果存在的话("[&]"在上面的记录比较例子中意味着“所有的局部变量都将按照引用的方式进行传递”)。如果不需要捕捉任何变量,则使用[]。(可选的)它的所有参数及其类型(例如:(inta,intb))。组织成一个块的函数行为(例如:{returnv[a].name<v[b].name;})。(可选的)采用了新的后缀返回类型符号的返回类型。但典型情况下,我们仅从return语句中去推断返回类型,如果没有返回任何值,则推断为void。参考:Standard5.1.2Lambdaexpressions[N1968=06-0038]JeremiahWillcock,JaakkoJarvi,DougGregor,BjarneStroustrup,andAndrewLumsdaine:

(originalproposalwithadifferentsyntax)[N2550=08-0060]JaakkoJarvi,JohnFreeman,andLawrenceCrowl:

(finalproposal).[N2859=09-0049]DaveedVandevoorde:

noexcept防止抛出异常如果一个函数不能抛出异常或者一个程序没有对函数抛出的异常进行处理,那么这个函数可以用关键字noexcept进行修饰,例如:1234567extern"C"doublesqrt(double)noexcept;

//willneverthrow

vector<double>my_computation(constvector<double>&v)noexcept//I'mnotpreparedtohandlememoryexhaustion

{

vector<double>res(v.size());

//mightthrow

for(inti;i<v.size();++i)res[i]=sqrt(v[i]);

returnres;

}如果一个被noexcept修饰的函数抛出了异常(所以异常会跳出呗noexcept修饰的函数),程序会调用std::terminate()这个函数来终止程序。在对象被明确定义的状态下不能调用terminate();比如无法保证析构函数正常调用,不能保证栈的自动释放,也无法保证在遇到任何问题时重新启动。故意这样的使noexcept成为一种简单“粗暴”而有效的处理机制-它比旧的处理机制throw()动态抛出异常要有效的多。它可以让一个函数根据条件来实现noexcept修饰。比如,一个算法可以根据他的模板参数来决定自己是否抛出异常。123456template<classT>

voiddo_f(vector<T>&v)noexcept(noexcept(f(v.at(0))))//canthrowiff(v.at(0))can

{

for(inti;i<v.size();++i)

v.at(i)=f(v.at(i));

}这里,第一个noexcept被用作操作符operator:如果iff(v.at(0))不能够抛出异常,noexcept(f(v.at(0)))则返回true,所以f()和at()是无法抛出异常noexcept。noexcept()操作符是一个常量表达式,并且不计算表达式的值。声明的通常形式是noexcept(expression),并且单独的一个“noexcept”关键字实际上就是的一个noexcept(true)的简化。一个函数的所有声明都必须与noexcept声明保持兼容。一个析构函数不应该抛出异常;通常,如果一个类的所有成员都拥有noexcept修饰的析构函数,那么这个类的析构函数就自动地隐式地noexcept声明,而与函数体内的代码没有关系。通常,将某个抛出的异常进行移动操作是一个很坏的主意,所以,在任何可能的地方都用noexcept进行声明。如果某个类的所有成员都有使用noexcept声明的析构函数,那么这个类默认生成的复制或者移动操作(类的复制构造函数,移动构造函数等)都是隐式的noexcept声明。(?)noexcept被广泛地系统地应用在C++11的标准库中,以此来提供标准库的性能和满足标准库对于简洁性的需求。参考:Standard:15.4Exceptionspecifications[except.spec].Standard:5.3.7noexceptoperator[expr.unary.noexcept].[N3103==10-0093]D.Kohlbrenner,D.Svoboda,andA.Wesie:Securityimpactofnoexcept.(Noexcept

must

terminate,asitdoes).[N3167==10-0157]DavidSvoboda:

[N3204==10-0194]JensMaurer:

[N3050==10-0040]D.Abrahams,R.Sharoni,andD.Gregor:

constexpr常量表达式机制:提供了更多的通用的值不发生变化的表达式允许用户自定义的类型成为常量表达式提供了一种保证在编译期完成初始化的方法考虑下面这段代码:1234567891011enumFlags{good=0,fail=1,bad=2,eof=4};constexprintoperator|(Flagsf1,Flagsf2){returnFlags(int(f1)|int(f2));}voidf(Flagsx){

switch(x){

casebad:

/*...*/break;

caseeof:

/*...*/break;

casebad|eof:

/*...*/break;

default:

/*...*/break;

}}在这里,常量表达式关键字constexpr表示这个重载的操作符“|”就应该像一个简单的表单一样,如果它的参数本身就是常量,那么这个操作符应该在编译时期就应该计算出它的结果来。

除了可以在编译时期被动地计算表达式的值之外,我们希望能够主动地要求表达式在编译时期计算其结果值,从而用作其它用途,比如对某个变量进行赋值。当我们在变量声明前加上constexpr关键字之后,可以实现这一功能,当然,它也同时会让这个变量成为常量。123456constexprintx1=bad|eof;//ok

voidf(Flagsf3)

{

constexprintx2=bad|f3;

//error:can'tevaluateatcompiletime

intx3=bad|f3;

//ok

}通常,我们希望编译时期计算可以保护全局或者名字空间内的对象,对名字空间内的对象,我们希望它保存在只读空间内。

对于那些构造函数比较简单,可以成为常量表达式(也就是可以使用constexpr进行修饰)的对象可以做到这一点(?)12345678structPoint{

intx,y;

constexprPoint(intxx,intyy):x(xx),y(yy){}

};

constexprPointorigo(0,0);

constexprintz=origo.x;

constexprPointa[]={Point(0,0),Point(1,1),Point(2,2)};

constexprintx=a[1].x;

//xbecomes1

const的主要功能是修饰一个对象而不是通过一个接口(即使对象很容易通过其他接口修改)。只不过声明一个对象常量为编译器提供了优化的机会。特别是,如果一个声明了一个对象常量而他的地址没有取到,编译器通常可以在编译时对他进行初始化(尽管这不是肯定的)保证这个对象在他的列表里而不是把它添加到生成代码里。constexpr的主要功能可以在编译时计算表达式的值进行了范围扩展,这是一种计算安全而且可以用在编译时期(如初始化枚举或者整体模板参数)。constexpr声明对象可以在初始化编译的时候计算出结果来。他们基本上只保存在编译器的列表,如果需要的话会释放到生成的代码里。参考:theC++draft3.6.2Initializationofnon-localobjects,3.9Types[12],5.19Constantexpressions,7.1.5The

constexprspecifier[N1521=03-0104]GabrielDosReis:

(originalproposal).[N2235=07-0095]GabrielDosReis,BjarneStroustrup,andJensMaurer:

nullptr一个指针空值常量nullptr是一个指针空值常量,不是一个整数。12345678910char*p=nullptr;

int*q=nullptr;

char*p2=0;

//0stillworksandp==p2

voidf(int);

voidf(char*);

f(0);

//callf(int)

f(nullptr);

//callf(char*)

voidg(int);

g(nullptr);

//error:nullptrisnotanint

inti=nullptr;

//error:nullptrisnotanint参考:[N1488==/03-0071]HerbSutterandBjarneStroustrup:

[N2214=07-0074]HerbSutterandBjarneStroustrup:

ECMA-372foradescriptionofthisfeatureasoriginallydesignedin

C++/CLI

beforebeingproposedforISOC++.复制并再抛出异常你如何捕获一个异常然后把它抛出到另一个线程?使用标准文档18.8.5里描述的标准库的魔力方法吧。exception_ptrcurrent_exception();正在处理的异常(15.3)或者正在处理的异常的副本(拷贝)将返回一个exception_ptr变量,如果当前没有遇到异常,返回值为一个空的exception_ptr变量。只要exception_ptr指向一个异常,那么至少在exception_ptr的生存期内,运行时能够保证被指向的异常是有效的。voidrethrow_exception(exception_ptrp);

templateexception_ptrcopy_exception(Ee);它的作用如同:12345try{

throwe;

}catch(...){

returncurrent_exception();

}当我们需要将异常从一个线程传递到另外一个线程时,这个方法十分有用的。内联命名空间内联命名空间机制是通过一种支持版本更新的机制来支持库的演化,推导:12345678910111213141516//fileV99.h:

inlinenamespaceV99{

voidf(int);

//doessomethingbetterthantheV98version

voidf(double);//newfeature

//...

}

//fileV98.h:

namespaceV98{

voidf(int);

//doessomething

//...

}

//fileMine.h:

namespaceMine{

#include"V99.h"

#include"V98.h"

}我们这里有一个命名空间Mine包含最新版本的(V99)和前一个版本(V98),如果你想要显式应用(某个版本的函数),你可以:123456#include"Mine.h"

usingnamespaceMine;

//...

V98::f(1);

//oldversion

V99::f(1);

//newversion

f(1);

//defaultversion内联的关键是使内联命名空间的声明和直接在外围命名空间声明一样。lnline是静态的及面向实现的设施,它由命名空间的设计者放置来帮助用户进行选择。对于Mine的用是不可以说“我想要的命名空间是V98而不是V99”。参照:Standard7.3.1Namespacedefinition[7]-[9].用户自定义数据标识C++提供了很多内置的数据标识符(2.14节变量)built-intypes(2.14Literals):1234567123//int

1.2//double

1.2F

//float

'a'//char

1ULL

//unsignedlonglong

0xD0

//hexadecimalunsigned

"as"

//string然而,爱C++98里并没有用户自定义的数据标识符。这就有悖于甚至冲突“用户自定义类型和内置leiixng一样得到支持”的原则。特殊情况下,人们有这样的需求:1234567"Hi!"s

//std::string,not``zero-terminatedarrayofchar''

1.2i

//imaginary

123.4567891234df

//decimalfloatingpoint(IBM)

101010111000101b

//binary

123s

//seconds

123.56km

//no

温馨提示

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

评论

0/150

提交评论