第1章 新標准的誕生
1.1 曙光:C++11標准的誕生
1.2 今時今日的C++
1.3 C++11特性的分類
1.4 C++特性一覽
1.5 本書的約定
第2章 保證穩定性和兼容性
2.1 保持與C99兼容
1、__func__:返回所在函數的名字
2、_Pragma操作符:
#pragma是一條預處理指令。C++11定義了與預處理指令#pragma功能相同的操作符_Pragma。格式如下:_Pragma(字符串字面量)。
#pragma不能在宏中展開,而_Pragma具有更大的靈活性。
3、變長參數的宏定義以及__VA_ARGS__:
#define PR(...) printf(__VA_ARGS__)
4、在C++11標准中,窄字符串可以轉換成寬字符串。但是之前標准中,將窄字符串轉換成寬字符串是未定義的行為。
2.2 long long整型
1、使用LL后綴表示一個long long類型的字面量,ULL表示一個unsigned long long類型的字面量。
2.3 擴展的整型
標准的有符號整型:signed char、short int、int、long int、long long int
2.4 宏__cplusplus:C與C++混合編寫
在C++03標准中,__cplusplus的值為199711L,在C++11標准中,__cplusplus為201103L。
#error:
2.5 靜態斷言
assert:只在程序運行時才能起作用。
static_assert:斷言表達式的結果必須是在編譯時期可以計算的表達式,即必須是常量表達式。
2.6 noexcept修飾符與noexcept操作符
noexcept:表示其修飾的函數不會拋出異常。如果noexcept修飾的函數拋出了異常,編譯器直接調用std::terminate()函數來終止程序。比throw()在效率上高。
void func() noexcept;
void func() noexcept(常量表達式); //常量表達式的結果會被轉換成一個bool類型的值。該值為true,表示函數不會拋出異常,反之,則有可能拋出異常。
2.7 快速初始化成員變量
2.8 非靜態成員的sizeof
在C++08標准中,對非靜態成員變量使用sizeof是不能通過編譯的,但是C++11可以。
2.9 擴展的friend語法
1、在C++11中,聲明一個類為另外一個類的友元時,不再需要使用class關鍵字。
2.10 final/override控制
1、final關鍵字阻止函數繼續重寫。
2、如果派生類在虛函數聲明時使用了override描述符,那么該函數必須重載其基類中的同名函數,否則代碼將無法通過編譯。
2.11 模板函數的默認模塊參數
2.12 外部模板
template void fun<int>(int); //顯示實例化
extern template void fun<int>(int); //外部模板聲明
2.13 局部和匿名類型作模板實例
1、局部的類型和匿名的類型在C++98中不能做模板類的實參。
2、不能將匿名的結構體直接聲明在模板實參的位置。
2.14 本章小結
第3章 通用為本,專用為末
3.1 繼承構造函數
struct B : A {
using A::A; //繼承構造函數
}
3.2 委派構造函數
struct A {
A(){}
A(int i):A(){}
}
3.3 右值引用:移動語義和完美轉發
1、指針成員與拷貝構造
2、移動語義:TODO:p87
3、
左值:可以取地址的、有名字的
右值:不能取地址的、沒有名字的
4、std::move:強制轉化為右值
#include <utility>
5、盡量編寫不拋出異常的移動構造函數,可以使用noexcept關鍵字。可以用std::move_if_noexcept代替move函數。
RVO/NRVO優化(返回值優化):
6、完美轉發:在函數模板中,完全按照模板的參數類型,將參數傳遞給函數模板中調用的另外一個函數。
template <typename T>
void PerfectForwart(T && t) { RunCode(forward<T>(t)}; }
3.4 顯示轉換操作符
1、explicit:聲明為explicit的構造函數不能在隱式轉換中使用。
3.5 列表初始化
1、初始化列表:{}
2、在C++11中,列表初始化是唯一一種可以防止類型收窄的初始化方式。
3.6 POD類型
1、好處:
字節賦值,可以安全使用memset和memcpy等操作;
提供對C內存布局兼容;
保證靜態初始化的安全有效
2、什么樣的是POD類型:(可以通過 std::is_pod 判斷)
平凡:(可以通過 std::is_trivial 判斷)
擁有平凡的默認構造函數和析構函數;(不包含參數,函數體沒有任何代碼)
擁有平凡的拷貝構造函數;
擁有平凡的拷貝賦值運算符和移動賦值運算符;
不能包含虛函數以及虛基類;
標准布局:(可以通過 std::is_standard_layout 判斷)
所有非靜態成員有相同的訪問權限;
在類或結構體繼承時,派生類中有非靜態成員且只有一個僅包含靜態成員的基類 或 基類有非靜態成員而派生類沒有非靜態成員;
類中的第一個非靜態成員的類型與其基類不同;
沒有虛函數和虛基類;
所有非靜態數據成員均符合標准布局類型,其基類也符合標准布局
3.7 非受限聯合體
在C++11中取消了聯合體對於數據成員類型的限制,任何非引用類型都可以成為聯合體的數據成員。
3.8 用戶自定義字面量
規則:
如果字面量為整型數,那么字面量操作符函數只能接受unsigned long long 或者 const char* 為其參數
如果字面量為浮點型數,則字面量操作符函數只能接受long double 或者 const char* 為參數
如果字面量為字符串,則字面量操作符函數只能接受const char*,size_t為參數
如果字面量為字符,則字面量操作符函數只能接受char為參數
注意:
在字面量操作符函數的生命中,operator""與用戶自定義后綴之間必須有空格
后綴建議以下划線開始
示例:
struct Watt{ unsigned int v; };
Watt operator "" _w(unsigned long long v) { return {(unsigned int)v}; }
int main() { Watt capacity = 1024_w; }
3.9 內聯名字空間
C++98不允許在不同的名字空間中對模板進行特化。
C++11引入內斂命名空間,可以解決該問題。(inline namespace)
3.10 模板的別名(using)
可以使用 std::is_same 判斷兩個類型是否一致。
3.11 一般化的SFINEA規則(匹配失敗不是錯誤:Substitution failure is not an error)
3.12 本章小結
第4章 新手易用,老兵易用
4.1 右尖括號>的改進
C++98會將>>優先解析為右移。在C++11中要求編譯器智能地判斷。
4.2 auto類型推導
1、優勢:
簡化代碼(在擁有初始化表達式的復雜類型變量聲明時)
避免類型聲明時的錯誤
在一定程度上支持泛型編程
2、auto可以與cv限制符一起用,不過聲明為auto的變量不能從其初始化表達式中“帶走”cv限制符。cv限制符:volatile(易失的)和const(常量)
3、以下情況不能使用auto:
不能做形參類型
對於結構體來說,非靜態成員變量的類型不能為auto
不能聲明auto數組
不能在實例化模板的時候使用auto昨模板參數,如vector<auto> v
4.3 decltype
1、C完全不支持動態類型,C++98對動態類型支持即C++中的運行時類型識別(RTTI)
2、typeid:返回類型為type_info,包含nameHe hash_code。除typeid外,RTTI還包括C++中的dynamic_cast等特性,由於RTTI會帶來一些運行時的開銷,所以建議關閉該特性。
3、decltype(e)推導規則:
如果 e 是一個沒有帶括號的標記符表達式或者類成員訪問表達式,那么decltype(e)就是e所命名的實體的類型。如果e是一個被重載的函數,則會導致編譯時錯誤。
否則,假設e的類型是T,如果e是一個將亡值(xvalue),那么decltype(e)為T&&。
否則,假設e的類型是T,如果e是一個左值,則decltype(e)為T&。
否則,假設e的類型是T,則decltype(e)為T。
標記符表達式:所有除去關鍵字、字面量等編譯器需要使用的標記之外的程序員自定義的標記。
4、decltype與auto不同,能“帶走”表達式的cv限制符。不過,如果對象的定義中有const或volatile限制符,使用decltype進行推導時,其成員不會繼承const或volatile限制符。
4.4 追蹤返回類型
示例:
template<typename T1, typename T2>
auto Sum(T1& t1, T2& t2) -> decltype(t1 + t2) { return t1 + t2; }
4.5 基於范圍的for循環
4.6 本章小結
第5章 提高類型安全
5.1 強類型枚舉
1、匿名枚舉:enum { Male, Female };
2、枚舉類缺點:非強類型作用域,允許隱式轉換為整型,占用存儲空間及符號性不確定
3、枚舉類(強類型枚舉):enum class Type {};
優勢:
強作用域,強類型枚舉成員的名稱不會被輸出到其父作用域空間
轉換限制,強類型枚舉成員的值不可以與整型隱式地互相轉換
可以指定底層類型。enum class Type : char {};
5.2 堆內存管理:智能指針與垃圾回收
1、顯示堆內存管理問題: 野指針、重復釋放、內存泄漏。
2、
unique_ptr:與所指對象的內存綁定緊密,不能與其他unique_ptr類型的指針對象共享所指對象的內存。
shared_ptr:允許多個智能指針共享地“擁有”同一堆分配對象的內存。
weak_ptr:可以指向shared_ptr指針指向的對象內存,卻並不擁有該內存。而使用wak_ptr成員lock,則可返回其指向內存的一個shared_ptr對象,且在所指對象內存已經無效時,返回指針空值。
3、垃圾回收
基於引用計數的垃圾回收器
基於跟蹤處理的垃圾回收器
4、安全派生指針的操作:
在解引用基礎上的引用,如:&*p
定義明確的指針操作,如:p+1
定義明確的指針轉換,如:static_cast<void *>(p)
指針和整型之間的轉換,如reinterpret_cast<intptr_t>(p)
5.3 本章小結
第6章 提高性能及操作硬件的能力
6.1 常量表達式
1、
const:運行時常量
constexpr:編譯時常量。可以在函數返回類型前加入constexpr使其成為常量表達式函數
常量表達式函數要求:
函數體只有單一的return返回語句
函數必須返回值
在使用前必須已有定義
return返回語句表達式中不能使用非常量表達式的函數、全局數據,且必須是一個常量表達式
2、常量表達式值
const int i = 1; // 如果i在全局名字空間中,編譯器一定會為i產生數據
constexpr int j = 1; // 如果不是有代碼顯示地使用了j的地址,編譯器可以不選擇為它生成數據,而僅將其當做編譯時期的值
3、constexpr不能修飾自定義類型,但是通過定義自定義常量構造函數可以。
實例:
struct MyType { constexpr MyType(int x) : i(x){} int i;};
constexpr MyType mt = {0};
4、常量表達式可以用於模板函數。由於模板中類型的不確定性,所以模板函數是否會被實例化為一個能夠滿足編譯時常量性的版本通常是未知的。C++11標准規定,當聲明為常量表達式的模板函數后,而某個該模板函數的實例化結果不滿足常量表達式的需求的話,constexpr會被自動忽略。
6.2 變長模板
template<typename... A> class Template: private B<A...>{};
6.3 原子類型與原子操作
內存模型:
memory_order_relaxed 不對執行順序做任何保證
memory_order_acquire 本線程中,所有后續的讀操作必須在本條原子操作完成后執行
memory_order_release 本線程中,所有之前的寫操作完成后才能執行本條原子操作
memory_order_acq_rel 同時包含memory_order_acquire好memory_order_release標記
memory_order_consume 本線程中,所有后續的有關本原子類型的操作,必須在本條原子操作完成之后執行
memory_order_seq_cst 全部存取都按順序執行
原子存儲操作(store):可以使用memory_order_relaxed、memory_order_release、memory_order_seq_cst
原子讀取操作(load):可以使用memory_order_relaxed、memory_order_consume、memory_order_acquire、memory_order_seq_cst
6.4 線程局部存儲
線程局部存儲變量:擁有線程生命期及線程可見性的變量。
int thread_local errcode; // 一旦聲明一個變量為thread_local,其值將在線程開始時被初始化,而在線程結束時,該值也將不再有效。
6.5 快速退出:quick_exit與at_quick_exit
terminate:終止程序。在默認情況下會調用abort函數。可以通過set_terminate函數來改變默認行為。
abort:不會調用任何的析構函數,默認情況下會拋出一個信號:SIGABRT。
exit:正常退出。會正常調用自動變量的析構函數,還會調用atexit注冊的函數。
quick_exit:不執行析構函數。
at_quick_exit:注冊函數。
6.6 本章小結
第7章 為改變思考方式而改變
7.1 指針空值-nullptr
nullptr是有類型的(nullptr_t),且僅可以被隱式轉化為指針類型。
nullptr是編譯時期的關鍵字。
7.2 默認函數的控制
1、默認函數:構造、拷貝構造、拷貝賦值、移動構造、移動拷貝、析構
2、=default 修飾的函數為顯示缺省函數,=delete 修飾的函數為刪除函數。
7.3 lambda函數
1、[捕捉列表](參數列表) 修飾符 ->返回類型{ 函數體 }
參數列表和返還類型是可選部分,捕捉列表和函數體可能為空。最簡略聲明為:[]{}
2、捕捉列表
[var]:值傳遞方式捕捉變量var
[=]:值傳遞方式捕捉所有父作用域的變量
[&var]:引用傳遞捕捉變量var
[&]:引用傳遞捕捉所有父作用域的變量
[this]:值傳遞方式捕捉當前的this指針
[=, a, &b]:以引用傳遞的方式捕捉變量a和b,值傳遞方式捕捉其他所有變量
[&, a, this]:以值傳遞的方式捕捉變量a和this,引用傳遞方式捕捉其他所有變量
但是:捕捉列表不允許變量重復傳遞。
7.4 本章小結
第8章 融入實際應用
8.1 對齊支持
1、offset:查看類成員的偏移。
alignof:操作符,查看數據的對齊方式
alignas:描述符,設定數據的對齊方式。可以接受常量表達式,也可以接受類型表達式做參數
std::align:庫函數
aligned_storage:模板
aligned_union:模板
2、保證數據對齊是保證正確有效讀寫數據的一個基本條件。對齊的數據在讀寫上會有性能上的優勢。
8.2 通用屬性
1、屬性是通過GNU的關鍵字__attribute__來聲明的。如:__attribute__((attribute-list))。
windows評估,使用__declspec來擴展屬性。
2、C++11通用屬性:[[attribute-list]]。可以作用於類型、變量、名稱、代碼塊等。既可以寫在聲明的起始處,也可以寫在聲明的標識符之后。
3、C++11預定義的通用屬性包括[[noreturn]]和[[carries_dependency]]兩種。
[[noreturn]]:用於標識不會返回的函數。沒有返回值的void函數在調用完成后,調用者會接着執行函數后的代碼;而不會返回的函數在被調用完成后,后續代碼不會被執行。
[[carries_dependency]]:為了解決弱內存模型平台上使用memory_order_consume內存順序枚舉問題。
8.3 Unicode支持
1、C++98為了支持Unicode,定義了wchar_t,但是不同操作系統定義不一樣,不可移植。
C++11定義了:char16_t(存儲UTF-16編碼的Unicode數據)和char32_t(存儲UTF-32編碼的Unicode數據)
在聲明常量字符串時,定義了3種前綴:
u8:UTF-8
u:UTF-16
U:UTF-32
2、C11編碼轉換:mbrtoc16、c16rtomb、mbrtoc32、c32rtomb
C++11編碼轉換:codecvt
8.4 原生字符串字面量
在字符串前加入前綴(R),並在引號中使用括號左右標識即可。如 R"(hello,\n world)"
8.5 本章小結
附錄A C++11對其他標准的不兼容項目
1、在C++11中R、u8、u8R、u、uR、U、UR和LR是新的字符串修飾符,當用它們來修飾字符串時,即使是宏名,也將作為修飾符來解釋。
2、C++11要求數組初始化時,不能將數據的類型收窄。如下面代碼非法:int arr[]={1.0}
附錄B 棄用的特性
附錄C 編譯器支持
附錄D 相關資源
