c++11特性


0. 簡介

在c++11標准中, 語言本身和標准庫都增加了很多新內容. 里面的某些特性, 會讓你在代碼編寫時更優雅.

 

我的環境:

系統: ubuntu16.04

g++版本: g++5.4.0

使用c++11特性編譯時需加參數 -std=gnu++11  或  -std=c++11

 

1. 特性

1.1 nullptr

NULL可以直接賦值給bool, int, float等類型(有些會編譯警告), 而nullptr賦值給這些類型會編譯報錯.

比起NULL宏來說, nullptr常量更利於函數的重載.

 1 #include <iostream>
 2 
 3 void f(int i)
 4 {
 5     std::cout << "i: " << i << std::endl;
 6     return;
 7 }
 8 
 9 void f(int *s)
10 {
11     std::cout << "s: " << s << std::endl;
12     return;
13 }
14 
15 int main(void)
16 {
17     //f(NULL);
18     f(nullptr);
19 
20     return 0;
21 }

 對於上面的代碼, 使用NULL會編譯報錯(看到有些文章說會調用void f(int); 編譯器不同, 還是有些差別的. 我是編譯都不能通過). 使用nullptr會調用void f(int*s);

 

1.2 強類型枚舉

在傳統的c++枚舉類型中, 程序員必須為同一作用域每一個枚舉項設定一個唯一的名字. 否則就會出現重命名. 而且它們會被隱式轉換為int類型, 無法擁有特定的用戶定義類型.

如下代碼就是傳統c++枚舉出現重命名的問題(代碼編譯報錯).

 1 #include <iostream>
 2 
 3 enum E0
 4 {
 5     Val1,
 6     Val2
 7 };
 8 
 9 enum E1
10 {
11     Val,
12     Val1
13 };
14 
15 int main(void)
16 {
17     return 0;
18 }

 

c++11中引入強類型枚舉的新類型, 不會將枚舉常量暴露到外層作用域, 也不會隱式轉換為int類型, 並且擁有用戶指定的特定類型. 在c++11中, 傳統的枚舉類型也可以由用戶指定特定類型.

如下代碼可以正常編譯. 枚舉類型轉換其它類型只能顯式轉換.

 1 #include <iostream>
 2 
 3 enum class E2 : bool
 4 {
 5     Val1,
 6     Val2,
 7 };
 8 
 9 enum class E3
10 {
11     Val,
12     Val1
13 };
14 
15 int main(void)
16 {
17     //bool k = E2::Val1; /* error */
18     bool b = static_cast<bool>(E2::Val2);
19     return 0;
20 }

 

1.3 類型推斷

在c++11之前, auto關鍵字就已經存在, 之前的auto用於聲明變量為自動變量, 自動變量意味着擁有自動的生命期. 就算不使用auto聲明, 變量依舊默認是有自動的生命期.

int a = 10;            /* 擁有自動生命期 */
auto int b = 10        /* 擁有自動生命期 */
static int c = 01      /* 延長生命期 */

 

在c++11之前, 對auto的使用是極少的, 在c++11已經刪除了這個用法, 取而代之的是全新的auto: 變量的自動類型推斷.

 1 #include <iostream>
 2 #include <typeinfo>
 3 
 4 int main(void)
 5 {
 6     auto a = 20;
 7     auto b = 20LL;
 8     auto c = "hello world";
 9 
10     if (typeid(a) == typeid(int))
11     {
12         std::cout << "a is int" << std::endl;
13     }
14     if (typeid(b) == typeid(long long))
15     {
16         std::cout << "b is long long" << std::endl;
17     }
18     if (typeid(c) == typeid(const char *))
19     {
20         std::cout << "c is const char *" << std::endl;
21     }
22 
23     return 0;
24 }

 

上面代碼輸出:

a is int
b is long long
c is const char *

 

從輸出可以看出, auto 推斷出a為int類型, b 為long long類型, c為const char * 類型; 在編程時不建議這樣使用auto, 直接寫出變量的類型更加的清晰易懂.

auto的自動類型推斷發生在編譯期, 所以使用auto並不會造成程序運行時效率的降低. 而是否會造成編譯期的時間消耗, 我認為是不會的, 在未使用auto時, 編譯器也需要得知右操作數的類型, 再與左操作數的類型進行比較, 檢查是否可以發生相應的轉化, 是否需要進行隱式類型轉換.

auto關鍵字正確的用法:

用於代替冗長復雜, 變量使用范圍專一的變量聲明.

 

 1 #include <string>
 2 #include <vector>
 3 
 4 int main(void)
 5 {
 6     /* 傳統寫法 */
 7     std::vector<std::string> vs1;
 8     for (std::vector<std::string>::iterator i = vs1.begin(); i != vs1.end(); i++)
 9     {
10         /* ... */
11     }
12 
13     /* 使用auto */
14     std::vector<std::string> vs2;
15     for (auto i = vs2.begin(); i != vs2.end(); i++)
16     {
17         /* ... */
18     }
19 
20     /* 配合范圍的遍歷操作更加簡潔 */
21     std::vector<std::string> vs3;
22     for (auto &i : vs3)
23     {
24         /* ... */
25     }
26 
27     return 0;
28 }

