1. gzyueqian
      13352868059

      想成為一名合格的程序員應該怎么選擇好的c++開發培訓學校?

      更新時間: 2018-10-05 12:00:00來源: c++培訓瀏覽量:2712

      C++17標準已經在2017上半年就已經討論確定了,正在形成ISO標準文檔,今年晚些時候會正式發布。本文將介紹標準中值得開發者關注的新特新和基本用法。
      總的來說C++17相比C++11的新特性來說新特性不算多,做了一些小幅改進。C++17增加了數十項新特性,值得關注的特性大概有下面這些:
      constexpr if
      constexpr lambda
      fold expression
      void_t
      structured binding
      std::apply, std::invoke
      string_view
      parallel STL
      inline variable
      剩下的有一些來自于boost庫,比如variant,any、optional和filesystem等特性,string_view其實在boost里也有。還有一些是語法糖,比如if init、deduction guide、guaranteed copy Elision、template、nested namespace、single param static_assert等特性。我接下來會介紹C++17主要的一些特性,介紹它們的基本用法和作用,讓讀者對C++17的新特性有一個基本的了解。
      fold expression
      C++11增加了一個新特性可變模版參數(variadic template),它可以接受任意個模版參數在參數包中,參數包是三個點…,它不能直接展開,需要通過一些特殊的方法才能展開,導致在使用的時候有點難度。現在C++17解決了這個問題,讓參數包的展開變得容易了,Fold expression就是方便展開參數包的。
      fold expression的語義
      fold expression有4種語義:
      unary right fold (pack op …)
      unary left fold (… op pack)
      binary right fold (pack op … op init)
      binary left fold (init op … op pack)
      其中pack代表變參,比如args,op代表操作符,fold expression支持32種操作符:
      引用
      + - * / % ^ & | = > += -= *= /= %= ^= &= |= >= == != = && || , .* ->*
      1
      + - * / % ^ & | = > += -= *= /= %= ^= &= |= >= == != = && || , .* ->*
      unary right fold的含義
      fold (E op …) 意味著 E1 op (… op (EN-1 op EN)).
      顧名思義,從右邊開始fold,看它是left fold還是right fold我們可以根據參數包…所在的位置來判斷,當參數包…在操作符右邊的時候就是right fold,在左邊的時候就是left fold。我們來看一個具體的例子:

      template<typename... Args>
      auto add_val(Args&&... args) {
          return (args +  ...);
      }
      
      
      auto t = add_val(1,2,3,4); //10

      template<typename... Args>
      auto add_val(Args&&... args) {
          return (args +  ...);
      }

      auto t = add_val(1,2,3,4); //10
      right fold的過程是這樣的:(1+(2+(3+4))),從右邊開始fold。

      unary left fold的含義
      fold (… op E) 意味著 ((E1 op E2) op …) op EN。

      對于+這種滿足交換律的操作符來說left fold和right fold是一樣的,比如上面的例子你也可以寫成left fold。

      template<typename... Args>
      auto add_val(Args&&... args) {
          return (... + args);
      }
      
      
      auto t = add_val(1,2,3,4); //10

      template<typename... Args>
      auto add_val(Args&&... args) {
          return (... + args);
      }
       
      auto t = add_val(1,2,3,4); //10
      對于不滿足交換律的操作符來說就要注意了,比如減法。

      template<typename... Args>
      auto sub_val_right(Args&&... args) {
          return (args - ...);
      }
      
      template<typename... Args>
      auto sub_val_left(Args&&... args) {
          return (... - args);
      }
      
      auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3
      auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5

      template<typename... Args>
      auto sub_val_right(Args&&... args) {
          return (args - ...);
      }
       
      template<typename... Args>
      auto sub_val_left(Args&&... args) {
          return (... - args);
      }
       
      auto t = sub_val_right(2,3,4); //(2-(3-4)) = 3
      auto t1 = sub_val_left(2,3,4); //((2-3)-4) = -5
      這次right fold和left fold的結果就不一樣。

      binary fold的含義
      Binary right fold (E op … op I) 意味著 E1 op (… op (EN-1 op (EN op I)))。

      Binary left fold (I op … op E) 意味著 (((I op E1) op E2) op …) op E2。

      其中E代表變參,比如args,op代表操作符,I代表一個初始變量。

      二元fold的語義和一元fold的語義是相同的,看一個二元操作符的例子:

      template<typename... Args>
      auto sub_one_left(Args&&... args) {
          return (1 - ... - args);
      }
      
      template<typename... Args>
      auto sub_one_right(Args&&... args) {
          return (args - ... - 1);
      }
      
      auto t = sub_one_left(2,3,4);// (((1-2)-3)-4) = -8
      auto t1 = sub_one_right(2,3,4);//(2-(3-(4-1))) = 2


      template<typename... Args>
      auto sub_one_left(Args&&... args) {
          return (1 - ... - args);
      }
       
      template<typename... Args>
      auto sub_one_right(Args&&... args) {
          return (args - ... - 1);
      }
       
      auto t = sub_one_left(2,3,4);// (((1-2)-3)-4) = -8
      auto t1 = sub_one_right(2,3,4);//(2-(3-(4-1))) = 2
      相信通過這個例子大家應該對C++17的fold expression有了基本的了解。

      comma fold
      在C++17之前,我們經常使用逗號表達式和std::initializer_list來將變參一個個傳入一個函數。比如像下面這個例子:

      template<typename T>
      void print_arg(T t)
      {
          std::cout << t << std::endl;
      }
      
      
      template<typename... Args>
      void print2(Args... args)
      {
          //int a[] = { (printarg(args), 0)... };
          std::initializer_list<int>{(print_arg(args), 0)...};
      }

      template<typename T>
      void print_arg(T t)
      {
          std::cout << t << std::endl;
      }
       
      template<typename... Args>
      void print2(Args... args)
      {
          //int a[] = { (printarg(args), 0)... };
          std::initializer_list<int>{(print_arg(args), 0)...};
      }
      這種寫法比較繁瑣,用fold expression就會變得很簡單了。

      template<typename... Args>
      void print3(Args... args)
      {
          (print_arg(args), ...);
      }

      template<typename... Args>
      void print3(Args... args)
      {
          (print_arg(args), ...);
      }
      這是right fold,你也可以寫成left fold,對于comma來說兩種寫法是一樣的,參數都是從左至右傳入print_arg函數。

      template<typename... Args>
      void print3(Args... args)
      {
          (..., print_arg(args));
      }

      template<typename... Args>
      void print3(Args... args)
      {
          (..., print_arg(args));
      }
      你也可以通過binary fold這樣寫:

      template<typename ...Args>
      void printer(Args&&... args) {
          (std::cout << ... << args) << 'n';
      }

      template<typename ...Args>
      void printer(Args&&... args) {
          (std::cout << ... << args) << 'n';
      }
      也許你會覺得能寫成這樣:

      template<typename ...Args>
      void printer(Args&&... args) {
          (std::cout << args << ...) << 'n';
      }

      template<typename ...Args>
      void printer(Args&&... args) {
          (std::cout << args << ...) << 'n';
      }
      但這樣寫是不合法的,根據binary fold的語法,參數包…必須在操作符中間,因此上面的這種寫法不符合語法要求。
      借助comma fold我們可以簡化代碼,假如我們希望實現tuple的for_each算法,像這樣:

      for_each(std::make_tuple(2.5, 10, 'a'),[](auto e) { std::cout << e<< 'n'; });

      這個for_each將會遍歷tuple的元素并打印出來。在C++17之前我們如果要實現這個算法的話,需要借助逗號表達式和std::initializer_list來實現,類似于這樣:
      template <typename... Args, typename Func, std::size_t... Idx>
      void for_each(const std::tuple& t, Func&& f, std::index_sequence<Idx...>) {
          (void)std::initializer_list<int> { (f(std::get<Idx>(t)), void(), 0)...};
      }

      template <typename... Args, typename Func, std::size_t... Idx>
      void for_each(const std::tuple& t, Func&& f, std::index_sequence<Idx...>) {
          (void)std::initializer_list<int> { (f(std::get<Idx>(t)), void(), 0)...};
      }
      這樣寫比較繁瑣不直觀,現在借助fold expression我們可以簡化代碼了。

      template <typename... Args, typename Func, std::size_t... Idx>
      void for_each(const std::tuple<Args...>& t, Func&& f, std::index_sequence<Idx...>) {
          (f(std::get<Idx>(t)), ...);
      }

      template <typename... Args, typename Func, std::size_t... Idx>
      void for_each(const std::tuple<Args...>& t, Func&& f, std::index_sequence<Idx...>) {
          (f(std::get<Idx>(t)), ...);
      }
      借助coma fold我們可以寫很簡潔的代碼了。
      constexpr if
      constexpr標記一個表達式或一個函數的返回結果是編譯期常量,它保證函數會在編譯期執行。相比模版來說,實現編譯期循環或遞歸,C++17中的constexpr if會讓代碼變得更簡潔易懂。比如實現一個編譯期整數加法:

      template<int N>
      constexpr int sum()
      {
          return N;
      }
      
      template <int N, int N2, int... Ns>
      constexpr int sum()
      {
          return N + sum<N2, Ns...>();
      }

      template<int N>
      constexpr int sum()
      {
          return N;
      }
       
      template <int N, int N2, int... Ns>
      constexpr int sum()
      {
          return N + sum<N2, Ns...>();
      }
      C++17之前你可能需要像上面這樣寫,但是現在你可以寫更簡潔的代碼了。

      template <int N, int... Ns>
      constexpr auto sum17()
      {
          if constexpr (sizeof...(Ns) == 0)
              return N;
          else
              return N + sum17<Ns...>();
      }

      template <int N, int... Ns>
      constexpr auto sum17()
      {
          if constexpr (sizeof...(Ns) == 0)
              return N;
          else
              return N + sum17<Ns...>();
      }
      當然,你也可以用C++17的fold expression:

      template<typename ...Args>
      constexpr int sum(Args... args) {
          return (0 + ... + args);
      }

      template<typename ...Args>
      constexpr int sum(Args... args) {
          return (0 + ... + args);
      }
      constexpr還可以用來消除enable_if了,對于討厭寫一長串enable_if的人來說會非常開心。比如我需要根據類型來選擇函數的時候:

      template<typename T>
      std::enable_if_t<std::is_integral<T>::value, std::string> to_str(T t)
      {
          return std::to_string(t);
      }
      
      template<typename T>
      std::enable_if_t<!std::is_integral<T>::value, std::string> to_str(T t)
      {
          return t;
      }

      template<typename T>
      std::enable_if_t<std::is_integral<T>::value, std::string> to_str(T t)
      {
          return std::to_string(t);
      }
       
      template<typename T>
      std::enable_if_t<!std::is_integral<T>::value, std::string> to_str(T t)
      {
          return t;
      }
      經常不得不分開幾個函數來寫,還需要寫長長的enable_if,比較繁瑣,通過if constexpr可以消除enable_if了。

      template<typename T>
      auto to_str17(T t)
      {
          if constexpr(std::is_integral<T>::value)
              return std::to_string(t);
          else
              return t;
      }

      template<typename T>
      auto to_str17(T t)
      {
          if constexpr(std::is_integral<T>::value)
              return std::to_string(t);
          else
              return t;
      }
      constexpr if讓C++的模版具備if-else if-else功能了,是不是很酷,C++程序員的好日子來了。
      不過需要注意的是下面這種寫法是有問題的。

      template<typename T>
      auto to_str17(T t)
      {
          if constexpr(std::is_integral<T>::value)
              return std::to_string(t);
      
              return t;
      }

      template<typename T>
      auto to_str17(T t)
      {
          if constexpr(std::is_integral<T>::value)
              return std::to_string(t);
       
              return t;
      }
      這個代碼把else去掉了,當輸入如果是非數字類型時代碼可以編譯過,以為if constexpr在模版實例化的時候會丟棄不滿足條件的部分,因此函數體中的前兩行代碼將失效,只有一句有效。當輸入的為數字的時候就會產生編譯錯誤了,因為if constexpr滿足條件了,這時候就會有兩個return了,就會導致編譯錯誤。
      constexpr if還可以用來替換#ifdef宏,看下面的例子:

      enum class OS { Linux, Mac, Windows };
      
      
      //Translate the macros to C++ at a single point in the application
      #ifdef __linux__
      constexpr OS the_os = OS::Linux;
      #elif __APPLE__
      constexpr OS the_os = OS::Mac;
      #elif __WIN32
      constexpr OS the_os = OS::Windows;
      #endif
      
      
      void do_something() {
           //do something general
      
           if constexpr (the_os == OS::Linux) {
               //do something Linuxy
           }
           else if constexpr (the_os == OS::Mac) {
               //do something Appley
           }
           else if constexpr (the_os == OS::Windows) {
               //do something Windowsy
           }
      
      
           //do something general
      }

      enum class OS { Linux, Mac, Windows };
       
      //Translate the macros to C++ at a single point in the application
      #ifdef __linux__
      constexpr OS the_os = OS::Linux;
      #elif __APPLE__
      constexpr OS the_os = OS::Mac;
      #elif __WIN32
      constexpr OS the_os = OS::Windows;
      #endif
       
      void do_something() {
           //do something general
       
           if constexpr (the_os == OS::Linux) {
               //do something Linuxy
           }
           else if constexpr (the_os == OS::Mac) {
               //do something Appley
           }
           else if constexpr (the_os == OS::Windows) {
               //do something Windowsy
           }
       
           //do something general
      }
      代碼變得更清爽了,再也不需要像以前一樣寫#ifdef那樣難看的代碼塊了。

      constexpr lambda
      constexpr lambda其實很簡單,它的意思就是可以在constexpr 函數中用lambda表達式了,這在C++17之前是不允許的。這樣使用constexpr函數和普通函數沒多大區別了,使用起來非常舒服。下面是constexpr lambda的例子:

      template <typename I>
      constexpr auto func(I i) {
        //use a lambda in constexpr context
        return [i](auto j){ return i + j; }; 
      }

      template <typename I>
      constexpr auto func(I i) {
        //use a lambda in constexpr context
        return [i](auto j){ return i + j; }; 
      }
      constexpr if和constexpr lambda是C++17提供的非常棒的特性,enjoy it.

      string_view
      string_view的基本用法

      C++17中的string_view是一個char數據的視圖或者說引用,它并不擁有該數據,是為了避免拷貝,因此使用string_view可以用來做性能優化。你應該用string_view來代替const char和const string了。string_view的方法和string類似,用法很簡單:

      const char* data = "test";
      std::string_view str1(data, 4);
      std::cout<<str1.length()<<'n'; //4
      if(data==str1)
          std::cout<<"ok"<<'n';
      
      const std::string str2 = "test";
      std::string_view str3(str2, str2.size());

      const char* data = "test";
      std::string_view str1(data, 4);
      std::cout<<str1.length()<<'n'; //4
      if(data==str1)
          std::cout<<"ok"<<'n';
       
      const std::string str2 = "test";
      std::string_view str3(str2, str2.size());
      構造string_view的時候用char*和長度來構造,這個長度可以自由確定,它表示string_view希望引用的字符串的長度。因為它只是引用其他字符串,所以它不會分配內存,不會像string那樣容易產生臨時變量。我們通過一個測試程序來看看string_view如何來幫我們優化性能的。

      using namespace std::literals;
      
      
      constexpr auto s = "it is a test"sv;
      auto str = "it is a test"s;
      
      constexpr int LEN = 1000000;
      boost::timer t;
      for (int i = 0; i < LEN; ++i) {
          constexpr auto s1 = s.substr(3);
      }
      std::cout<<t.elapsed()<<'n';
      t.restart();
      for (int i = 0; i < LEN; ++i) {
          auto s2 = str.substr(3);
      }
      std::cout<<t.elapsed()<<'n';
      
      
      //output
      0.004197
      0.231505

      using namespace std::literals;
       
      constexpr auto s = "it is a test"sv;
      auto str = "it is a test"s;
       
      constexpr int LEN = 1000000;
      boost::timer t;
      for (int i = 0; i < LEN; ++i) {
          constexpr auto s1 = s.substr(3);
      }
      std::cout<<t.elapsed()<<'n';
      t.restart();
      for (int i = 0; i < LEN; ++i) {
          auto s2 = str.substr(3);
      }
      std::cout<<t.elapsed()<<'n';
       
      //output
      0.004197
      0.231505
      我們可以通過字面量””sv來初始化string_view。string_view的substr和string的substr相比,快了50多倍,根本原因是它不會分配內存。
      string_view的生命周期
      由于string_vew并不擁有鎖引用的字符串,所以它也不會去關注被引用字符串的生命周期,用戶在使用的時候需要注意,不要將一個臨時變量給一個string_view,那樣會導致string_view引用的內容也失效。

      std::string_view str_v;
      {
          std::string temp = "test";
          str_v = {temp};
      }

      std::string_view str_v;
      {
          std::string temp = "test";
          str_v = {temp};
      }
      這樣的代碼是有問題的,因為出了作用域之后,string_view引用的內容已經失效了。

      總結
      本文介紹了C++17的fold expression、constexpr if、constexpr lambda和string_view。fold expression為了簡化可變模板參數的展開,讓可以模板參數的使用變得更簡單直觀;constexpr if讓模板具備if-else功能,非常強大。它也避免了寫冗長的enable_if代碼,讓代碼變得簡潔易懂了;string_view則是用來做性能優化的,應該用它來代替const char*和const string。 這些特性對之前的C++14和C++11做了改進和增強,非常酷。

      免費預約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 中文字幕在线你懂得 | 亚洲福利精品视频 | 在线国产亚洲91 | 久久中文字幕无吗一二区 | 中文字幕在线永久免费视频 | 思思国产91久久久久久 |