1 新類型
C++ 11新增了long long和unsigned long long,以支持64bit寬度;
新增char16_t和char32_t以支持16位和32位字符表示;
增加了“原始”字符串。
2 初始化
C++ 11擴展了大括號{}的適用范圍,既可以用於基本類型,也可以用於自定義類型:
int x = {5};
couble y{3.5};
short quar[5]{1, 2, 3, 4, 5};
int* p = new int[5]{1, 2, 3, 4, 5};
創建對象時,也可以使用大括號鏈表來調用構造函數:
class Stump
{
Public:
Stump(int r, double w) : roots(r), weight(w){}
Private:
int roots;
double weight;
};
Stump s1(3, 4.5); // old style
Stump s2{3, 4.5}; // new style
Stump s3 = {3, 4.5}; // new style
注意:使用初始化列表可以防止向下轉型,如:
char c1 = 1.57e27; // double-to-char, undefined behavior
char c1{1.57e27}; // double-to-char, compile error
std::initializer_list可以作為函數參數,如果一個類中的構造函數使用了這種方法,則其他函數將不能再使用其作為參數,這是為了防止調用二義性。
Initializer_list模板類提供了兩個成員函數begin()和end(),用來指定列表的范圍,如:
double sum(std::initializer_list<double> il)
{
double total = 0;
for(auto p = il.begin(); p != il.end(); p++)
total += *p;
return total;
}
double total = sum({1, 2.5, 8.3, 2.2, 1.0});
3 聲明
C++提供了多種簡化聲明的方式,尤其在使用模板時更加方便。
1 auto:用來實現自動類型推斷,如:
auto i = 112; // i is type int
auto pt = &i; // pt is type int*
double fm(double, int);
auto pf = fm; // pf if type double(*)(double, int);
用於模板時形式簡潔,如:
for(std::initializer_list<double>::iterator p = il.begin(); p != il.end(); p++)
改寫為:
for(auto p = il.begin(); p != il.end(); p++)
2 decltype:將變量的類型指定為表達式的類型。
decltyte (x) y; 將y設為與x相同的類型,其中x是一個表達式。
3 返回類型后置:在函數名和參數列表后面指定返回類型。
double f1(double int); // return double type
auto f2(double, int) -> double; // new syntax, return double type
如果結合上模板表示返回類型,那就更好了,如下:
template<typename T, typename U>
auto eff(T t, U u) -> decltype(T * U)
{
}
4 模板別名:using =
C++中創建別名一般用typedef,如:
typedef std::vector<std::string>::iterator itType;
還可以這樣做:
using itType = std::vector<std::string>::iterator;
二者的差別在於,using可以使模板具體化,如:
using arr = std::array<T, 12>;,此時typedef不行。
5 nullptr:空指針
之前,C++使用0表示空指針,同樣的0既可以表示整型,又可以表示空指針,比較混亂;新增的nullptr是指針類型,不能轉換為整型。為了兼容性,C++目前仍然允許0表示空指針,即nullptr == 0的結果為true。
4 智能指針
C++ 11摒棄了auto_ptr,新增了三種:unique_ptr、shared_ptr、weak_ptr。
5 異常
之前C++的語法中可以指出函數可能引發哪些異常,如:
void f1(int) throw(bad_alloc); // 可能拋出bad_alloc異常
void f2(long long) throw(); // 不拋異常
C++摒棄了異常規范,新增了如下規則:
Void f3(short, short) noexcept; // 不拋異常
6 作用域內枚舉
傳統的C++枚舉的作用域在所屬的域內,就是說同一作用域內不能出現兩個同名的枚舉變量。
C++ 11新增了一種枚舉,使用class或者struct定義:
enum Old{yes, no}; // old style
enum class New{yes, no}; // new style
enum struct New{yes, no}; // new style
由於允許同名存在,因此引用時需要使用枚舉名限定:New::yes。
7 對類的修改
在擴展類的設計方面,C++ 11很多改進,比如允許構造函數被繼承、彼此調用、移動構造函數、移動賦值運算符等。
7.1 顯示轉換運算符
早期的C++會導致自動類型轉換,比如:
class Plebe
{
Plebe(int);
explicit Plebe(double);
...
};
Plebe a, b;
a = 5; // 發生隱式類型轉換,實則調用Plebe(5);
b = 0.5; // 不允許
b = Plebe(0.5); // 允許
C++ 11擴展了explicit,使得可以如下這樣做:主要是針對轉換函數:
比如:
operator int() const;
explicit operator double() const;
int n = a; // allow
double x = b; // not allow
x = double(b); // allow
7.2 類內成員初始化
class Session
{
int mem = 10;
double mem2{2.35};
...
};
8 模板和STL方面
為了改善模板和標准化方面的易用性,C++ 11做了多個改進:
8.1 改進的for循環
double prices[5] = {1, 2, 3, 4, 5};
for(double x : prices)
for(auto x : prices)
如果要在循環中修改數組或容器中的每個元素,可以使用引用:
for(auto& x : prices)
8.2 新增的STL容器
C++ 11新增了forward_list、unordered_map、unordered_multimap、unordered_set、unordered_multiset。
新增了模板array,實例化時指定元素類型和個數:std::array<int, 10> ar; // 10個int
8.3 新增STL方法
cbegin()、cend()、crbegin()、crend()。這幾個方法將容器元素視為const。
8.4 valarray升級版
對於valarray模板,C++ 11新增了兩個方法begin()和end()。
8.5 摒棄了export
C++ 98增加的關鍵字export可以讓程序員將模板定義放在接口文件中,實現文件中放置方法體,但是實踐證明這一點也不現實,因此C++ 11把它摒棄了。
8.6 尖括號
舊時的C++要求在定義嵌套的模板時,兩個尖括號之間必須有空格,比如:vector<list<int> >,但是C++ 11中不再需要這樣了,比如vector<list<int>>也可以通過的。
9 右值引用
C++新增了右值引用,使用&&表示。【相對於左值,右值表示字面常量、表達式、函數的非引用返回值等】
如:
int&& r1 = 12;
int x = 5;
int y = 8;
int&& r2 = x + y;
我們可以通過r1來修改12,很方便。
10 移動語義和右值引用
10.1 為何需要移動語義
移動語義就是為了避免多余的復制工作,就是說與其復制,還不如將源地址傳給使用者,因為有時復制工作確是沒什么用。
要實現移動語義,需要采用某種措施讓編譯器知道到底什么情況下需要復制,什么情況下不必復制。這時,右值引用就可以配上用場了。
傳統的復制構造函數執行深復制,並且不改變實參,因此,參數為const類型;
移動構造函數只是更改了引用記錄,並且可能會改變實參,因此,參數必須是非const類型;
如下實例:
class Use
{
public:
Use();
Use(const Use& f); // copy constructor
Use(Use&& f); // move constructor
...
private:
int n;
char* pc;
};
...
Use::Use(const Use& f) : n(f.n) // 深復制
{
pc = new char[n];
for(int i = 0; i < n; i++)
pc[i] = f.pc[i];
}
Use::Use(Use&& f) : n(f.n)
{
pc = f.pc; // 移動構造,轉移控制權
f.pc = nullptr;
f.n = 0;
}
10.2 移動構造解析
雖然移動構造定義好了,但是如何調用呢,也就是說什么情況下才會發生移動構造呢?
必須使用右值引用調用:
Use two = one; // match Use::Use(const Use&);
Use four(one + three); // match Use::Use(Use&&);
因為one是左值,而one + three是右值。
移動賦值情況類似,不記錄了。
10.3 強制移動
移動構造和移動賦值使用右值引用,如果需要讓他們操作左值呢?
答案是使用static_cast<>將對象強制轉換為Use&&即可。不過在C++ 11種提供了更便捷的操作,在頭文件utility中聲明的函數std::move(...)。
如果使用了std::move()函數調用,但是類中卻沒有定義移動相關函數,那么編譯器會調用傳統版本的移動構造函數和移動賦值操作符。
11 新的類功能
假設你為類定義了構造函數,那么類就不會自動提供默認的構造函數了,然而,如果你仍然想使用類提供的默認版本,那么可以使用default關鍵字:
class Some
{
public:
Some(Some&&);
Some() = default; // use default constructor
...
};
相反地,如果要禁用編譯器提供的默認函數,可以使用delete:
class Some
{
public:
Some(Some&&);
Some() = default; // use default constructor
Some(const Some&) = delete; //disable copy constructor
...
};
當然要想禁用某個編譯器提供的函數也可以顯式聲明為private,但是使用delete更方便且不易出錯。
注意:default關鍵字只能用於6個特殊函數,而delete卻能夠用於任何成員函數。
委托構造
如果一個類包含多個構造函數,C++ 11允許在一個構造函數中的定義中使用另一個構造函數,但這必須通過初始化列表進行操作,如下:
class Notes
{
int k; double x; string s;
public:
Notes(int kk, double xx, string ss) : k(kk), x(xx), s(ss){ }
Notes(int kk) : Notes(0, 0.5, “benxin”){ k = kk; }
...
};
繼承構造
C++ 11允許派生類繼承基類的構造函數。C++ 98提供了一種讓某個名稱空間中的同名重載函數都可用的語法,如下:
namespace Box
{
int fn(){}
int fn(int){}
int fn(double){}
}
using Box::fn;該語句使得fn的所有重載版本都可用。我們一般使用這種方法在派生類中調用基類的同名函數。之所以提供這種語法,就是因為覆蓋是以函數名為基礎的,不論參數是否對應都將被覆蓋。
C++ 11將這種語法用於構造函數中,使得派生類將繼承基類的構造函數(默認構造函數、復制構造函數、移動構造函數除外)。
管理虛方法:override和final
傳統的虛函數是為了實現多態調用,但這必須是派生類與基類的虛函數簽名完全一致的情況下才會發生多態,如果不一致,假設如下:
1 class Base 2 { 3 public: 4 virtual void Fn(){ cout << "Base::Fn()" << endl; } 5 }; 6 7 class Derived : public Base 8 { 9 public: 10 virtual void Fn(int){ cout << "Derived::Fn()" << endl; } 11 }; 12 13 int main() 14 { 15 Base* d = new Derived; 16 d->Fn(); 17 18 return 0; 19 }