從上面可以看出, 使用auto, 會使代碼變得非常簡潔.

 

  • 在定義模板函數時, 用於聲明依賴模板參數的變量類型.
#include <iostream>

template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y)
{
    auto v = x * y;
    std::cout << v << std::endl;
}

int main(void)
{
    return 0;   
}

 

在模板函數中, 我們很難確定x * y的真正類型, 這樣使用auto就很容易定義函數了.

 

  • 模板函數依賴於模板參數的返回值.

decltype 是檢查實體的聲明類型, 或表達式的類型和值類別的一個關鍵字.

返回值后置式聲明語法也是c++11的特性.

#include <iostream>

template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(x * y)
{
    return x * y;
}

int main(void)
{
    return 0;
}

 

當模板函數的返回值依賴於模板的參數時, 我們無法在編譯代碼前確定模板參數的類型, 也無法知道返回值類型, 這時候可以使用auto.

atuo在這里只是返回值占位,  它只是為函數返回值占了一個位置, 真正的返回值是后面的decltype(_Tx * _Ty). 為什么要用返回值后置式聲明語法呢? 

如果沒有后置, 那么聲明就是decltype(x * y) multiply(_Tx x, _Ty y), 而此時x, y還沒有聲明, 編譯時無法通過的.

使用返回值后置式函數聲明語法, 返回值類型的位置必須記做 "auto"

注意事項:

    • auto變量必須在定義時初始化.
    • 函數或模板參數不能被聲明為auto
    • 定義在一個auto序列的變量, 必須始終推到成同一類型. 例如: 
1 auto a1 = 1, a2 = 12, a3 = 30;   /* 正確 */
2 auto a4 = 1, a5 = 1.1, a6 = 'k'; /* 錯誤, 沒有推到成為同一類型 */
    •  auto僅僅是一個占位符, 它並不是一個真正的類型, 不能使用一些以類型為操作數的操作符, 如sizeof, typeid等.

 

1.4 基於范圍的遍歷操作

遍歷容器時支持"foreach" 用法.

可以遍歷c類型的數組, 初始化列表以及任何沖在了begin()和end()函數的類型.

寫法非常簡潔:

 1 #include <iostream>
 2 
 3 int main(void)
 4 {
 5     int arr[] = {1, 20, 9, 4, 111, 53, 23, 7};
 6     //for (const auto &val : arr)
 7     for (const int &val : arr)
 8     {
 9         std::cout << "val: " << val << std::endl;
10     }
11 
12     return 0;
13 }

 

1.5 委托和成員默認初始值

  • 一個構造函數可以調用該類中的其它構造函數來完成部分初始化任務(委托).
  • 聲明成員時可以直接指定默認初始值.
 1 #include <iostream>
 2 
 3 class A
 4 {
 5 public:
 6     A(int new_number) : m_nNumber(new_number) { std::cout << "hello world" << std::endl;    }
 7     A() : A(42) {   std::cout << "hello world 2" << std::endl;  }
 8 
 9 private:
10     int m_nNumber;
11     int m_nValue = 10;
12 
13 };
14 
15 int main(void)
16 {
17     A a;
18     return 0;
19 }

 

1.6 初始化列表和統一初始化方式

允許非POD容器類型通過初始化列表完成構造.

無論是POD還是非POD類型都可以使用 obj = { ... }的方式來進行初始化. 對非POD類型{ ... }將自動匹配和調用構造函數. 因此對於 A(x)的構造函數可以這樣寫: A{x},  A a{x}. 在需要返回一個A類型對象時, 可以寫: return{x}; (建議不要濫用該用法, 否則降低代碼的可讀性和可維護性)

 1 #include <iostream>
 2 #include <vector>
 3 #include <map>
 4 #include <initializer_list>
 5 
 6 class A
 7 {
 8 public:
 9     A(std::initializer_list<int> items)
10     : m_nNumber(*items.begin())
11     {};
12 
13 private:
14     int m_nNumber;
15 };
16 
17 
18 int main(void)
19 {
20     std::vector<int> vec1{1, 6, 22, 100, 8, 15, 10, 9};
21     std::vector<int> vec2 = {1, 6, 22, 100, 8, 15, 10, 9};
22     std::map<int, int> map1 = { {1, 2}, {3, 4}, {5, 6}, {7, 8} };
23 
24     A a1{1, 2};
25     A a2{1, 2, 5, 1000};
26 
27     return 0;
28 }

 

1.7 常量表達式 constexpr

