C++11带来的优雅语法
自动类型推导auto
auto的自动类型推导,用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推导,可以简化我们的编程工作;
auto是在编译时对变量进行了类型推导,所以不会对程序的运行效力造成不良影响;
另外,仿佛auto也并不会影响编译速度,由于编译时本来也要右边推导然后判断与左边是不是匹配。
autoa;//毛病,auto是通过初始化表达式进?类型推导,如果没有初始化表达式,就没法肯定a的类型
autoi=1;
autod=1.0;
autostr=HelloWorld
autoch=A
auto对援用的推导默许为值类型,可以指定援用修饰符设置为援用:
intx=5;
inty=x;
auotz=y;//z为int
autoz=y;//z的类型为int
对指针的推导默许为指针类型,固然,也可以指定*修饰符(效果一样):
int*px=x;
autopy=px;
auto*py=px;
推导常量
constint*px=x;
autopy=px;//py的类型为constint*
constautopy=px;//py的类型为constint*
萃取类型decltype
decltype实际上有点像auto的反函数,使用auto可以用来声明一个指定类型的变量,而decltype可以通过一个变量(或表达式)得到类型;
#include
intmain(){
intx=5;
decltype(x)y=x;//等于autoy=x;
conststd::vectorv(1);
autoa=v[0];//ahastypeint
decltype(v[1])b=1;//bhastypeconstint,thereturntypeof
//std::vector::operator[](size_type)const
autoc=0;//chastypeint
autod=c;//dhastypeint
decltype(c)e;//ehastypeint,thetypeoftheentitynamedbyc
decltype((c))f=c;//fhastypeint,because(c)isanlvalue
decltype(0)g;//ghastypeint,because0isanrvalue
}
有没有联想到STL中的萃取器?写模版时有了这个是否是会方便很多;
返回类型后置语法Trailingreturntype
C++11支持返回值后置
例如:
intadding_func(intlhs,intrhs);
可以写为:
autoadding_func(intlhs,intrhs)-int
auto用于占位符,真正的返回值在后面定义;
这样的语法用于在编译时返回类型还不确定的场合;
比如有模版的场合中,两个类型相加的终究类型只有运行时才能肯定:
template
autoadding_func(constLhslhs,constRhsrhs)-decltype(lhs+rhs)
{returnlhs+rhs;}
coutadding_func(dv,iv)endl;
auto用于占位符,真正的返回值类型在程序运行中,函数返回时才肯定;
不用auto占位符,直接使用decltype推导类型:
decltype(lhs+rhs)adding_func(constLhslhs,constRhsrhs)
这样写,编译器没法通过,由于模版参数lhs和rhs在编译期间还未声明;
固然,这样写可以编译通过:
decltype((*(Lhs*)0)+(*(Rhs*)0))adding_func(constLhslhs,constRhsrhs)
但这类情势实在是不直观,不如auto占位符方式直观易懂;
空指针标识nullptr
空指针标识(nullptr)(其本质是一个内置的常量)是一个表示空指针的标识,它不是一个整数。这里应当与我们经常使用的NULL宏相区分,虽然它们都是用来表示空置针,但NULL只是一个定义为常整数0的宏,而nullptr是C++11的一个关键字,一个内建的标识符。
nullptr和任何指针类型和类成员指针类型的空值之间可以产生隐式类型转换,一样也可以隐式转换为bool型(取值为false)。但是不存在到整形的隐式类型转换。
有了nullptr,可以解决原来C++中NULL的二义性问题;
voidF(inta){
coutaendl;p=""/aendl;
}
voidF(int*p){
assert(p!=NULL);
coutpendl;p=""/endl;
}
intmain(){
int*p=nullptr;
int*q=NULL;
boolequal=(p==q);//equal的值为true,说明p和q都是空指针
inta=nullptr;//编译失败,nullptr不能转型为int
F(0);//在C++98中编译失败,有二义性;在C++11中调用F(int)
F(nullptr);
return0;
}
区间迭代range-basedforloop
C++11扩大了for的语法,终究支持区间迭代,可以便捷的迭代一个容器的内的元素;
intmy_array[5]={1,2,3,4,5};
//doublethevalueofeachelementinmy_array:
for(intx:my_array){
x*=2;
}
固然,这时候使用auto会更简单;
for(autox:my_array){
x*=2;
}
如果有更加复杂的场景,使用auto的优势立刻体现出来:
mapmap;
sertmake_pair(ss,1);/make_pair
for(autox:my_map)
{
rst/cond;
}
去除右尖括号的蹩脚语法rightanglebrackets
在C++98标准中,如果写一个含有其他模板类型的模板:
vectorvector_of_int_vectors;
你必须在结束的两个’‘之间添加空格。这不仅烦人,而且当你写成而没有空格时,你将得到困惑和误导的编译错误信息。产生这类行动的缘由是C++词法分析的最大匹配原则(maximalmunchrule)。一个好消息是从今往后,你再也不用担心了:
vectorvector_of_int_vectors;
在C++98中,这是一个语法错误,由于两个右角括号(‘’)之间没有空格(译注:因此,编译器会将它分析为””操作符)。C++0x可以正确地分辨出这是两个右角括号(‘’),是两个模板参数列表的结尾。
为何之前这会是一个问题呢?一般地,一个编译器前端会依照“分析/阶段”模型进行组织。扼要描写以下:
词法分析(从字符中构造token)
语法分析(检查语法)
类型检查(肯定名称和表达式的类型)
这些阶段在理论上,乃至在某些实际运用中,都是严格独立的。所以,词法分析器会认为””是一个完全的token(通常意味着右移操作符或是输入),而没法理解它的实际意义(译注:即在具体的上下文环境下,某一个符号的具体意义)。特别地,它没法理解模板或内置模板参数列表。但是,为了使上述示例“正确”,这三个阶段必须进行某种情势的交互、配合。解决这个问题的最关键的点在于,每个C++编译器已完全理解全部问题(译注:对全部问题进行了全部的词法分析、符号分析及类型检测,然后分析各个阶段的正确性),从而给出令人满意的毛病消息。
lambda表达式的引入
对为标准库算法写函数/函数对象(functionobject)这个事儿大家已抱怨很久了(例如Cmp)。特别是在C++98标准中,这会使人更加痛苦,由于没法定义一个局部的函数对象。
首先,我们需要在我们实现的逻辑作用域(一般是函数或类)外部定义比较用的函数或函数对象,然后,才能使用:
boolmyfunction(inti,intj){return(ij);p=}=
structmyclass{
booloperator()(inti,intj){return(ij);}p=""/j);}
}myobject;
intmain()
{
intmyints[]={32,71,12,45,26,80,53,33};
std::vectormyvector(myints,myints+8);
//usingfunctionas
std::sort(gin(),d(),myfunction);
//usingfunctionobjectas
std::sort(gin(),d(),myobject);
}
不过现在好多了,lambda表达式允许用”inline”的方式来写函数了:
sort(gin(),d(),[](inti,intj){returnij;});
真是亲切!lambda的引入应该会增加大家对STL算法的使用频率;
原生字符串Rawstringliterals
比如,你用标准regex库来写一个正则表达式,但正则表达式中的反斜杠’’其实却是一个“转义(escape)”操作符(用于特殊字符),这相当使人讨厌。斟酌如何去写“由反斜杠隔开的两个词语”这样一个模式(w\w):
strings=\\w\\\\\\w;//不直观、且容易出错
请注意,在正则表达式和普通C++字符串中,各自都需要使用连续两个反斜杠来表示反斜杠本身。但是,假设使用C++11的原生字符串,反斜杠本身仅需一个反斜杠就可以表示。因此,上述的例子简化为:
strings=R(\w\\\w);//ok
非成员begin()和end()
非成员begin()和end()函数。他们是新加入标准库的,除能提高了代码一致性,还有助于更多地使用泛型编程。它们和所有的STL容器兼容。更重要的是,他们是可重载的。所以它们可以被扩展到支持任何类型。对C类型数组的重载已包括在标准库中了。
在这个例子中我打印了一个数组然后查找它的第一个偶数元素。如果std::vector被替换成C类型数组。代码可能看起来是这样的:
intarr[]={1,2,3};
std::for_each(arr[0],arr[0]+sizeof(arr)/sizeof(arr[0]),[](intn){std::coutnstd::endl;});
autois_odd=[](intn){returnn%2==1;};
autobegin=arr[0];
autoend=arr[0]+sizeof(arr)/sizeof(arr[0]);
autopos=std::find_if(begin,end,is_odd);
if(pos!=end)
std::cout*posstd::endl;
如果使用非成员的begin()和end()来实现,就会是以下这样的:
intarr[]={1,2,3};
std::for_each(std::begin(arr),std::end(arr),[](intn){std::coutnstd::endl;});
autois_odd=[](intn){returnn%2==1;};
autopos=std::find_if(std::begin(arr),std::end(arr),is_odd);
if(pos!=std::end(arr))
std::cout*posstd::endl;
这基本上和使用std::vecto的代码是完全一样的。这就意味着我们可以写一个泛型函数处理所有支持begin()和end()的类型。
初始化列表及统一初始化方法Initializerlists
在C++98中,对vector的多个初始化,我们需要这样:
intmyints[]={10,20,30,30,20,10,10,20};
std::vectormyvector(myints,myints+8);
现在,我们可以这样:
std::vectorsecond={10,20,30,30,20,10,10,20};
初始化表有时可以像参数那样方便的使用。看下边这个例子(x,y,z是string变量,Nocase是一个大小写不敏感的比较函数):
autox=max({x,y,z},Nocase());
初始化列表不再仅限于数组。对常见的map、string等,我们可以使用以下语法来进行初始化:
intarr[3]{1,2,3};
vectoriv{1,2,3};
mapm{{1,a},{2,b}};
stringstr{HelloWorld};
可以接受一个“{}列表”对变量进行初始化的机制实际上是通过一个可以接受参数类型为std::initializer_list的函数(通常为构造函数)来实现的。例如:
voidf(initializer_list);
f({1,2});
f({23,,,});
f({});//以空列表为参数调用f()
f{1,2};//毛病:缺少函数调用符号()
sert({{Bjarne,Stroustrup},{,,}});
初始化列表可以是任意长度,但必须是同质的(所有的元素必须属于某一模板类型T,或可转化至T类型的)。
容器可以用以下方式来实现“初始化列表构造函数”:
templateclassvector{
public:
//初始化列表构造函数
vector(std::initializer_lists)
{
//预留出适合的容量
reserve(ze());//
//初始化所有元素
uninitialized_copy(gin(),d(),elem);
sz=ze();//设置容器的size
}
//...其他部份保持不变...
};
使用“{}初始化”时,直接构造与拷贝构造之间仍有细微差异,但不再像之前那样明显。例如,std::vector具有一个参数类型为int的显式构造函数及一个带有初始化列表的构造函数:
vectorv1(7);//OK:v1有7个元素
v1=9;//Err:没法将int转换为vector
vectorv2=9;//Err:没法将int转换为vector
voidf(constvector);
f(9);//Err:没法将int转换为vector
vectorv1{7};//OK:v1有一个元素,其值为7.0
v1={9};//OK:v1有一个元素,其值为9.0
vectorv2={9};//OK:v2有一个元素,其值为9.0
f({9});//OK:f函数将以列表{9}为参数被调用
vectorvs={
vector(10),//OK,显式构造(10个元素,都是默认值0.0)
vector{10},//OK:显式构造(1个元素,值为10.0)
10//Err:vector的构造函数是显式的
};
函数可以将initializer_list作为一个不可变的序列进行读取。例如:
voidf(initializer_listargs)
{
for(autop=gin();p!=d();++p)
cout*p\n;
}
仅具有一个std::initializer_list的单参数构造函数被称为初始化列表构造函数。
标准库容器,string类型及正则表达式均具有初始化列表构造函数,和(初始化列表)赋值函数等。初始化列表亦可作为一种“序列”以供“序列化for语句”使用。
白癜风中医北京有专治白癜风的医院