結果如上,編譯器竟然會根據指針的靜態類型發生了調用,並無多態發生。
好在C++ 11提供了override指出了該虛函數是為了覆蓋基類的虛函數而存在的,此時如果不小心與基類中的虛函數不一致了,那么編譯器不會讓你通過的。
1 class Derived : public Base 2 { 3 public: 4 virtual void Fn(int) override{ cout << "Derived::Fn()" << endl; } 5 };

如果想禁止派生類覆蓋基類的虛函數,可在基類的虛函數參數列表后加上final。
1 class Base 2 { 3 public: 4 virtual void Fn() final{ cout << "Base::Fn()" << endl; } 5 }; 6 7 class Derived : public Base 8 { 9 public: 10 virtual void Fn() override{ cout << "Derived::Fn()" << endl; } 11 };

最后需要指出的是:override和final並非關鍵字,而是具有特殊含義的標識符,編譯器會根據上下文確定其到底是否代表特殊性。
12 Lambda函數
如下示例使用了三種方法給STL算法傳遞信息:函數指針、函數符、lambda函數。出於方便性,我們將其統稱為函數對象。
假設生成一個包含1000個數的容器,並判斷其中有多少個可以被3整除,有多少個可以被13整除:
vector<int> numbers(1000);
generate(numbers.begin(), numbers.end(), rand);
說明:generate函數前兩個參數指定容器區間,並將每個元素設置為第三個參數返回的值。
bool f3(int x){ return x % 3 == 0; }
bool f13(int x){ return x % 13 == 0; }
接下來可以借助於count_if函數,該函數前兩個參數也是指定區間,第三個參數是一個返回true或false的函數對象,該函數計算使得返回true的元素個數。
int count = count_if(numbers.begin(), numbers.end(), f3);
如上述所示,rand和f3都是作為函數指針傳遞過去的。
下面看一下函數符如何使用:
我們知道函數符是一個類對象,主要利用重載()操作符來完成任務:
class f_mod
{
public:
f_mod(int d = 1) : dv(d){ }
bool operator()(int x){ return x % dv == 0; }
private:
int dv;
};
創建對象:
f_mod obj(3); // set dv to 3
使用:
bool rv = obj(7); // same as obj.operator()(7);
// return 7 % 3 == 0
因此:
int count = count_if(numbers.begin(), numbers.end(), f_mod(3));
最后看看如何使用lambda:C++ 11中,對於接受函數指針或函數符的函數,也可以使用匿名函數定義(lambda)作為其參數:
bool f3(int x){ return x % 3 == 0; }
上述函數f3用lambda來表示即為:
[](int x){ return x % 3 == 0; }
lambda和一般函數的區別如下:
使用[]代替函數名;沒有返回類型。返回類型相當於使用decltype推斷而得。如果表達式中不包括返回語句,則推斷出類型為void。但要特別注意,僅當lambda表達式中僅包含一條返回語句時,自動推斷類型才起作用;否則,需要使用新增的返回類型后置語法,如下:
[](double x) -> double { int y = x; return x – y; }
int count = count_if(numbers.begin(), numbers.end(), [](int x){ return x % 3 == 0; });
我們也可以給lambda指定一個名稱,如:
auto mod3 = [](int x){ return x % 3 == 0; }
int count = count_if(numbers.begin(), numbers.end(), mod3);
當然,我們也可以像普通函數一樣使用lambda,如:mod3(z); ---> return z % 3 == 0;
還有,lambda還可以操作變量,如:
[count]:表示以傳值方式訪問變量;
[&count]:表示以引用方式訪問變量;
[count1, &count2]:以傳值方式訪問count1,以引用方式訪問count2;
[&, count]:表示以傳值方式訪問count,以引用方式訪問其他變量;
[=]:表示以傳值方式訪問所有變量;
[&]:表示以引用方式訪問所有變量;
1 int count3 = 0; 2 int count13 = 0; 3 vector<int> numbers(100); 4 generate(numbers.begin(), numbers.end(), rand); 5 for_each(numbers.begin(), numbers.end(), [&](int x){ count3 += x % 3 == 0; count13 += x % 13 == 0;}); 6 7 cout << count3 << " " << count13 << endl;

注意:
C++引入lambda的主要目的是為了簡化程序的編寫,典型的lambda是測試表達式或者比較表達式,因為它們一般只有一條返回語句,因此可以使用返回類型自動推斷。
13 可變參數模板
C++ 11提供了一個用省略號表示的元運算符,使得可以聲明表示模板參數列表,其語法如下:
template<typename... Args>
void show(Args... args){ }
其中Args表示模板參數列表,args表示函數參數列表。
這里有一個問題,我們如何訪問具體的某個參數呢?使用args[2]嗎?答案是否定的,我們在可變參數中不能使用索引來訪問,這里需要遞歸地訪問每個參數:
template<typename T, typename... Args>
void show(T value, Args... args)
{
cout << value << “, ”;
show(args...);
}
每次調用show,可變參數將減少一個,直到調用show()時,調用參數為空,不接受,則退出遞歸。
14 斷言
C++提供了調試工具assert,這是一個宏,用於在運行階段對斷言進行檢查,如果為true,則顯示一條消息,否則調用abort();
C++ 11新增了關鍵字static_assert,可用於在編譯階段對斷言進行測試。