值不會改變並且編譯期就能得到計算結果的表達式.

 

  • constexpr 變量. 一定是一個常量. 而且必須用常量表達式初始化.

  • constexpr 函數 指能用於常量表達式的函數. 函數的返回類型以及所有形參類型都得是字面值類型, 而且函數體中必須有且只有一條return語句.

  • constexpr 構造函數. 用於生成constexpr對象以及constexpr函數的參數或返回類型. constexpr構造函數可以聲明成=default或=delete的形式. constexpr構造函數必須即符合構造函數的要求, 又符合constexpr函數的要求. 所以constexpr構造函數一般是空的.constexpr構造函數必須初始化所有數據成員, 初始值或者使用constexpr構造函數, 或者是一條常量表達式.

 

允許constexpr函數的返回值並非一個常量. 當函數的實參是常量表達式時, 它的返回值也是常量表達式; 反之則不然.

 

 1 constexpr int func1()
 2 {
 3     return 100;
 4 }
 5 
 6 constexpr int func2(int val)
 7 {   /* 如果val是常量表達式, 則func2(val)也是常量表達式 */
 8     return val * func1();
 9 }
10 
11 int main(void)
12 {
13     constexpr int a = 20;        /* 20 是常量表達式 */
14     constexpr int b = a + 2;     /* a + 2 是常量表達式 */
15     constexpr int c = func2(100);
16 
17     //int i = 100;
18     //constexpr int d = func2(i); /* i 不是常量表達式, 所以func2(i)不是常量表達式, 該語句error */
19     return 0;
20 }

 

1.8 顯式默認和禁用方法

用戶可以通過"=default"后綴修飾符, 為每個類顯式指定默認構造函數. 也可以通過"=delete"后綴修飾構造函數和賦值等操作(通常用來實現禁止復制的語義)

顯式限定這些特殊方法增強了代碼的可讀性和可維護性.

 1 class A
 2 {
 3 public:
 4     A() = default;
 5     A(const A &a) = delete;
 6 };
 7 
 8 int main(void)
 9 {
10     A a;
11     //A b(a); /* 拷貝構造函數已經聲明為delete, 無法調用. */
12 
13     return 0;
14 }

 

1.9 override 和 final 修飾符

override 修飾符用於標識指定的虛函數重載了基類中的同名虛方法(而不是定義一個新的同名虛方法). 該修飾符在編譯時嚴格檢查, 避免因為函數簽名不同而導致重載失敗.

final 修飾符可用於修飾類或方法. 在修飾類時, 它表示指定類是一個concrete類(不能再作為基類而被其它類繼承). 

用於函數時, 表示此虛方法已經是最終實現, 任何派生類中重載這個方法的企圖都將引發一個編譯錯誤.

 1 #include <iostream>
 2 
 3 class A
 4 {
 5 public:
 6     A() = default;
 7     virtual ~A() {  std::cout << "~A" << std::endl; }
 8 
 9     virtual void func1() {  std::cout << "A::func1" << std::endl;   }
10     virtual void func2() final {    std::cout << "A::func2" << std::endl;   }
11 };
12 
13 class Base final
14 {
15 public:
16     Base() = default;
17     ~Base() = default;
18 };
19 
20 class B
21 : public A
22 //, public Base /* Base被final修飾, 不能再作為基類被繼承 編譯會失敗*/
23 {
24 public:
25     B() = default;
26     virtual ~B() {  std::cout << "~B" << std::endl; }
27     virtual void func1() override { std::cout << "B::func1" << std::endl;   }
28     //virtual void func2() override {   std::cout << "B::func2" << std::endl;   } /* 基類已經禁止重載 編譯會失敗 */
29     //virtual void func3() override {   std::cout << "B::func3" << std::endl;   } /* 基類沒有該函數 編譯會失敗 */
30 
31 };
32 
33 int main(void)
34 {
35     int override = 20;
36     int final;
37     final = 30;
38     std::cout << "override: " << override << std::endl;
39     std::cout << "final: " << final << std::endl;
40     A *a = new B();
41     a->func1();
42 
43     delete a;
44     a = nullptr;
45 
46     return 0;
47 }

 

通過上面的例子我發現final和override還可以被定義成變量.

使用override和final 有利於增強可讀性, 定義和派生抽象類的利器. 它們在除了特定上下文中有特別的含義外, 仍然可以作為合法的標識符使用.

 

1.10 static_assert

靜態斷言, 編譯時的斷言檢查. 如果為假, 編譯器會打印錯誤信息.

下面列舉幾個使用的例子:

 1 #include <type_traits>
 2 
 3 class A
 4 {
 5 public:
 6     A() {}
 7     virtual ~A() {}
 8 private:
 9 };
