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

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  

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

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  

nullptr

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

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

序列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;  

Lambda表达式

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

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

  1. vector<int> iv{5, 4, 3, 2, 1};  
  2. int a = 2, b = 1;  
  3.  
  4. for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;}); // (1)  
  5.  
  6. for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);});     // (2)  
  7.  
  8. 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...);  

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(10));
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的新特性提供了一些支持。

     C++迭代速度相对来说还是比较慢的,2010年以后,C++的新版本迭代速度有所加快,这一点,从C++标准版本的历史发布图1就可以看出来:

                             

                                                  图1 C++正式版本发布历史

     C++11算是更新比较大的一次了,引入了很多新属性,以至于C++11出来以后,好多C++同行感叹,这看了C++11感觉像是在学习一门新语言!哈哈,这可能跟C++属性众多和库众多有关吧。现在我们来看看C++17的新增属性吧。

     C++17的入选特性有:

(1).非类型模板参数的 auto

      模板参数分为两种,一种是类型模板参数,也是我们用得最多的一种:

    template <typename T, typename U>
    auto add(T t, U u) {
        return t+u;
    }

     里面的 T 和 U 都是类型模板参数。另一种是非类型模板参数,它可以让不同的字面量成为模板的参数:

    template <typename T, int BufSize>
    class buffer_t {
    public:
        T& alloc();
        void free(T& item);
    private:
        T data[BufSize];
    }
     
    buffer_t<int, 100> buf; // 100 作为模板参数

       遗憾的是我们在编写模板的时候就必须明确非类型模板参数的具体类型,C++17 打破了这一限制,让我们能够在非类型模板参数中使用 auto 关键字,从而让编译器推导具体的类型:

    template <auto value> void foo() {
        return;
    }
     
    foo<10>();  // value 被推导为 int 类型

(2).std::variant<>

       熟悉 boost 的人应该很早就听说过 variant<> 了。variant<> 可以用于存储和操作不同类型的对象。我们在前面(对标准库的扩充:新增容器)对于迭代 std::tuple 时,简单使用了 boost::variant<>。提供给 variant<> 的类型模板参数可以让一个 variant<> 从而容纳提供的几种类型的变量(在其他语言(例如 Python/JavaScript 等)表现为动态类型)。

       C++17 正式将 variant<> 纳入标准库,摇身一变成为 std::variant<>,有了它之后,我们可以将前面的代码更改为:

    #include <variant>
    template <size_t n, typename... Args>
    std::variant<Args...> _tuple_index(size_t i, const std::tuple<Args...>& tpl) {
        if (i == n)
            return std::get<n>(tpl);
        else if (n == sizeof...(Args) - 1)
            throw std::out_of_range("越界.");
        else
            return _tuple_index<(n < sizeof...(Args)-1 ? n+1 : 0)>(i, tpl);
    }
    template <typename... Args>
    std::variant<Args...> tuple_index(size_t i, const std::tuple<Args...>& tpl) {
        return _tuple_index<0>(i, tpl);
    }

(3).结构化绑定(Structured bindings)
       结构化绑定提供了类似其他语言中提供的多返回值的功能。到目前为止,我们可以通过 std::tuple 来构造一个元组,囊括多个返回值。但缺陷是显而易见的,我们没有一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 std::tie 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型。

      C++17 给出的结构化绑定可以让我们写出这样的代码:

    std::tuple<int,double,std::string> f() {
        return std::make_tuple(1,2.3,"456");
    }
    auto [x,y,z] = f(); // x,y,z 分别被推导为int,double,std::string

(4).变量声明的强化

      变量的声明在虽然能够位于任何位置,甚至于 for 语句内能够声明一个临时变量 int,但始终没有办法在 if 和 switch语句中声明一个临时的变量。例如:

    auto p = map_container.try_emplace(key, value);
    if(!p.second) {
        //...
    } else {
        //...
    }

C++17 消除了这一限制,使得我们可以:

    if (auto p = m.try_emplace(key, value); !p.second) {   
        //...
    } else {
        //...
    }


C++17未入选特性有:

(1).Concepts

      C++ 组委会在讨论投票最终确定 C++17 有很多提案,诸如 Concepts/Ranges/Module 等等,其中最受关注的就是 Concepts,可惜这一提案最终被拒,作为技术规范(Technical Specifications, TS) 将其发布。

      Concepts 是对 C++ 模板编程的进一步增强扩展。简单来说,Concepts 是一种编译期的特性,它能够让编译器在编译期时对模板参数进行判断,从而大幅度增强我们在 C++ 中模板编程的体验。使用模板进行编程时候我们经常会遇到各种令人发指的错误,这是因为到目前为止我们始终不能够对模板参数进行检查与限制,例如下面简单的两行代码会造成大量的几乎不可读的编译错误:

    #include <list>
    #include <algorithm>
    int main() {
        std::list<int> l = {1, 2, 3};
        std::sort(l.begin(), l.end());
        return 0;
    }

      而这段代码出现错误的根本原因在于,std::sort 对排序容器必须提供随机迭代器,否则就不能使用,而我们知道 std::list 是不支持随机访问的。用 Concepts 的话来说就是:std::list中的迭代器不满足std::sort中随机迭代器这个 Concepts(概念) 的 requirements(要求)。有了 Concepts,我们就可以这样:

    template <typename T>
    requires Sortable<T>    // Sortable 是一个 concept
    void sort(T& c);

   缩写为:

    template<Sortable T>    // T 是一个 Sortable 的类型名
    void sort(T& c)

甚至于直接将其作为类型来使用:

void sort(Sortable& c); // c 是一个 Sortable 类型的对象

       遗憾的是,C++组委会没有将 Concetps 纳入新标准,而是将其作为TS正式发布(其实早在 C++11 最终定案之前就已经有 Concepts 的呼声了,但 Concepts TS 是2015年才完整正式发布),也就是我们现在看到的 Concepts TS。C++组委会拒绝将 Concepts 纳入新标准的原因其实很简单,并不是技术层面上的原因,纯粹是觉得它还不够成熟。

       Concepts TS 的发布到最后一次 C++17 的讨论会只相隔了不到四个月的时间,Concepts 的(唯一)实现只存在于一个未发布的 gcc 版本中。而 gcc 中关于 Concepts 的实现就是由撰写 Concepts TS 的人开发的,虽然它能够进行相关测试,但还没有认真讨论过这份 TS 会产生哪些不良后果,更何况这份 TS 都没有被测试过。此外,已知的 Concepts 的一个明显的作用就是去辅助实现 Ranges TS 等提案,但实际上它们也没有被选入 C++17,所以可以把 Concepts 继续延后。

        总的来说,类似于 Concepts/Ranges/Modules 这些令人兴奋的特性并没有入选至 C++17,这注定了 C++17 某种意义上来说相较于 C++11/14 依然只是小幅度更新,但我们有望在 C++2x 中看到这些东西的出现。

   C++11/14/17对C++编译器的支持情况:

http://en.cppreference.com/w/cpp/compiler_support
 ————————————————  
版权声明:本文为CSDN博主「老樊Lu码」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fanyun_01/article/details/80471626

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值