C++ 11和C++98相比有哪些新特性 C11标准提供了许多有用的新特性。这篇文章特别针对使C11和C98相比看上去像一门新语言的特性因为C11改变了书写C代码的风格和习惯也改变了设计C库的方式。例如你会看到更多的被当作参数和返回值的智能指针还有按值(by value)返回巨大对象的函数。它们被使用的非常广泛在大多数代码中你都能看到它们。举个例子在现代C中几乎每5行C代码你就能看到auto关键字。还有一些其它的非常好的C11新特性但先把这篇文章所描述的新特性熟悉起来把因为这些被广泛使用的特性展示了为什么C11代码是简洁的安全的和快速的就像其它现代主流开发语言一样并且性能和传统C一样强大。1. Auto在任何可能的时候使用auto。因为有两个原因。第一非常明显能够避免重复输入我们已经声明的并且编译器已经认识的类型名称这是非常方便的。12345678// C98mapint,string::iterator i m.begin();doubleconstxlimit config[xlimit];singleton s singleton::instance();// C11auto i begin(m);autoconstxlimit config[xlimit];auto s singleton::instance();第二当有遇到一个你不知道或者无法用语言表达的类型时auto就不仅仅是使用方便这么简单了比如大多数lambda函数的类型你不能够容易的将其类型拼写出来甚至根本就不能够写出来。1234// C98binder2nd greater x bind2nd( greater(), 42 );// C11auto x [](inti) {returni 42; };注意使用auto并没有修改代码的语义。代码仍然是静态输入(statically typed)的,并且每个表达式都干净利落只是不再强制我们多余的重新声明类型的名称。一些人开始的时候害怕使用auto因为给人的感觉像是并没有声明重新声明我们想要的类型这意味着我们可能会突然得到一个不同的类型。如果你想显示的做强制类型转换这没有问题声明目标类型就可以了。但是在大部分情况下使用auto就足够了由于出现错误而得到另外一个类型的情况很少见在使用强静态类型strong static typing情况下如果类型出现错误编译器就会告诉你。2. 智能指针no delete总是使用智能指针不要用原生指针和delete。除非需要实现你自己的底层数据结构把原生指针很好的封装在类(class boundary)中如果你知道你是另外一个对象的唯一拥有着使用unique_ptr来表示唯一的拥有权。一个new T表达式能很快的初始化一个拥有 这个智能指针的对象特别是unique_ptr。典型的例子是指向实现的指针Pimpl Idiom12345678910111213// C11 Pimpl idiom: header fileclasswidget {public:widget();// ... (see GotW #100) ...private:classimpl;unique_ptrimpl pimpl;};// implementation fileclasswidget::impl {/*...*/};widget::widget() : pimpl{newimpl{/*...*/} } { }// ...使用shared_ptr来表示共享所有权shared ownership。使用make_shared来创建共享对象更好。123456// C98widget* pw newwidget();:::deletepw;// C11auto pw make_sharedwidget();使用weak_ptr来打破循环和表示可选性比如实现一个对象缓存12345678910// C11classgadget;classwidget {private:shared_ptrgadget g;// if shared ownership};classgadget {private:weak_ptrwidget w;};如果你了解到另外一个对象比你的生存周期要长并且你想观察这个对象那么使用原生指针raw pointer。// C11class node {vectorunique_ptrnode children;node* parent;public::::};3. Nullptr用nullptr来表示一个空指针不要再使用数字0或者宏NULL来表示空指针了因为这些是模棱两可的既能表示整形也可表示指针。// C98int* p 0;// C11int* p nullptr;4. Range for对一个范围内的元素进行有序访问基于range的for循环会是更方便的用法。// C98for( vectorint::iterator i v.begin(); i ! v.end(); i ) {total *i;}// C11for( auto d : v ) {total d;}5. 非成员begin和end使用非成员函数begin(x)和end(x)(不是x.begin()和x.end())因为begin(x)和end(x)是可扩展的能同所有容器类型一块工作——甚至数组也可以——并不是只针对提供了STL风格的x.begin()和x.end()成员函数的容器。如果你正在使用一个非STL集合类型这个类型提供迭代器但不是STL风格的x.begin()和x.end()你可以对他的非成员函数begin()和end()进行重载这样你就可以使用同STL容器同样的风格进行编码。标准中举了一个例子数组并且提供了对象的begin和end函数vectorint v;int a[100];// C98sort( v.begin(), v.end() );sort( a[0], a[0] sizeof(a)/sizeof(a[0]) );// C11sort( begin(v), end(v) );sort( begin(a), end(a) );6. Lambda函数和算法Lambda表达式改变了游戏规则它会时不时的改变你的编码方式这种方式优雅并且快速。Lambda使现存STL算法实用性提高了百倍。新增加的C库的设计都以支持lambad表达式为前提(例如PPL)甚至有一些库需要通过你编写lambda表达式来使用库例如c AMP。这里有个例子找到v中的X并且Y的第一个元素。在C11中最简单并且干净的代码是使用标准算法。// C98: write a naked loop (using std::find_if is impractically difficult)vectorint::iterator i v.begin(); // because we need to use i laterfor( ; i ! v.end(); i ) {if( *i x *i y ) break;}// C11: use std::find_ifauto i find_if( begin(v), end(v), [](int i) { return i x i y; } );想使用一个循环或者类似的语言特性language feature但实际上在该语言中并不存在怎么办将其实现成模板函数库算法就可以了多亏了lambda使用它就像是用一个语言特性一样的方便但是更灵活因为它确实是一个库而不是一个固定的语言特性。// C#lock( mut_x ) {... use x ...}// C11 without lambdas: already nice, and more flexible (e.g., can use timeouts, other options){lock_guardmutex hold { mut_x };... use x ...}// C11 with lambdas, and a helper algorithm: C# syntax in C// Algorithm: templatetypename T void lock( T t, F f ) { lock_guard hold(t); f(); }lock( mut_x, []{... use x ...});熟悉一下lambda吧你会发现他们很有用并不只是在c中它们已经在几个主流语言中得到支持并且流行开来。7. Move/把move当作是对拷贝的优化最合适不过了虽然它也包含其他方面的东西像完美转发(perfect forwarding)move语义改变了我们设计API的方式。我们会越来越多的将函数设计成return by value。// C98: alternatives to avoid copyingvectorint* make_big_vector(); // option 1: return by pointer: no copy, but dont forget to delete:::vectorint* result make_big_vector();void make_big_vector( vectorint out ); // option 2: pass out by reference: no copy, but caller needs a named object:::vectorint result;make_big_vector( result );// C11: movevectorint make_big_vector(); // usually sufficient for callee-allocated out situations:::auto result make_big_vector(); // guaranteed not to copy the vector如果你想获得比copy更高效的办法对你的类型使用move语义吧。8. 统一初始化和初始化列表没有发生变化的当初始化一个non-POD或者auto的本地变量时继续使用熟悉的不带额外花括号{}的语法。// C98 or C11int a 42; // still fine, as always// C 11auto x begin(v); // no narrowing or non-initialization is possible在其他情况中特别是随处可见的使用来构造对象使用花括号{}会更好。使用花括号{}能避免一些潜在的问题你不会突然得到一个收缩转换(narrowing conversions)后的值(比如float转换成int),也不会有偶尔突发的未初始化POD成员变量或者数组的存在也能避免在c98中会碰到的奇怪事你的代码编译没问题你需要的是变量但实际上你声明了一个函数这都源于C声明语法的模糊不清Scott Meyers的著名说法“C最令人苦恼的解析”。通过使用新风格的语法上面解析问题会不复存在。