10 
11 class B
12 : public A
13 {
14 public:
15     B() {}
16 };
17 
18 
19 int main(void)
20 {
21     /* 若B空類類型(無異於0大小位域的非靜態數據成員, 無虛函數, 無虛基類, 且無非空基類和非聯合類類型), 則為true */
22     static_assert(std::is_empty<B>::value, "error..."); /* 是否是空類 */
23 
24     /* 若B派生自A 或 為同一非聯合類, 則為true */
25     static_assert(std::is_base_of<A, B>::value, ".......error........");
26 
27     /* POD 類型為true */
28     static_assert(std::is_pod<B>::value, ".......error........");
29 
30     /* 
31      * 是否是整數類型 bool 、 char 、 char8_t 、 char16_t 、 char32_t 、
32      * wchar_t 、 short 、 int 、 long 、 long long 類型,或任何實現定義
33      * 的擴展整數類型,包含任何有符號、無符號及 cv 限定的變體。則提供等
34      * 於 true
35      * */
36     static_assert(std::is_integral<int>::value, ".......error........");
37 
38     /* ... ... */
39 
40     return 0;
41 }

 

1.11 自定義字面值后綴

后綴名盡量以下划線開頭(否則會報警告, 可能有些編譯器會報錯)

后座的參數只能是unsigned long long, long double, const char*或者const char* + size_t

 1 #include <iostream>
 2 
 3 unsigned long long operator"" _god(unsigned long long n)
 4 {
 5     return n * 100;
 6 }
 7 
 8 int main(void)
 9 {
10     auto n = 3_god;
11     std::cout << "n: " << n << std::endl;
12 
13     return 0;
14 }

 

使用不當會降低代碼的可讀性和可維護性

 

1.12 新的字符類型和字符串字面值定義

新增char16_t 和 char32_t 兩種類型. 

新增 utf-8(u8), utf-16(u), utf-32(U)字符串字面值定義方法.

