C++11特性劃分爲七大類,分別是:python
1.保持語言的穩定性和兼容性ios
2.更傾向於通用的而不是特殊化的手段來實現特性c++
3.專家新手都支持程序員
4.加強類型的安全性算法
5.加強性能和操做硬件的能力編程
6.開發可以改變人們思惟方式的特性安全
7.融入編程現實ide
一、保持語言的穩定性和兼容性函數
(1)新增關鍵字佈局
alignas alignofdecltype auto從新定義 static_assert using從新定義 noexcept export nullptr constexpr thread_local
(2)返回函數名字
const char* hello(){return __func__;};
(3)#pragma once 等價於
#ifndef THIS_HEADER #define THIS_HEADER //... #endif
(4)變長函數參數的宏定義,例子:
#define LOG(...) {\ fprintf(stderr, "%s: line %d: \t", __FILE__, __LINE__); \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n") ; \ }
使用方式:
LOG("x=%d", x)
注意加上編譯選項 g++ -std=c++11 xx.cpp
(5)__cplusplus 判斷編譯器類型, 其等於199711L(c++03) 201103L(c++11)
#if __cplusplus< 201103L #error "should use c++11 implementation" #endif
(6)運行時檢查用assert, 編譯時檢測用static_assert(1==1, "information")
在c++11以前能夠經過除0致使編譯器出錯的方式來模擬一個靜態斷言
#define assert_static(e) \ do{\ enum{assert_static__ == 1/(e)}; \ }while(0)
(7)noexcept
若是c++11中noexcept修飾的函數拋出了異常,編譯器能夠選擇直接調用std::terminate()來終止程序運行
c++11默認將delete函數設置成noexcept
類析構函數默認也是noexcept(false)的
這個用法能夠推廣到函數模板
template<class T> void fun() noexcept(noexcept( T() ) ) ){}
這樣能夠根據條件決定「函數是否拋出異常」
(8)快速初始化成員變量
能夠經過=、{}對非靜態成員儘快就地初始化,以減小多個構造函數重複初始化變量的工做,注意初始化列表會覆蓋就地初始化操做
class Group{ public: Group(){} Group(int a) : data(a) {} Group(Mem m) : mem(m) {} Group(int a, Mem m, string n) : data(a), mem(m), name(n) {} private: int data = 1; Memmem{0}; string name("Group"); };
(9) sizeof能夠對People::hand這樣的類的非靜態成員進行計算,而不用先新建一個對象People p了
(10)類模板能夠聲明友元了
template<typename T> class People[ friend T; }
(11)經過final和override讓編譯器輔助作一些函數是否禁止重載或函數是不是重載函數的判斷,
這樣作的目的是避免——類繼承者的編寫者有時想重載某個基類的函數,結果因爲參數寫錯或者函數名寫錯,致使意想不到的誤會
(12)c++98中函數有默認參數,模板類有默認模板參數,可是函數模板沒有,c++11增長了這一特性,但與類模板有區別
template<T1, T2=int> class xxx; //OK template<T1 = int, T2> class xxx2; //error template<T, int i=0> class xxx3; //OK template<inti=0, T> class xxx4; //error template<T1=int, T2> void fun(T1 a, T2 b); //OK template<inti=0, T> void fun2(T a); //OK
(13)外部模板加快編譯器編譯速度,以下(固然不加也無妨)
extern template void fun<int>(int);
二、更傾向於通用的而不是特殊化的手段來實現特性
(1)繼承構造函數
目的:因爲類構造函數是不可繼承的,爲了不派生類逐個實現基類的構造函數(透傳)
例子:
struct A{ A(int i){} A(double d, int i){} A(float f, int i, const char* c){} } struct B:A{ using A::A; }
等價於
struct B{ B(int i){} B(double d, int i ){} B(float f, int i, const char* c){} }
(2)委派構造函數
目的:減小多構造函數的類編寫時間,讓複雜的構造函數調用簡單的構造函數(通用函數),實現這個目的在之前一般採用一個通用的init的類成員函數來實現
例子:有點像變量初始化列表
class Info{ public: Info() {InitRest(); } Info(int i) : Info() {type = i;} Info(char e) : Info() {name = e;} private: void InitRest() { //其餘初始化操做} int type{1}; char name{'a'}; };
須要注意的是, : Info()不能與初始化列表一塊兒使用
以下面這段是錯誤的:
Info(int i) : Info(), type(i){}
(3)移動語義
目的:解決深拷貝問題,同時減小函數返回類對象時拷貝構造的次數,注意這裏移動構造函數只對臨時變量起做用,在以前的解決中都是經過手工定製拷貝構造函數來解決的
概念:
左值——能夠取地址的、有名字的就是左值,如 a = b+c中的a
右值——不能取地址的、沒有名字的就是右值,如a = b+c中的b+c
右值引用——就是對一個右值進行引用的類型,由於右值沒有名字,下面代碼是從右值表達式獲取其引用:
T && a = ReturnRvalue();
能夠理解左值引用是具備名字的變量名的別名,右值引用則是沒有名字的變量名的別名,因此必須當即初始化(如上)
在c++98中,常量左值引用(const T&) 是一個「萬能」的引用類型,它能夠接受很是量左值(T a),常量左值(const T a),右值(T fun() )進行初始化
使用常量左值引用能夠減小臨時對象的開銷,以下
struct Copyable { Copyable() {} Copyable(const Copyable& o) { cout<< "Copied" <<endl; } }; Copyable ReturnRvalue() { return Copyable(); } void AcceptVal(Copyable) {} void AcceptRef(const Copyable& cp) {}//Copyable c = std::move(cp);} void AcceptRRef(int&& i) {i+=3; cout<< (char)i<<endl;} int main() { cout<< "Pass by value: " <<endl; AcceptVal(ReturnRvalue()); // 臨時值被拷貝傳入 cout<< "Pass by reference: " <<endl; AcceptRef(ReturnRvalue()); // 臨時值被做爲引用傳遞 AcceptRRef('c'); // 臨時值被做爲引用傳遞 }
例子:
①移動構造函數
#include <iostream> using namespace std; class HasPtrMem { public: HasPtrMem(): d(new int(3)) { cout<< "Construct: " << ++n_cstr<<endl; } #if 0 //拷貝一分內存,並用h.d的值賦值 HasPtrMem(const HasPtrMem& h): d(new int(*h.d)) { cout<< "Copy construct: " << ++n_cptr<<endl; } #endif HasPtrMem(HasPtrMem&& h): d(h.d) { // 移動構造函數 h.d = nullptr; // 將臨時值的指針成員置空 cout<< "Move construct: " << ++n_mvtr<<endl; } ~HasPtrMem() { delete d; cout<< "Destruct: " << ++n_dstr<<endl; } int * d; static int n_cstr; static int n_dstr; static int n_cptr; static int n_mvtr; }; int HasPtrMem::n_cstr = 0; int HasPtrMem::n_dstr = 0; int HasPtrMem::n_cptr = 0; int HasPtrMem::n_mvtr = 0; const HasPtrMem GetTemp() { const HasPtrMem h; cout<< "Resource from " << __func__ << ": " << hex <<h.d<<endl; return h; } int main() { const HasPtrMem&& a = GetTemp(); //延長臨臨時變量的生命週期 cout<< "Resource from " << __func__ << ": " << hex <<a.d<<endl; return 0; }
②<utility>的函數std::move(T) 能夠調用類T的移動構造函數,來完成對象的深度拷貝,在類沒有定義移動構造函數的時候,會默認調用該類的拷貝構造函數
一個高性能的置換函數以下,調用時使用移動構造函數,移動賦值函數
class T { T(T&& t); //移動構造函數 T& operator=(T&& t); //移動賦值函數 T(const T& t); //拷貝構造函數 T& operator=(const T& t); //賦值函數 } template<class T> void swap(T& a, T& b) { T tmp(move(a)); a = move(b); b = move(tmp); }
(4)完美轉發
目的:完美轉發的意思就是在函數模板中,徹底依照模板的參數的類型,將參數傳遞給函數模板中調用的另一個函數,如:
template<typename T> void Iamforwording(T t){ IrunCodeActually(t); }
爲了使這個函數可以支持左值引用和右值引用, c++11裏面定義了新的引用摺疊規則(屬於模板推導規則的一部分),詳細的推導規則能夠參見《深刻理解c++11》一書P86的表3-2(屬於模板的推導規則之一)
簡單例子以下:
void IamForwording(X&&& t){ IrunCodeActually(static_cast<X&&&>(t)); }
應用引用摺疊規則,則是
void IamForwording(X& t){ IrunCodeActually(static_cast<X&>(t)); }
例子:
void RunCode(int&& m) { cout<< "rvalue ref" <<endl; } void RunCode(int& m) { cout<< "lvalue ref" <<endl; } void RunCode(const int&& m) { cout<< "constrvalue ref" <<endl; } void RunCode(const int& m) { cout<< "constlvalue ref" <<endl; } template<typename T> void PerfectForward(T&& t) { RunCode(forward<T>(t)); } int main() { int a; int b; const int c = 1; const int d = 0; PerfectForward(a); // lvalue ref PerfectForward(move(b)); // rvalue ref PerfectForward(c); // constlvalue ref PerfectForward(move(d)); // constrvalue ref }
(5)初始化列表
容許如下的初始化代碼,而c++98只能經過第一行的編譯
int a[] = {1,2,3}; int b[]{1,2,3}; vector<int> c{1,2,3}; map<int, float> d= {{1,1.0f}, {2, 2.0f}};
總結起來,如下初始化結果是等價的
int a= 3+4; int a= {3+4}; int a(3+4); int a{3+4}; int *pa = new int(3+4);
初始化列表也能夠用於類的構造函數
enum Gender {boy, girl}; class People { public: People(initializer_list<pair<string, Gender>> l) // initializer_list的構造函數 { autoi = l.begin(); for (;i != l.end(); ++i) data.push_back(*i); } private: vector<pair<string, Gender>> data; }; People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};
(6)POD(plain old data)類型
POD一般是用戶自定義的普通類型,體現了與C的兼容性,能夠用最老的memcpy進行復制,能夠用memset進行初始化
c++11將POD劃分爲兩個基本概念的合集,即:平凡的(trivial) 和 標準佈局的(standard layout)
平凡的類或結構體應符合如下定義:
①擁有平凡的默認構造函數和析構函數,一旦定義了構造函數,即便是一個無參數的空函數,該構造函數也再也不是的平凡的。
②擁有平凡的拷貝構造函數和移動構造函數
③擁有平凡的拷貝賦值運算符和移動賦值運算符
④不能包含虛函數以及虛基類
能夠經過
cout<<is_trivial<sturct XXX>::value <<endl
來檢驗,頭文件<type_traits>
標準佈局的類或結構體應符合如下定義:
①全部非靜態成員有相同的訪問權限
②在類或者結構體繼承時,知足如下條件之一:
*派生類中有非靜態成員,且只有一個僅包含靜態成員的基類
*基類有非靜態成員,而派生類沒有非靜態成員
③類中第一個非靜態成員的類型與其基類不一樣
④沒有虛函數和虛基類
⑤全部非靜態數據成員均符合標準佈局類型,其基類也符合標準佈局
能夠經過
cout<<is_standard<sturct XXX>::value<<endl
來檢驗,
頭文件<type_traits>
(7)用戶自定義常量
struct Watt{ unsigned int v; }; Watt operator "" _w(unsigned long long v) { return {(unsigned int)v}; } int main() { Watt capacity = 1024_w; }
(8)模板別名
typedef unsignd int UINT 等價於 using UINT = unsigned int 一樣適用於模板
template<typename T> using MapString = std::map<T, char*> MapString<int> test;
三、專家新手都支持
(1)右尖括號改進, Y<X<1>>再也不編譯失敗了。
(2)auto類型推導
從新定義了auto的語意,經過編譯器實現對類型的自動推導,使c++有點相似python等動態類型語言
簡單例子:
int main() { double foo(); auto x = 1; // x的類型爲int auto y = foo(); // y的類型爲double struct m { int i; }str; auto str1 = str; // str1的類型是struct m auto z; // 沒法推導,沒法經過編譯 z = x; }
做用以下:
①簡化代碼
std::vector<std::string> t; for(auto i=t.begin(); i<t.end(); i++){}
②免除類型聲明麻煩
class PI { public: double operator* (float v) { return (double)val * v; // 這裏精度被擴展了 } const float val = 3.1415927f; }; int main() { float radius = 1.7e10; PI pi; auto circumference = 2 * (pi * radius); }
③跨平臺
④模板推導,須要配合decltype
template<typename T1, typename T2> auto Sum(T1& t1, T2& t2) ->decltype(t1+t2){ return t1+t2; }
⑤用在宏定義上,避免屢次變量展開
#define Max1(a, b) ((a) > (b)) ? (a) : (b) #define Max2(a, b) ({ \ auto _a = (a); \ auto _b = (b); \ (_a > _b) ? _a: _b; }) int main() { int m1 = Max1(1*2*3*4, 5+6+7+8); int m2 = Max2(1*2*3*4, 5+6+7+8); }
上述代碼避免了,1*2*3*4被屢次計算
(3)decltype,一樣也是類型推導,區別在於
decltype的類型推導並非像auto同樣是從變量聲明的初始化表達式得到變量的類型,其老是以一個普通表達式爲參數,返回該表達式的類型。
做用以下:
①增長代碼可讀性
vector<int> vec; typedef decltype(vec.begin()) vectype; vectype i; // 這是auto沒法作到的 for (i = vec.begin(); i<vec.end(); i++) { } for (decltype(vec)::iterator i = vec.begin(); i<vec.end(); i++) { }
②恢復匿名結構體
sturct{ int d; }ttt; decltype(ttt) ok;
③模板利器
// s的類型被聲明爲decltype(t1 + t2) template<typename T1, typename T2> void Sum(T1 & t1, T2 & t2, decltype(t1 + t2) & s) { s = t1 + t2; }
④基於decltype的result_of
#include <type_traits> using namespace std; typedef double (*func)(); int main() { result_of<func()>::type f; // 由func()推導其結果類型 }
⑤追蹤函數的返回類型
下面兩個代碼是等價的,第二個代碼可讀性稍好
int (* (*pf()) () ) () { return 0;} auto pf1() -> auto (*) () ->int (*) () { return 0; }
可用於函數轉發:
#include <iostream> using namespace std; double foo(int a) { return (double)a + 0.1; } int foo(double b) { return (int)b; } template<class T> auto Forward(T t) ->decltype(foo(t)){ return foo(t); } int main(){ cout<< Forward(2) <<endl; // 2.1 cout<< Forward(0.5) <<endl; // 0 }
推導法則:
首先定義標記符表達式:除去關鍵字、字面量等編譯器須要使用的標記以外的程序員自定義的標記均可以是標記符。而耽擱標記符對應的表達式就是標記符表達式。
經過下面的例子和註釋能夠了解推導的四個法則。
int i = 4; int arr[5] = {0}; int *ptr = arr; struct S { double d; } s; void Overloaded(int); void Overloaded(char); // 重載的函數 int&& RvalRef(); const bool Func(int);
// 規則1: 單個標記符表達式以及訪問類成員,推導爲本類型
decltype(arr) var1; // int[5], 標記符表達式 decltype(ptr) var2; // int*, 標記符表達式 decltype(s.d) var4; // double, 成員訪問表達式 decltype(Overloaded) var5; // 沒法經過編譯,是個重載的函數
// 規則2: 將亡值,推導爲類型的右值引用
decltype(RvalRef()) var6 = 1; // int&&
// 規則3: 左值,推導爲類型的引用
decltype(true ? i : i) var7 = i; // int&, 三元運算符,這裏返回一個i的左值 decltype((i)) var8 = i; // int&, 帶圓括號的左值 decltype(++i) var9 = i; // int&, ++i返回i的左值 decltype(arr[3]) var10 = i; // int& []操做返回左值 decltype(*ptr) var11 = i; // int& *操做返回左值 decltype("lval") var12 = "lval"; // const char(&)[9], 字符串字面常量爲左值
// 規則4:以上都不是,推導爲本類型
decltype(1) var13; // int, 除字符串外字面常量爲右值 decltype(i++) var14; // int, i++返回右值 decltype((Func(1))) var15; // constbool, 圓括號能夠忽略
(4)基於範圍的for循環關鍵字改進
int arr[5] = {1,2,3,4,5}; for(auto i : arr) cout<<i;
要求迭代的對象實現++和==等操做符,且範圍是肯定的。同時注意迭代器對象在for中是解引用的
vector<int> v = {1, 2, 3, 4, 5}; for (auto i = v.begin(); i != v.end(); ++i) cout<< *i<<endl; // i是迭代器對象 for (auto e: v) cout<< e <<endl; // e是解引用後的對象
四、加強類型的安全性
(1)強類型枚舉
(2)堆內存管理
五、加強性能和操做硬件的能力
(1)常量表達式
(2)變長模板
(3)原子類型與原子操做 aotmic<float>af{1.2f}
(4)線程局部存儲, 原來的__thread interrCode 能夠寫成intthread_localerrCode;
(5)快速退出:quick_exit與at_quick_exit 不執行析構函數而只是讓程序終止,與exit同屬於正常退出
用法相似下面:
#include <cstdlib> #include <iostream> using namespace std; void openDevice() { cout<< "device is opened." <<endl; } void resetDeviceStat() { cout<< "device stat is reset." <<endl; } void closeDevice() { cout<< "device is closed." <<endl; } int main() { atexit(closeDevice); atexit(resetDeviceStat); openDevice(); exit(0); }
輸出:
device is opened device stat is reset device is closed
六、開發可以改變人們思惟方式的特性
(1)nullptr
(2)=default =deleted
(3)lambda
lambda語法
[捕捉列表] (參數) mutable修飾符 -> 返回類型 {}
若是不須要參數,其括號能夠省略
捕捉列表用於捕捉上下文的變量,形式以下:
[var]表示值傳遞方式捕捉變量var
[=] 表示值傳遞方式捕捉全部父做用域的變量(包括this)
[&var] 表示引用傳遞捕捉變量var
[&] 表示引用傳遞捕捉全部父做用域的變量(包括this)
[this] 表示值傳遞捕捉當前的this指針
①lambda 顯然比仿函數(operator() (參數) )好用
class AirportPrice{ private: float _dutyfreerate; public: AirportPrice(float rate): _dutyfreerate(rate){} float operator()(float price) { return price * (1 - _dutyfreerate/100); } }; int main(){ float tax_rate = 5.5f; AirportPrice Changi(tax_rate); auto Changi2 = [tax_rate](float price)->float{ return price * (1 - tax_rate/100); }; float purchased = Changi(3699); float purchased2 = Changi2(2899); }
②代碼可讀性強
int Prioritize(int i); int AllWorks(int times){ int i; int x; try { for (i = 0; i < times; i++) x += Prioritize(i); } catch(...) { x = 0; } const int y = [=]{ int i, val; try { for (i = 0; i < times; i++) val += Prioritize(i); } catch(...) { val = 0; } return val; }(); }
③捕捉列表不一樣會致使不一樣的結果
#include <iostream> using namespace std; int main() { int j = 12; auto by_val_lambda = [=] { return j ;}; auto by_ref_lambda = [&] { return j;}; cout<< "by_val_lambda: " <<by_val_lambda() <<endl; //12 cout<< "by_ref_lambda: " <<by_ref_lambda() <<endl; //12 j++; cout<< "by_val_lambda: " <<by_val_lambda() <<endl; //12, 注意這裏,j傳入的時候是12 cout<< "by_ref_lambda: " <<by_ref_lambda() <<endl; //13 }
④mutable的做用
int main(){ int val; // 編譯失敗, 在const的lambda中修改常量 auto const_val_lambda = [=]() { val = 3;}; // 非const的lambda,能夠修改常量數據 auto mutable_val_lambda = [=]() mutable { val = 3;}; // 依然是const的lambda,對引用不影響 autoconst_ref_lambda = [&] { val = 3;}; // 依然是const的lambda,經過參數傳遞val auto const_param_lambda = [&](int v) { v = 3;}; const_param_lambda(val); return 0; }
⑤與STL結合
#include <vector> #include <algorithm> using namespace std; vector<int>nums; vector<int>largeNums; const int ubound = 10; inline void LargeNumsFunc(inti){ if (i>ubound) largeNums.push_back(i); } void Above() { // 傳統的for循環 for (auto itr = nums.begin(); itr != nums.end(); ++itr) { if (*itr>= ubound) largeNums.push_back(*itr); } // 使用函數指針 for_each(nums.begin(), nums.end(), LargeNumsFunc); // 使用lambda函數和算法for_each for_each(nums.begin(), nums.end(), [=](inti){ if (i>ubound) largeNums.push_back(i); }); }
七、融入編程現實
(1)algnof和alognas
(2)[[ attibute-list ]]
(3)Unicode的庫支持
(4)原生字符串字面量