C++11 & C++14 & C++17新特性

时间:2023-12-12 08:06:43

C++11:C++11包括大量的新特性:包括lambda表达式,类型推导关键字auto、decltype,和模板的大量改进。

新的关键字

auto

C++11中引入auto第一种作用是为了自动类型推导

auto的自动类型推导,用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推导,可以大大简化我们的编程工作

auto实际上实在编译时对变量进行了类型推导,所以不会对程序的运行效率造成不良影响

另外,似乎auto并不会影响编译速度,因为编译时本来也要右侧推导然后判断与左侧是否匹配

  1. auto a; // 错误,auto是通过初始化表达式进行类型推导,如果没有初始化表达式,就无法确定a的类型
  2. auto i = 1;
  3. auto d = 1.0;
  4. auto str = "Hello World";
  5. auto ch = 'A';
  6. auto func = less<int>();
  7. vector<int> iv;
  8. auto ite = iv.begin();
  9. auto p = new foo() // 对自定义类型进行类型推导

auto不光有以上的应用,它在模板中也是大显身手,比如下例这个加工产品的例子中,如果不使用auto就必须声明Product这一模板参数:

  1. template <typename Product, typename Creator>
  2. void processProduct(const Creator& creator) {
  3. Product* val = creator.makeObject();
  4. // do somthing with val
  5. }
  6. .

如果使用auto,则可以这样写:

  1. template <typename Creator>
  2. void processProduct(const Creator& creator) {
  3. auto val = creator.makeObject();
  4. // do somthing with val
  5. }

抛弃了麻烦的模板参数,整个代码变得更加正解了。

decltype

decltype实际上有点像auto的反函数,auto可以让你声明一个变量,而decltype则可以从一个变量或表达式中得到类型,有实例如下:

  1. int x = 3;
  2. decltype(x) y = x;

有人会问,decltype的实用之处在哪里呢,我们接着上边的例子继续说下去,如果上文中的加工产品的例子中我们想把产品作为返回值该怎么办呢?我们可以这样写:

  1. template <typename Creator>
  2. auto processProduct(const Creator& creator) -> decltype(creator.makeObject()) {
  3. auto val = creator.makeObject();
  4. // do somthing with val
  5. }

nullptr

nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0,

  1. void F(int a){
  2. cout<<a<<endl;
  3. }
  4. void F(int *p){
  5. assert(p != NULL);
  6. cout<< p <<endl;
  7. }
  8. int main(){
  9. int *p = nullptr;
  10. int *q = NULL;
  11. bool equal = ( p == q ); // equal的值为true,说明p和q都是空指针
  12. int a = nullptr; // 编译失败,nullptr不能转型为int
  13. F(0); // 在C++98中编译失败,有二义性;在C++11中调用F(int)
  14. F(nullptr);
  15. return 0;
  16. }

序列for循环

在C++中for循环可以使用类似java的简化的for循环,可以用于遍历数组,容器,string以及由begin和end函数定义的序列(即有Iterator),示例代码如下:

  1. map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};
  2. for (auto p : m){
  3. cout<<p.first<<" : "<<p.second<<endl;
  4. }

Lambda表达式

lambda表达式类似Javascript中的闭包,它可以用于创建并定义匿名的函数对象,以简化编程工作。Lambda的语法如下:

[函数对象参数](操作符重载函数参数)->返回值类型{函数体}

  1. vector<int> iv{5, 4, 3, 2, 1};
  2. int a = 2, b = 1;
  3. for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;}); // (1)
  4. for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);});     // (2)
  5. for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);});// (3)
  • []内的参数指的是Lambda表达式可以取得的全局变量。(1)函数中的b就是指函数可以得到在Lambda表达式外的全局变量,如果在[]中传入=的话,即是可以取得所有的外部变量,如(2)和(3)Lambda表达式
  • ()内的参数是每次调用函数时传入的参数
  • ->后加上的是Lambda表达式返回值的类型,如(3)中返回了一个int类型的变量
  • 下面是各种变量截取的选项:
    • [] 不截取任何变量
    • [&} 截取外部作用域中所有变量,并作为引用在函数体中使用
    • [=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
    • [=, &foo]   截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对foo变量使用引用
    • [bar]   截取bar变量并且拷贝一份在函数体重使用,同时不截取其他变量
    • [this]            截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

变长参数的模板

我们在C++中都用过pair,pair可以使用make_pair构造,构造一个包含两种不同类型的数据的容器。比如,如下代码:

  1. auto p = make_pair(1, "C++ 11");

由于在C++11中引入了变长参数模板,所以发明了新的数据类型:tuple,tuple是一个N元组,可以传入1个, 2个甚至多个不同类型的数据

  1. auto t1 = make_tuple(1, 2.0, "C++ 11");
  2. auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});

这样就避免了从前的pair中嵌套pair的丑陋做法,使得代码更加整洁

另一个经常见到的例子是Print函数,在C语言中printf可以传入多个参数,在C++11中,我们可以用变长参数模板实现更简洁的Print

  1. template<typename head, typename... tail>
  2. void Print(Head head, typename... tail) {
  3. cout<< head <<endl;
  4. Print(tail...);
  5. }

Print中可以传入多个不同种类的参数,如下:

  1. Print(1, 1.0, "C++11");

更加优雅的初始化方法

在引入C++11之前,只有数组能使用初始化列表,其他容器想要使用初始化列表,只能用以下方法:

  1. int arr[3] = {1, 2, 3}
  2. vector<int> v(arr, arr + 3);

在C++11中,我们可以使用以下语法来进行替换:

  1. int arr[3]{1, 2, 3};
  2. vector<int> iv{1, 2, 3};
  3. map<int, string>{{1, "a"}, {2, "b"}};
  4. string str{"Hello World"};

然后呢…

如果你想了解更多C++11令人兴奋的新特性,我会向你推荐这两个博客:

胡健的C++11系列博文

ToWrting的C++11系列博文

C++11的编译器支持列表

原文链接:http://my.oschina.net/wangxuanyihaha/blog/183151

C++14:C++14的主要特性可以分为三个领域:Lambda函数、constexpr和类型推导。

Lambda函数

C++14的泛型Lambda使编写如下语句成为可能:

auto lambda = [](auto x, auto y) {return x + y;};

而另一方面,C++11要求Lambda参数使用具体的类型声明,比如:

auto lambda = [](int x, int y) {return x + y;};

此外,新标准中的std::move函数可用于捕获Lambda表达式中的变量,这是通过移动对象而非复制或引用对象实现的:

std::unique_ptr ptr(new int());
auto lambda = [value = std::move(ptr)] {return *value;};

constexpr

在C++11中,使用constexpr声明的函数可以在编译时执行生成一个值,用在需要常量表达式的地方,比如作为初始化模板的整形参数。C++11的constexpr函数只能包含一个表达式,C++14放松了这些限制,支持诸如if 和switch等条件语句,支持循环,其中包括基于区间(range)的for 循环。

类型推导

C++11仅支持Lambda函数的类型推导,C++14对其加以扩展,支持所有函数的返回类型推导:

auto DeducedReturnTypeFunction();

因为C++14是强类型语言,有些限制需要考虑:

  • 如果一个函数的实现中有多个返回语句,这些语句一定要推导出同样的类型。
  • 返回类型推导可以用在前向声明中,但是在使用它们之前,翻译单元中必须能够得到函数定义。
  • 返回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导,以便编译器推导出返回类型。

C++14带来的另一个类型推导方面的改进是decltype(auto)语法,它支持使用与auto同样的机制计算给定表达式的类型。auto和 decltype在C++11中就已经出现了,但是它们在推导类型时使用了不同的机制,这可能会产生不同的结果。

C++14中的其他改变包括可以声明变量模板,支持使用0b或0B前缀来声明二进制字面常量。InfoQ已经介绍过C++14中可能破坏C++11程序的其他小型修改

主流C++编译器对新语言特性的支持正在有条不紊地开发:Clang“完全实现了当前草案的所有内容”;GCCVisual Studio也对C++14的新特性提供了一些支持。