同時新增了對 引號(")和反斜杠(\)等特殊字符無需轉換的裸字符串字面值定義方法(R), 該方法還可以與utf字面值前綴接合使用(u8R, uR, UR)

 1 #include <iostream>
 2 
 3 int main(void)
 4 {
 5     char16_t ch1;
 6     char32_t ch2;
 7 
 8     std::cout << u8"hello world" << std::endl;
 9     std::cout << u"hello world" << std::endl;
10     std::cout << U"hello world" << std::endl;
11     std::cout << R"("hello  world")" << std::endl;
12     std::cout << u8R"(hello"" \   world)" << std::endl;
13 
14     return 0;
15 }

 

1.13 模板的可變參數

為模板方法提供了類似普通函數的可變參數(...)支持.

 1 #include <iostream>
 2 
 3 void f()
 4 {
 5     std::cout << "f()" << std::endl;
 6 }
 7 
 8 template <typename T, typename... Args>
 9 void f(T t, Args... args)
10 {
11     /* 打印可變參數的長度(參數的個數) */
12     std::cout << "args: " << sizeof...(args) << std::endl;
13 
14     f(args...); /* 傳入可變參數 */
15     return;
16 }
17 
18 int main(void)
19 {
20     f(1, 2, 3, "he", "");
21     return 0;
22 }

 

1.14 改進大於號解析

不必擔心模板定義中的 ">>" 被解析成右移操作.

1 #include <vector>
2 
3 int main(void)
4 {
5     std::vector<std::vector<int>> vec1;  /* c++11支持 */
6     std::vector<std::vector<int> > vec2;
7     return 0;
8 }

 

1.15 lambda 表達式

[capture list] (parameter list) mutable或exception聲明 ->return type {function body};
  • [capture list]

按值傳遞的參數默認是const的, 不可修改.

空 :   [] 沒有任何capture

= : 函數體內可以使用lambda所在范圍內所有可見的局部變量(包括lambda所在類的this), 並且是值傳遞方式.

& : 函數體內可以使用lambda所在范圍內所有可見的局部變量(包括lambda所在類的this), 並且是引用傳遞方式.

this :函數體內可以使用lambda所在類中的成員變量

x :將x按值進行傳遞(默認是const, 不可修改)

&x : 將x按引用進行傳遞.

x, &y : 將x按值傳遞, b按引用傳遞.

=, &x, &y : 除x和y按引用傳遞外, 其它參數都按值進行傳遞.

&, x, y : 除x和y按值進行傳遞外, 其它參數都按引用進行傳遞.

 

  • (parameter list)

如同普通函數的參數列表.

 

  • mutable 或 exception 聲明

這部分可以省略. 按值傳遞capture list時, 加上mutable修飾符后, 可以修改傳遞進來的拷貝(只是修改拷貝的副本, 而不是修改本身). 

exception聲明用於指定函數拋出的異常, 如拋出整數類型的異常, 可以使用throw(int).

 

  • ->return type

標識函數返回值的類型, 當返回值為void, 或者函數體中只有一處return的地方(此時編譯器可以自動推斷出返回值類型)時, 這部分可以省略.

 

  • {function body}

函數的實現, 函數體可以為空.

 

下面是對lambda表達式的簡單使用:

 1 #include <iostream>
 2 
 3 int main(void)
 4 {
 5     int n = 5;
 6     int m = 5;
 7 
 8     auto f1 = [m]() mutable {   return ++m; };
 9     std::cout << "f1: " << f1() << std::endl;
10     std::cout << "m: " << m << std::endl;
11 
12     auto f2 = [&n]() {  return ++n; };
13     std::cout << "f2: " << f2() << std::endl;
14     std::cout << "n: " << n << std::endl;
15 
16     auto f3 = [=](char x) mutable ->int {   m = x; return m;    };
17     std::cout << "f3: " << f3('3') << std::endl;
18     std::cout << "m: " << m << std::endl;
19 
20 
21     return 0;
22 }

 

一個沒有指定任何捕獲的lambda函數, 可以顯式的轉換成一個具有相同聲明形式的函數指針.

1 int main(void)
2 {
3     auto f4 = [](int x) {   return ;    };
4     void (*func_ptr)(int) = f4;
5 
6     return 0;
7 }

 

  注意: 使用引用捕獲時, 若引用的是函數體內的局部變量, 那么lambda的生命周期不要超過所在函數體的生命周期.

 

1.16 右值引用和移動構造語義

右值引用解決了左值引用無法傳遞臨時對象和常引用傳遞的對象只讀的問題. 右值引用允許傳遞一個可變的臨時對象引用.

移動構造使用移動而非賦值語義完成構造過程, 主要用於解決函數返回值時的大量深拷貝開銷.

 

不能將一個右值引用直接綁定到一個變量上, 即使這個變量是右值引用類型也不行.

下面是對右值引用使用簡單的例子: 

 1 #include <utility>
 2 
 3 int main(void)
 4 {
 5     int a = 30;
 6     int &b = a;
 7     //int &&c = a;          /* error 右值引用不能綁定到左值 */
 8     //int &d = a * 2;       /* erro   a * 2 是一個右值 */
 9     const int e = a * 2;    /* const 的引用可以綁定到一個右值 */
10     int && f = a * 2;       /* 右值引用可以綁定到右值 */
11     //int && g = f;         /* error 不能將一個右值引用綁定到一個變量上, 即使這個變量是右值引用類型也不行 */
12     int && h = std::move(f); /* ok */
13 
14 
15     return 0;
16 }

 

1 int r1 = 20;
2 int &&r2 = std::move(r1);

 

調用move就意味着承諾: 除了對r1賦值或銷毀它以外, 將不能再使用它.

 

 1.17 標准庫函數begin, end 和 容器內的cbegin, cend

 標准庫的begin, end和容器中的成員begin, end功能相似,

 正確使用begin和end是將數組作為它們的參數. 可以方便的獲取數組的尾指針和頭指針.

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 #include <iostream>
 5 #include <iterator>
 6 
 7 int main(void)
 8 {
 9      char s1[] = "hello world";
10      const char *psbeg = std::begin(s1);
11      char *psend = std::end(s1);
12      printf("psbeg:\t%p\ns1:\t%p\n", psbeg, s1);
13      /* end返回的是數組結尾的下一個地址 */
14      printf("psend - 1:\t%p\ns1[strlen(s1)]:\t%p\n", psend - 1, &s1[strlen(s1)]);
15 
16     return 0;
17 }

 

 

容器中新增了cbegin和cend, 類似容器中的begin, end返回指示容器第一個元素或最后一個元素下一個的迭代器. 不同的是不論容器對象本身是否是常量, cbegin, cend返回的是const_iterator.

1 #include <vector>
2 
3 int main(void)
4 {
5     std::vector<int> vec;
6     std::vector<int>::const_iterator it = vec.cbegin();
7 
8     return 0;
9 }

 

 

1.18 array 和 forward_list

  • array是固定大小的序列容器, 它包含以嚴格線性序列排序的特定數量的元素. 不通過分配器管理其元素的分配. 成員方法和vector容器有諸多類似之處. 就不再一一闡述了.
  • forward_list 是一個單向鏈表.
    • 無法使用反向迭代器. 只能從它得到const或non-const前項迭代器, 這些迭代器都不能解引用, 只能自增;
    • 沒有可以返回最后一個引用的成員函數back();
    • 因為只能通過自增前面元素的迭代器來到達序列的終點, 所以push_back(), pop_back(), emplace_back()也無法使用.

forward_list的操作要比list容器更快, 而且占用更少的內存.

 

下面是array簡單使用的例子:

 1 #include <iostream>
 2 #include <array>
 3 
 4 int main(void)
 5 {
 6     std::array<int, 5> arr1 = {1, 2};
 7 
 8     std::cout << sizeof(arr1) << std::endl;
 9     std::cout << arr1.size() << std::endl;
10     std::cout << "arr[1]: " << arr1[100] << std::endl;
11     for (auto &val : arr1)
12     {
13         std::cout << "val: " << val << std::endl;
14     }
15 
16     return 0;
17 }

 

 

1.19 string 和數值之間的轉換

string和數值轉換  轉換類型
to_string(val) 把val轉換成string
stoi(s, p, b) 返回s的起始子串(表示整數內容)的數值, 返回值類型分別是int, long, unsigned long, long long, unsigned long long. b 表示轉換所用的基數, 默認值是10(十進制). p是size_t指針, 用來保存s中第一個非數值字符的下標, p默認為0(不保存下標)
stol(s, p, b)
stoul(s, p, b)
stoll(s, p, b)
stoull(s, p, b)
stof(s, p) 返回s的起始子串(表示浮點數內容)的數值, 返回值類型分別是float, double, long double. 參數p的作用和整數轉換函數一樣.
stod(s, p)
stold(s, p)

 

 

 

 

 

 

 

 

 

下面是簡單使用例子:

 1 #include <iostream>
 2 #include <string>
 3 
 4 int main(void)
 5 {
 6     int a1 = 100;
 7     std::string s1 = std::to_string(a1);
 8 
 9     a1 = std::stoi("200", nullptr, 10);
10     int a2 = std::stoi("30");            /* 使用默認的p和b, 可以省略不寫 */
11 
12     std::cout << "s1: " << s1 << std::endl;
13     std::cout << "a1: " << a1 << std::endl;
14     std::cout << "a2: " << a2 << std::endl;
15 
16     return 0;
17 }

 

 

1.20 無序關聯容器

unordered_set, unordered_map, unordered_multiset, unordered_multimap

它們分別對應有序關聯容器set, map, multiset, multimap.

這四個無序關聯容器是使用哈希函數和關鍵字類型的==運算符來組織元素的.

除了哈希管理操作之外, 無序關聯容器提供了與有序容器相同的操作(find, insert等), 意味着可以像操作set, map一樣操作unordered_set, unordered_map

通常情況下, 無序關聯容器可以獲得更好的性能, 但也會占用更多的內存.

 

1.21 正則表達式 regex

正則表達式庫非常的強大, 用於字符串匹配非常的方便. 這里只簡單介紹c++中的使用.

正則表達式庫組件
regex 表示有一個正則表達式類
regex_match 將一個字符序列與一個正則表達式匹配
regex_search 尋找第一個與正則表達式匹配的子序列
regex_replace 使用給定格式替換一個正則表達式
sregex_iterator 迭代適配器, 調用regex_search來遍歷一個string中所有匹配的子串
smatch 容器類, 保存在string中搜索的結果
ssub_match string中匹配的子表達式的結果

 

 

 

 

 

 

 

 

 

 

 

 1 #include <iostream>
 2 #include <string>
 3 #include <regex>
 4 
 5 int main(void)
 6 {
 7     std::string str1[] = {"foo.txt", "b.txt", "abcd.txt", "txt", "a0.txt", "AAA.txt", "wt.txt"};
 8 
 9     std::regex txtRegex("[a-z]+\\.txt");
10 
11     std::regex baseRegex("([a-z]+)\\.txt");
12     std::smatch baseMatch;
13     for (const auto &s : str1)
14     {
15         if (std::regex_match(s, baseMatch, baseRegex))
16         {
17             std::cout << "baseMatch[0]: " << baseMatch[0] << std::endl;
18             std::cout << "baseMatch.szie: " << baseMatch.size() << std::endl;
19             if (baseMatch.size() == 2)
20             {
21                 std::string str = baseMatch[0].str();
22                 std::string base = baseMatch[1].str();
23                 std::cout << "str: " << str << std::endl;
24                 std::cout << s << " : " << base << std::endl;
25             }
26         }
27     }
28 
29     return 0;
30 }

 

 

1.22 noexcept異常說明 和 noexcept運算符

  noexcept說明符:

  • 知道函數不會拋出異常有助於簡化調用該函數的代碼.
  • 如果編譯器確認該函數不會拋出異常, 它就能執行某些特殊的優化操作, 而這些優化操作並不適合可能出錯的代碼.

 

noexcept說明符出現的位置:

  • 函數的尾置返回類型之前. 
  • 函數指針的聲明和定義中指定noexcept
  • 在成員函數中, noexcept說明符需要跟在const及引用限定符之后, 而在final, override或虛函數的=0 之前.

noexcept運算符:

返回值是一個bool類型的右值常量表達式, 用於表示給定的表達式是否會拋出異常. 和sizeof類似, noexcept也不會求其運算對象的值.

 1 #include <iostream>
 2 
 3 class Base
 4 {
 5 public:
 6     void func1() const noexcept {   return; }
 7     virtual void func2() {  return; }
 8     virtual void func3() noexcept = 0;
 9     virtual void func4() noexcept final {   return; }
10 };
11 
12 class Derive
13 : public Base
14 {
15 public:
16     virtual void func2() noexcept override {    return; }
17     virtual void func3() noexcept override {    return; }
18 };
19 
20 void f1() noexcept {    return; }
21 void f2() { return; }
22 
23 int main(void)
24 {
25     std::cout << noexcept(f1()) << std::endl;
26     std::cout << noexcept(f2()) << std::endl;
27     Derive d;
28     std::cout << noexcept(d.func2()) << std::endl;
29     std::cout << noexcept(2) << std::endl;
30 
31     return 0;
32 }

 

 

1.23 繼承的構造函數

  • 派生類能夠重用其直接基類定義的構造函數. 一個類只初始化它的直接基類, 出於同樣的原因, 一個類也只能繼承其直接基類的構造函數. 類不能繼承默認, 拷貝和移動構造函數. 如果派生類沒有定義這些構造函數, 則編譯器將為派生類合成它們.
  • 和普通的using聲明不一樣, 一個構造函數的using聲明不會改變該構造函數的訪問級別(不管using聲明出現在哪里, 基類的私有構造函數在派生類中還是私有構造函數; 受保護和公有的構造函數也是同樣的規則)
  • 一個using聲明語句不能指定explicit或constexpr. 如果基類的構造函數是explicit或constexpr, 則繼承的構造函數也擁有相同的屬性.
  • 當一個基類構造函數含有默認實參時, 這些實參並不會被繼承. 相反, 派生類將獲得多個繼承的構造函數, 其中每個構造函數分別省略掉一個含有默認實參的形參. 例如: 如果基類有一個接受兩個形參的構造函數, 其中第二個形參含有默認實參, 則派生類將獲得兩個構造函數: 一個構造函數接受兩個形參(沒有默認實參), 另一個構造函數只接收一個形參, 它對應於基類中最左側的沒有默認值的那個形參.
  • 如果基類含有多個構造函數, 則除了兩個例外情況, 大多數時候派生類會繼承所有這些構造函數.
    • 第一個例外是派生類可以繼承一部分構造函數, 而為其它構造函數定義自己的版本.  如果派生類定義的構造函數與基類的構造函數具有相同的參數列表, 則該構造函數將不會被繼承. 定義在派生類中的構造函數將替換繼承而來的構造函數.
    • 第二個例外是默認, 拷貝和移動構造函數不會被繼承.  這些構造函數按照正常規則被合成. 繼承的的構造函數不會被作為用戶定義的構造函數來使用, 因此, 如果一個類只含有繼承的構造函數, 則它也將擁有一個合成的默認構造函數.

 

如果從多個基類中繼承了相同的構造函數(形參列表完全相同), 這程序會產生錯誤, 這個類必須為該構造函數定義它自己的版本.

 1 #include <string>
 2 
 3 class Base1
 4 {
 5 public:
 6     Base1() = default;
 7     Base1(const std::string&)   {};
 8 };
 9 
10 class Base2
11 {
12 public:
13     Base2() = default;
14     Base2(const std::string&)  {};
15 };
16 
17 class Derive
18 : public Base1
19 , public Base2
20 {
21 public:
22     using Base1::Base1; /* 從Base1繼承構造函數 */
23     using Base2::Base2; /* 從Base2繼承構造函數 */
24     Derive() = default;
25     /*
26      * 因為從多個基類繼承了相同的構造函數,
27      * 如果不定義一個可以接受string的構造函數, 則會報錯
28      * */
29     Derive(const std::string&s): Base1(s), Base2(s) {}
30 };
31 
32 int main(void)
33 {
34     return 0;
35 }

 

 

1.24 智能指針

  • shared_pt 基於引用計數的智能指針, 會統計當前有多少個對象同時擁有該內部指針; 當引用計數降為0時, 自動釋放.
  • weak_ptr 一種弱引用, 指向shared_ptr所管理的對象; weak_ptr只引用, 不計數.
  • unique_ptr 獨占語義的智能指針, 資源只能唯一的被一個unique_ptr所占有, 當其離開作用域時自動析構.  

 

shared_ptr和unique_tr都支持的操作

shared_ptr<T> sp

unique_prt<T> up

空智能指針, 可以指向類型為T的對象.
p 將p用作一個條件判斷, 若p指向一個對象, 則為true
*p 解引用p, 獲得它指向的對象
p->mem 等價於(*p).mem
pget() 返回p中保存的指針. 

swap(p, q)

p.swap(q)

交換p和q中的指針

p.reset()

shared_ptr將引用計數遞減, unique_ptr將釋放p指向的對象

p.reset(q)

p.reset(nullptr)

shared_ptr將引用計數遞減, unique_ptr將釋放p所指的對象, 如果提供了內置指針q, 則將p指向這個對象.

 

 

 

 

 

 

 

 

 

 

 

 

shared_ptr的操作
make_shared<T>(args) 返回一個shared_ptr, 指向一個動態內存分配的類型為T的對象. 使用args初始化此對象
shared_ptr<T>p(q) p是shared_ptr q的拷貝; 此操作會遞增q中的計數器. q中的指針必須能轉換為T*
p = q p和q都是shared_ptr, 所保存的指針必須能相互轉換. 此操作會遞減p的引用計數, 遞增q的引用計數;
p.unique()

若p.use_count()為1, 返回true; 否則返回false

p.use_count() 返回與p共享對象的智能指針數量(引用計數值)

 

 

 

 

 

 

 

 

unique_ptr的操作
unique_ptr<T, D> u(d) 空unique_ptr, 指向類型為T的對象, 用類型為D的對象d代替delete
u = nullptr 釋放u指向的對象, 將u置為空
u.release() u放棄對指針的控制權, 返回指針, 並將u置為空

 

 

 

 

 

 

 

weak_ptr的操作
weak_ptr<T> wp 空的weak_ptr 可以指向類型為T的對象
weak_ptr<T> wp(sp) 與shared_ptr sp指向相同對象的weak_ptr. T必需能轉換成sp指向的類型
wp = p p可以指向一個shared_ptr或weak_ptr. 賦值后wp 與 p共享對象
wp.reset() 將wp置為空
wp.use_count()

與wp共享對象的shared_ptr的數量

wp.expired() 若wp.use_count()為0, 返回true, 否則返回false
wp.lock() 如果expired為true, 返回一個空的shared_ptr; 否則返回一個指向wp的對象的shared_ptr

 

 

 

 

 

 

 

 

shared_ptr使用注意事項:

  • 不使用相同的內置指針初始化(或reset)多個智能指針.
  • 不delete get() 返回的指針
  • 不使用get()初始化或reset另一個智能指針
  • 如果使用了get()返回的指針, 記住當最后一個對應的智能指針銷毀后, 這個指針就變為了無效指針了.
  • 如果使用智能指針管理的資源不是new分配的內存, 記住傳遞給它一個刪除器.

 

下面是使用示例:

 1 #include <memory>
 2 
 3 class Dealloc
 4 {
 5 public:
 6     Dealloc() = default;
 7 
 8     //括號()操作符的重載
 9     void operator() (int* p )
10     {
11         if (p != nullptr)
12         {
13             delete p;
14             p = nullptr;
15         }
16     }
17 };
18 
19 void shared_ptr_func(void)
20 {
21     //1. 創建
22     std::shared_ptr<int> sp0; /* 空智能指針 */
23     std::shared_ptr<int> sp1 = std::shared_ptr<int>(new int(100));
24     std::shared_ptr<int> sp2 = std::make_shared<int>(int(10));
25 
26     //2. 自定義資源釋放函數
27     {
28         auto sp4 = std::shared_ptr<int>(new int[5], [](int *p){ delete[ ] p; });
29     }
30     {
31         // 括號()操作符的重載
32         auto sp5 = std::shared_ptr<int>(new int(1000), Dealloc() );
33     }
34 
35     //3. 復制
36     auto sp6(sp1);
37     auto sp7 = sp1;
38 
39     //4. 獲取保存的指針
40     int* pRaw = sp2.get();
41 
42     //5. 返回共享對象的智能指針數量.
43     long nCount1 = sp1.use_count();
44     long nCount2 = sp2.use_count();
45 
46     //6. 是否是獨享
47     bool b1 = sp1.unique();
48     bool b2 = sp2.unique();
49 
50     //7. swap
51     sp1.swap(sp2);
52 
53     //8. reset 重置指向另一個對象, 原指針引用計數減一
54     sp1.reset(new int(20));
55 }
56 
57 void del(int *p) { delete p; }
58 
59 void unique_ptr_func(void)
60 {
61     std::unique_ptr<int> up0;
62     std::unique_ptr<int> up1(new int(20));
63     //std::unique_ptr<int> up2(up1); /* 不支持拷貝 */
64     std::unique_ptr<int> up2(up1.release());
65 
66     up2.reset(up0.release());
67 
68     //up0 = up1; /* error 不支持賦值 */
69 
70     std::unique_ptr<int, decltype(del) *> up3(new int(20), del);
71 }
72 
73 void weak_ptr_func()
74 {
75     std::shared_ptr<int> sp0 = std::make_shared<int>(int(10));
76     std::weak_ptr<int> wp0(sp0); /* 不會改變sp0的引用計數 */
77 
78     bool b1 = wp0.expired();
79     std::shared_ptr<int> sp1 = wp0.lock();
80     std::weak_ptr<int> wp1 = wp0.lock();
81 }
82 
83 int main(void)
84 {
85     shared_ptr_func();
86     unique_ptr_func();
87     weak_ptr_func();
88     return 0;
89 }

 

 

 

 

 

 

 

 

 

參考

[1] 自由真實個性 https://www.cnblogs.com/KunLunSu/p/7861330.html

[2] 白楊 http://www.baiy.cn/doc/cpp/comments_of_cxx0x.htm

[3] jiange_zh https://blog.csdn.net/jiange_zh/article/details/79356417

[4] DoubleLi  https://www.cnblogs.com/lidabo/p/7241381.html

[5] c++ primer 第五版

[6] 季末的天堂 https://www.cnblogs.com/jimodetiantang/p/9016826.html

[7] c++參考手冊 https://zh.cppreference.com

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM