《C++中文版Primer 第五版之C++11的新特性》

c++11新特性總結

1. long long 類型

c++11新增加了long long 類型,long long 在x86平臺下爲64位。

2. 列表初始化

在c++11新標準中,增加了使用列表初始化來初始化變量,即用花括號{}來初始化變量。

int value = 0;
int value = {0};
int value(0);

int vaue{0};    //c++11新特性
list<string> authors {"Milton","Austen","Shakespeare"}; //容器列表初始化

當用於內置類型的變量時,如果我們使用初始化列表初始化且初始化值存在丟失信息的風險,則編譯器將報錯。

long double ld = 3.1415926536
int a{ld}, b = {ld};   //錯誤:轉換未執行,因爲存在丟失信息的危險
int c(ld), d = ld;    // 成功:轉換執行,且確實丟失了部分值

3. nullptr常量

空指針: 不指向任何對象的指針。下列是幾種生成空指針的方法:

int *p1 = 0;          //直接將p1初始化爲字面常量0

//需要#include <stdlib.h>
int *p2 = NULL;      //等價於 int *p2 = 0;

int *p3 = nullptr;   //c++11新特性,等價於int *p3 = 0;

nullptr是一種特殊類型的字面值,它可以被轉換成任意其他的指針類型。新標準下,C++程序員最好使用nullptr,儘量避免使用NULL。使用未初始化的指針是引發運行時錯誤的一大原因,如果實在不清楚指針應該指向何處,就把它初始化爲nullptr或者0。

4. constexpr變量

c++11新標準規定,允許將變量聲明爲constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式。

聲明爲constexpr的變量一定是一個常量,而且必須將常量表達式初始化。

constexpr int mf = 20;             //20是常量表達式
constexpr  int limit = mf + 1;     //mf + 1 是常量表達式
constexpr int sz = size();           //只有當size是一個constexpr函數時纔是一條正確的聲明語句

注意明確一點:在constexpr聲明中如果定義了一個指針,限定符constexpr僅對指針有效,與指針所指的對象無關。

const int *p = nullptr;    //p是一個指向 整型常量 的 指針
constexpr int *q = nullptr;  //q是一個指向 整數 的 常量指針

和其他常量指針類似,constexpr指針即可指向常量也可以指向一個非常量:

int j = 0;
constexpr int i = 42;  // i的類型是整型常量
// i 和 j 都必須定義在函數體之外
constexpr const int *p = &i; // p 是常量指針,指向整型常量i
constexpr int *p1 = &j; // p1是常量指針,指向整數j

5. 類型別名聲明

類型別名是一個名字,它是某種類型的同義詞。
有兩種方法可用於定義類型別名:

1.傳統方法:使用關鍵字  typedef

typedef double wages; //wages 是 double 的同義詞
typedef wages base, *p; //base 是double的同義詞,p是double* 的同義詞

wages hourly,weekly; //等價於double hourly,weekly;

2.C++11新標準:使用 別名聲明,關鍵字 using

using SI = Sales_item; //SI是Sales_item的同義詞
SI item; //等價於Sales_item item;

6. auto類型指示符

C++11引入了auto 類型說明符,用它可以讓編譯器替我們去分析表達所屬的類型。auto定義的變量必須有初始值。

//由val1和val2相加的結果可以推斷出item的類型
  auto item = val1 + val2;
  
//使用auto可以在一條語句中聲明多個變量
auto i = 0, *p = &i;     //正確:i是整數,p是整型指針
auto sz = 0,pi = 3.14;  // 錯誤:sz和pi的類型不一致

//auto 一般會忽略掉頂層的const
int i = 0;
const int ci = i, &pr = ci;
auto b = ci;   // b是一個整數(ci的頂層const特性被忽略掉了)
auto  c = &i ; //c是一個整型指針(整數的地址就是指向整數的指針)
auto d = &ci;  //d是一個指向整數常量的指針(對常量對象取地址是一種底層的const)

//如果希望推斷出的auto類型是一個頂層const,需要明確指出:
const auto f = ci; //ci的推演類型是int,f是const int

7.decltype類型指示符

有時候我們希望從表達式的類型推斷出要定義的變量的類型,但是不想用該表達式的值初始化變量。爲了滿足這一要求,c++11標準引入了第二種類型說明符decltype作用是選擇並返回操作數的數據類型

decltype( fun() ) sum = x;   //sum的類型爲函數fun的返回類型

decltype處理頂層的const和引用的方式與auto有點不同,如果decltype使用的表達式是一個變量,則decltype返回該變量的類型(包括頂層const和引用在內):

const int ci = 0, &cj = ci;
decltype( ci ) x = 0; //x的類型是const int
decltype( cj ) y = x; //y的類型是const int& ,y綁定到變量x
decltype( cj ) z;  //錯誤:z是一個引用,必須初始化

decltype和auto的另一個重要區別是:decltype的結果類型與表達式形式密切相關。對於decltype所用的表達式來說,如果變量名加了一對括號,則得到的類型與不加括號時會有不同。

//decltype的表達式如果是加上了括號的變量,結果將是引用
decltype( (i) ) d; //錯誤:d是int& ,必須初始化
decltype(i) e;    // 正確,e是一個(未初始化)int

切記:decltype( (variable) ) (注意是雙括號)的結果永遠是引用,而decltype(variable)結果只有當variable本身就是一個引用時纔是引用。

8. 類內初始化

C++11新標準規定,可以爲數據成員提供一個類內初始化值。對類內初始值的限制:或者放在花括號裏,或者放在等號右邊,不能使用圓括號。

class Sales_data{
public:
	Sales_data() = default;
private:
	unsigned units_sold = 0;   //類內初始化
	double revenue = 0;       //類內初始化
};

9. 範圍for語句

C++11提供了基於範圍的for語句,語法形式如下:

for (declaration : expression)
	statement
例子:
string str("some");
for( auto c : str )    //對於str中的每個字符
	cout << c << endl;  // 輸出當前字符,後面緊跟一個換行符
	
輸出結果:
				  s
				  o
				  m
				  e
				  
vector<int> v = {0,1,2,3,4,5,6,7,8,9};
for(auto &r: v)
	r *= 2;       //將v中每個元素的值翻倍

10.vector對象的列表初始化

C++11新標準提供了爲vector對象的元素賦初值的另一種方法,列表初始化即用花括號括起來的0個或多個初始元素賦值給vector對象。

vector<string> articles = {"a", "an", "the"}; //傳統vector初始化
vector<string> v1{"a", "an", "the"};   //c++11列表初始化
vector<string> v2("a", "an", "the");   //錯誤,應用花括號

11.標準庫的begin和end函數

儘管能計算得到尾後指針,但這種方法很容易出錯。爲了讓指針的使用更簡單、更安全,c++11新標準引入了兩個名爲begin和end的函數,這兩個函數定義在iterator頭文件中。使用形式是將數組作爲它們的參數。

int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(arr);   //指向arr首元素的指針
int *last= end(arr);    //指向arr尾元素的下一個位置的指針

//計算數組的元素數量
auto n = end(arr) - begin(arr);   //n 爲10

//找出第一個大於5的數
while(beg != last && *beg > 5)
	++beg;

12. 將sizeof用於類成員

C++11新標準允許我們使用作用域運算符來獲取類成員大小。通常情況下只有通過類的對象才能訪問到類的對象成員,但是sizeof運算符無須我們提供一個具體的對象,因爲要想知道類成員的大小無須真的獲取該成員。

Sales_data data;
sizeof data.revenue;             //Sales_data的revenue成員對應類型的大小
sizeof Sales_data::revenue;     //另一種獲取revenue大小的方式

13. 標準庫initializer_list類

如果函數的實參數量未知但是全部實參的類型都相同,可以使用initializer_list類型的形參。initializer_list是一種標準庫類型,用於表示某種特定類型的值的數組。

initializer_list提供的操作
initializer_list<T> lst; 默認初始化; T類型元素的空列表
initializer_list<T> lst{a,b,c}; lst的元素數量和初始值一樣多,列表的元素是const
lst2(lst) 或 lst2 = lst 拷貝或賦值一個initializer_list對象,拷貝後,原始列表和副本共享元素
lst.size() 列表中元素的數量
lst.begin() 返回指向lst中首元素的指針
lst.end() 返回指向lst中尾元素的下一位置的指針

注意:與vector不同,initializer_list對象中的元素永遠是常量。

void error_msg(initializer_list<string> il)
{
	for(auto beg = il.begin(); beg != il.end(); ++beg)
		cout << *beg << " ";
	cout << endl;
}

如果想向initializer_list形參中傳遞一個值的序列,必須把序列放在一對花括號內:

//expexced 和actual是string對象
if(expected != actual)
	error_msg({"functionX", expected, actual});
else
	error_msg({"functionX","okay"});

14.列表初始化返回值

C++11新標準規定,函數可以返回花括號包圍的值的列表。

vector<string> process()
{
	//expexced 和actual是string對象
	if(expected.empty())
		return {};    //返回一個空vector對象
	else if(expected == actual)'
		return {"functionX", "okay"};  //返回列表初始化的vector對象
	else
		return {"functionX", expected, actual};
}

15.定義尾置返回類型

尾置返回類型跟在形參列表後面以一個 -> 符號開頭。

auto a = []()->int{
	int i = 1;
	return i;
};  //返回int類型

16. 使用 =default 生成默認構造函數

C++11新標準中,如果我們需要默認的行爲,可以在參數列表後面寫上 =default來要求編譯器生成構造函數。如果 =default在類的內部,則默認構造函數是內聯;在類的外部,則該成員默認情況下不是內聯的

struct Sales_data{
	Sales_data() = default; //默認構造函數
};

17.委託構造函數

委託構造函數使用其它所屬類的其他構造函數執行它自己的初始化過程。

class Sales_data{
public:
	//非委託構造函數使用對應的實參初始化成員
	Sales_data(string s, unsigned cnt, double price):
		bookNo(s),units_sold(cnt),revenue(cn*price){ }
		
	//其餘構造函數全部委託給另一個構造函數
	Sales_data():Sales_data("", 0, 0){ };
	Sales_data(string s):Sales_dtat(s, 0, 0){ }
private:
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};

18. array和forward_list容器

forward_list 單向鏈表。只支持單向順序訪問。在鏈表的任何位置進行插入/刪除操作速度都很快
array 固定數組大小。支持快速隨機訪問,不能添加和刪除元素

技巧: 通常使用vector是最好的選擇,除非有很好的理由選擇其他容器。

19.容器的emplace成員

新標準引入了三個新成員–emplace_front、emplace和emplace_back。這些操作構造而不是拷貝元素。這些操作分別對應push_front、insert和push_back,即允許我們將元素放置在容器頭部、一個指定位置之前或容器尾部。
調用push或者insert成員函數時,我們將元素類型的對象傳遞給它們,這些對象被拷貝到容器中。而當我們調用一個emplace成員函數時,則是將參數傳遞給元素類型的構造函數

//在c的末尾構造一個Sales_data對象
//使用三個參數的Sales_data構造函數
c.emplace_back("Jack",25,15.99);  

//錯誤,沒有接受三個參數的push_back版本
c.push_back("Jack",25,15.99);

//正確,創建一個臨時的Sales_data對象傳遞給push_back
c.push_back(Sales_data("Jack",25,15.99));

在調用emplace_back時,會在容器管理的內存空間中直接創建對象。而調用push_back則會創建一個局部臨時對象,並將其壓入容器中。

20. shrink_to_fit

新標準庫中,可以調用shrink_to_fit來要求deque、vector或string退回不需要的內存空間。即將容器的capacity()大小減少爲與size()相同大小。但是調用shrink_to_fit並不保證一定退回內存空間。

21. lambda表達式

一個lambda表達式可以表示一個可調用的代碼單元,可以理解爲一個未命名的內聯函數。與任何函數類似,一個lambda具有一個返回類型、一個參數列表和一個函數體。但與函數不同,lambda可能定義在函數內部。一個lambda表達式具有如下形式:

[capture list] (parameter list) -> return type {function body}

capture list(捕獲列表)是一個lambda所在函數中定義的局部變量的列表;return type、parameter list 和function body與任何函數一樣,分別表示返回類型、參數列表和函數體。但是,與普通函數不同,lambda必須使用尾置返回來指定返回類型。

//可以忽略參數列表和返回類型,但必須永遠包含捕獲列表和函數體
auto f = [ ] {return 42;};
//lambda調用方式與普通函數調用方式相同
cout << f() << endl;  //打印42

//向lambda傳遞參數
//空捕獲列表表明此lambda不使用它所在函數的任何局部變量
[](const string &a,const string &b)
{
	return a.size() > b.size();
}

//按長度排序,vec爲存放string類型的vector
stable_sort(vec.begin(),vec.end(),[](const string &a,const string &b)
{
	return a.size() > b.size();
});

lambda採用值捕獲的前提是變量可以拷貝。 與參數不同的是,被捕獲的變量的值是在lambda創建時拷貝,而不是調用時拷貝。

//按值捕獲
void fun1()
{
	size_t v1 = 42; //局部變量
	//將v1拷貝到名爲f的可調用對象
	auto f = [v1] {return v1; };
	v1 = 0;
	auto j = fun(); //j爲42;f保存了我們創建它時v1的拷貝
}

由於被捕獲的變量的值是在lambda創建時拷貝,因此隨後對其的修改不會影響到lambda內對應的值。

lambda採用引用方式捕獲一個變量,必須確保被引用的對象在lambda執行的時候是存在。

//引用捕獲
void fun2()
{
	size_t v1 = 42; //局部變量
	//對象f2包含v1的引用
	auto f2 = [&v1] {return v1;};
	v1 = 0;
	auto j = f2(); //j爲0;f2保存v1的引用,而非拷貝
}

lambda還有一種捕獲方式,隱式捕獲。在捕獲列表中寫一個 & 或者 = 。&告訴編譯器採用引用捕獲方式,=則表示採用值捕獲方式。

//混合使用隱式捕獲和顯示捕獲時,捕獲列表的第一個元素必須是 & 或 =
void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' ')
{
	//os隱式捕獲,引用捕獲方式;c顯示捕獲,值捕獲方式;其他變量按引用捕獲方式
	for_each(words.begin(), words.end(), [&, c](const string &s){
		os << s << c;
	});
	//os顯示捕獲,引用捕獲方式;c隱式捕獲,值捕獲方式;其他變量按值捕獲方式
	for_each(words.begin(), words.end(), [=, &os] (const string &s){
	os << s << c;
	});
}
//指定lambda的返回類型,必須使用尾置返回類型
transform(vi.begin(), vi.end(), vi.begin(),[ ](int i) -> int)
{
	if(i < 0)
		 return -i;
	else 
		return i;
});

22.標準庫bind函數

c++11 bind標準庫函數,定義在頭文件functional中。bind接受一個可調用對象,生成一個新的可調用對象來適配原對象的參數列表。

調用bind的一般形式爲:
auto newCallable = bind(callable,arg_list);

newCallable 爲一個可調用的對象,arg_list是一個逗號分隔的參數列表,也就是給callable的參數。調用newCallable 時,newCallable 會調用callable,並傳遞給它arg_list中的參數。
arg_list的參數也可能包含形如 _n 的名字,其中n爲一個整數,這些參數是“佔位符”,表示newCallable 的參數,在命名空間std::placeholders中。例如:_1爲newCallable 的第一個參數,_2爲第二個參數,依此類推。

/*checkTest是一個可調用對象,接收一個string類型的參數,
並用string和值6來調用check_size*/
using std::placeholders::_1;
auto checkTest = bind(check_size, _1, 6);

std::string s = "hello world";
bool b1 = checkTest(s);  //checkTest會調用check—_size(s, 6)

此bind調用只有一個佔位符,表示checkTest只接受單一參數。佔位符出現在arg_list的第一個位置。

ostream &print(ostream &os, const string &s, char c)
{
	return os << s << c;
}

//錯誤的做法,不能直接用bind來代替對os的捕獲
for_each(words.begin(),words.end(),bind(print, os, _1, ' '));

//正確做法如下
for_each(words.begin(),words.end(),bind(print, std::ref(os), _1, ' '))

原因在於bind拷貝其參數,而我們不能拷貝一個ostream。如果我們希望傳遞給bind一個對象而不拷貝它,就必須得用標準庫 ref 函數。ref 定義在functional頭文件中。

23. 關聯容器的列表初始化

定義一個map時,必須既指明關鍵字類型又指明值類型;而定義一個set時,只需指明關鍵字類型。在新標準下,我們可以對關聯容器進行值初始化。

map<string, size_t> word_count; //空容器
//列表初始化
set<string> exclude = {"the", "but", "and", "or"};

map<string, string> authors = {{"Joyce","James"}, {"Austen","Janes"}};

mapset的關鍵字是唯一的。而multimapmultiset允許多個元素具有相同的關鍵字。

vector<int> ivec;
for(vector<int>::size_type i = 0; i != 10; ++i)
{
	ivec.push_back(i);
	ivec.push_back(i); //每個數重複保存一次
}

set<int> iset(ivec.cbegin(), ivec.cend());
multiset<int> mset(ivec.cbegin(), ivec.cend());

cout << ivec.size() << end;    //20
cout << iset.size() << end;    //10
cout << mset.size() << end;    //20

24. pair的列表初始化

新標準下,創建一個pair最簡單的方法是在參數列表中使用花括號初始化。也可以調用make_pair顯示構造pair

//向word_count插入word的4種方法
map<string, int> word_count;
string word = "hello";

word_count.insert((word, 1));
word_count.insert(make_pair(word, 1));
word_count.insert(pair<string, size_t>(word, 1));
word_count.insert(map<string,size_t>::value_type(word, 1));

25. 無序容器

新標準定義了4個無序關聯容器(unordered_map, unordered_multimap, unordered_set, unordered_multiset)。這些容器不是使用比較運算符來組織元素,而是使用一個哈希函數和關鍵字類型的==運算符。
在關鍵字的元素沒有明顯的序關係的情況下,無序容器是非常有用的。無序容器提供了與有序容器相同的操作。

無序容器的管理操作
c.bucket_count() 正在使用的桶的數目
c.max_bucket_count() 容器能容納的最多的桶的數量
c.bucket_size(n) 第n個桶中有多少個元素
c.bucket(k) 關鍵字爲k的元素在哪個桶裏面
local_iterator 可以用來訪問桶中的迭代器類型
const_local_iterator 桶迭代器的const版本
c.begin(n), c.end(n) 桶n的首元素迭代器和尾後迭代器
c.load_factor() 每個桶的平均元素數量,返回float類型的值
c.max_load_factor() c試圖維護的平均桶大小,返回float值。c會在需要時添加新的桶,使得load_factor <= max_load_factor
c.rehash(n) 重組存儲,使得bucket_count >= n ,且bucket_count > size/max_load_factor
c.reserve(n) 重組存儲,使得c可以保存n個元素且不必rehash

26.動態內存與智能指針

c++中,動態內存管理通過newdelete來完成。new在動態中爲對象分配空間並返回一個指向該對象的指針,delete接收一個動態對象指針,銷燬該對象,釋放內存。
動態內存的使用很容易出現問題。有時候,我們會忘記釋放內存,從而產生內存泄露;有時候在尚有指針引用內存的情況下我們就釋放了它,在這種情況下就會產生引用非法內存的指針。
爲了更容易和更安全的使用動態內存,新的標準庫提供了智能指針。shared_ptr允許多個指針指向同一個對象;unique_ptr則"獨佔"所指向的對象;weak_ptr爲弱引用,指向一個shared_ptr管理的對象,而不會改變shared_ptr的引用計數,解決shared_ptr互指的問題。這三種類型都定義在memory頭文件中。

  1. shared_ptr類

最安全的分配和使用動態內存的方法是調用一個名爲make_shared的標準庫函數。此函數在動態內存中分配一個對象並初始化它,返回指向此對象的shared_ptr。make_shared也是定義在memeory頭文件中。

//str 指向一個值爲"9999999999"的string
shared_ptr<string> str = make_shared<string>(10, '9');
//p指向一個值初始化的int,值爲0
shared_ptr<int> p = make_shared<int>();
//p1指向一個值初始化的int,值爲20
shared_ptr p1 = make_shared<int>(20);

如果將shared_ptr存放於一個容器中,而後不再需要全部元素,只使用其中的一部分,要記得用erase刪除不再需要的那些元素。

shared_ptr和unique_ptr都支持的操作
shared_ptr< T > sp 空智能指針,可以指向類型爲T的對象
unique_ptr< T > up 空智能指針,可以指向類型爲T的對象
p 將p用作一個條件判斷,若p指向一個對象,則爲true
*p 解引用p,獲得它指向的對象
p->mem 等價於(*p).mem
p.get() 返回p中保存的指針。要小心使用,若智能指針釋放了對象,返回的指針所指向的對象也就消失了
swap(p, q) 交換p和q中的指針
p.swap(q) 交換p和q中的指針
shared_ptr獨有的操作
make_shared< T > (args) 返回一個shared_ptr,指向一個動態分配的類型爲T的對象。使用args初始化對象
shared_ptr< T > p(q) p是shared_ptr的拷貝;此操作會遞增q的計數器。q中的指針必須能轉換爲T*
p = q p和q都是shared_ptr,所保存的指針都必須能相互轉換。此此操作會遞減p的引用計數,遞增q的引用計數。若p的引用計數爲0,則將其管理的原內存釋放
p.unique() 若p.use_count()爲1,返回true;否則返回false
p.use_count() 返回與p共享對象的智能指針數量;可能很慢,主要用於調試
定義和改變shared_ptr的方法
shared_ptr< T > p(q) p管理內置指針q所指向的對象;q必須指向new分配的內存,且能夠轉換爲T*類型
shared_ptr< T > p(q, d) p接管了內置指針q所指向的對象的所有權。q必須能轉換成T*類型。p將使用可調用對象d來替代delete
p.reset() 若p是唯一指向其對象的shared_ptr,reset會釋放此對象。若傳遞了可選的參數內置指針q,會令p指向q,否則會將p置爲空。若還傳遞了參數d,將會調用d而不是delete來釋放q
p.reset(q)
p.reset(q, d)
  1. unique_ptr類

與shared_ptr不同,unique_ptr沒有類似make_shared的標準庫函數返回一個unique_ptr。當我們定義一個unique_ptr時,需要將其綁定到一個new返回的指針上。初始化unique_ptr必須採用直接初始化形式:

unique_ptr<double> p1;
unique_ptr<int> p2(new int(42)); //p2指向一個值爲42的int

unique_ptr不支持普通拷貝或賦值操作。

unique_ptr<string> p1(new string("hello")); //正確,直接初始化
unique_ptr<string> p2(p1); //錯誤:unique_ptr不支持拷貝
unique_ptr<string> p3;
p3 = p2; //錯誤:unique_ptr不支持賦值
unique_ptr操作
unique_ptr< T > u1 空unique_ptr,可以指向類型爲T的對象。u1會使用delete來釋放它的指針。
unique_ptr< T, D > u2 u2會使用一個類型爲D的可調用對象來釋放它的指針
u = nullptr 釋放u指向的對象,將u置爲空
u.release() u放棄對指針的控制,返回指針,並將u置爲空
u.reset() 釋放u指向的對象
u.reset(q) 如果提供了內置指針q,令u指向這個對象;否則將u置爲空
u.reset(nullptr) 將u置爲空

雖然不能拷貝或賦值unique_ptr,但可以通過release或reset將指針的所有權從一個(非const)unique_ptr轉移到另一個unique_ptr。

//將所有權從p1轉移給p2
unique_ptr<string> p2(p1.release()); //release將p1置爲空
unique_ptr<string> p3(new string("hello"));
//將所有權從p3轉移給p2
p2.reset(p3.release()); //reset釋放了p2原來指向的內存

release成員返回unique_ptr當前保存的指針並將其置爲空。因此,p2被初始化爲p1原來保存的指針,而p1被置爲空。
調用release會切斷unique_ptr和它原來管理的對象間的聯繫。release返回的指針通常被用來初始化另一個智能指針或給另一個智能指針賦值,如果我們不用另一個智能指針來保存release返回的指針,我們的程序就要負責資源的釋放:

p2.release(); //錯誤:p2不會釋放內存,而且我們丟失了指針
auto p = p2.release(); //正確,但我們必須記得delete(p)
  1. weak_ptr類

weak_ptr是一種不控制所指向對象生存週期的智能指針,它指向一個shared_ptr管理的對象。將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。一旦最後一個指向對象的shared_ptr被銷燬,對象就會被釋放。即使有weak_ptr所指向的對象,對象也還是會被釋放。

weak_ptr 操作
weak_ptr < T > w 空weak_ptr 可以指向類型爲T的對象
weak_ptr < T> w(sp) 與shared_ptr指向相同對象的weak_ptr 。T必須能轉換爲sp指向的類型
w = p p可以是一個shared_ptr或一個weak_ptr 。賦值後w與p共享對象
w.reset() 將w置爲空
w.use_count() 與w共享對象的shared_ptr的數量
w.expired() 若w.use_count()爲0,返回true,否則返回false
w.lock() 如果expired爲true,返回一個空shared_ptr;否則返回一個指向w的對象的shared_ptr

創建一個weak_ptr,要用一個shared_ptr來初始化它:

auto p =make_shared<int>(42);
weak_ptr<int> wp(p); //wp弱共享p;p的引用計數未改變

由於對象可能不存在,我們不能直接使用weak_ptr訪問對象,而必須調用lock。此函數檢查weak_ptr指向的對象是否仍存在。存在,lock返回一個指向共享對象的shared_ptr。

if(shared_ptr<int> np = wp.lock())  //如果np不爲空則條件成立
{

}

27.將=default用於拷貝控制成員

class Sales_data{
public:
	//拷貝控制成員;使用default
	Sales_data() = default;
	Sales_data(const Sales_data&) = default;
	Sales_data& operator = (const Sales_data &);
	~Sales_data() = default;
};

Sales_data& Sales_data::operator=(const Sales_data&) = default;

當我們在類內使用=default修飾成員的聲明時,合成的函數將隱式地聲明爲內聯的。如果不希望合成的成員是內聯函數,應該對成員的類外定義使用=default。

28.使用=delete阻止拷貝類對象

新標準發佈之前,類是通過將其拷貝構造函數和拷貝賦值函數運算符聲明爲private的來阻止拷貝。而在新標準下,我們可以將拷貝構造函數和拷貝賦值運算符定義爲刪除的函數來阻止拷貝
刪除函數,就是我們雖然聲明瞭它們,但不能以任何方式使用它們。在函數的參數列表後面加上 =delete指出我們希望將它定義爲刪除的。

struct NoCopy{
	NoCopy() = default;  //使用過合成的默認構造函數
	NoCopy(const NoCopy&) = delete;   //阻止拷貝
	NoCopy &operator =(const NoCopy&) = delete; //阻止賦值
	~NoCopy() = default;  //使用合成的析構函數
};

注意:析構函數不能是刪除的成員。因爲如果析構函數被刪除,就無法銷燬此類型的對象了。

29.右值引用

爲了支持移動操作,新標準引入了一種新的引用類型——右值引用右值引用,就是必須綁定到右值的引用,通過&&而不是&來獲得右值引用。
一般而言,左值表達式表示的是一個對象的身份,而右值表達式表示的是對象的值。

int i = 42;
int &r = i;                 //正確:r引用i
int &&rr = i;              //錯誤:不能將一個右值引用綁定到一個左值上
int &r2 = i * 42;         //錯誤:i * 42是一個右值
const int &r3 = i * 42;  //正確:我們可以將一個const的引用綁定到一個右值上
int &rr2 = i *42;       //正確:將rr2綁定到乘法結果上

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

int &&rr1 = 42;   //正確:字面常量是右值
int &&rr2 = rr1;  //錯誤:表達式rr1是左值

雖然不能將一個右值引用直接綁定到一個左值上,但我們可以顯示地將一個左值轉換爲對應的右值引用類型。通過調用一個名爲move的新標準函數來獲得綁定到左值上的右值引用,該函數定義在utility中。

int &&rr3 = std::move(rr1);  //正確

30.移動構造函數和移動賦值運算符

爲了讓我們自己的類型支持移動操作,需要爲其定義移動構造函數和移動賦值運算符。除了完成資源移動,移動構造函數還必須確保移動後源對象處於這樣一個狀態——銷燬它是無害的。一旦資源完成移動,源對象必須不再指向被移動的資源。

StrVec::StrVec(StrVec && s) noexcept   //移動操作不應拋出任何異常
:elements(s.elements),first_free(s.first_free),cap(s.cap)
{
	//令s進入這樣的狀態——對其運行析構函數是安全的
	s.elements = s.first_free = s.cap = nullptr;
}

如果沒有移動構造函數,右值也會被拷貝。

與拷貝構造函數不同,移動構造函數不分配任何新內存。它接管給定的StrVec中的內存。在接管內存之後,將給定對象中的指針都置爲nullptr。這樣就完成了從給定對象的移動操作,此對象將繼續存在。

我們編寫一個移動操作時,確保移動後源對象進入一個可析構的狀態。

StrVec &StrVec::operator = (StrVec && rhs) noexcept
{
	if(this != &rhs)
	{
		elements = rhs.elements; 
		first_free = rhs.first_free;
		cap = rhs.cap;
		rhs.elements  = rhs.first_free = rhs.cap = nullptr;
	}
	return *this;
}

建議:不要隨意使用移動操作,因爲移動後源對象具有不確定狀態,對其調用std::move是危險的。當我們調用move時,必須絕對確認移動後源對象沒有其他用戶。

31.function類模板

function定義在functional頭文件中。

function的操作
function< T > f f是一個用來存儲可調用對象的空function,這些課調用對象的調用形式應該與函數類型T相同
function< T > f(nullptr) 顯示地構造一個空function
function< T > f(obj) 在f中存儲可調用對象obj的副本
f 將f作爲條件:當f含有一個可調用對象時爲真;否則爲假
f(args) 調用f中的對象,參數是args
定義爲function< T >的成員的類型
result_type 該function類型的可調用對象返回的類型
argument_type 當T有一個或兩個實參時定義的類型。如果T只有一個實參,則argument_type是該類型的同義詞。
first_argument_type second_argument_type : T有兩個實參,則first_argument_type和second_argument_type分別代表兩個實參的類型
int add(int i, int j){return i + j;}
struct divide{
	int operator()(int denominator, int divisor)
	{
		return denominator / divisor;
	}
};

function<int(int, int)> f1 =add;  //函數指針
function<int(int, int)> f2 = devide(); //函數對象類的對象
function<int(int, int)> f3 = [] (int i, int j){return i * j;};  //lambda

cout << f1(4,2) << endl; //打印6
cout << f2(4,2) << endl; //打印2
cout << f3(4,2) << endl; //打印8

32.explicit類型轉換運算符

class SmallInt
{
public:
	//編譯器不會自動執行這一類型轉換
	explicit operator int() const{ return val; }
};

SmallInt si = 3; //正確:SmallInt的構造函數不是顯式的
si + 3; //錯誤:此處需要隱式的類型轉換,但類的運算符是顯式的
static_cast<int> (si) + 3; //正確:顯式地請求類型轉換

33.虛函數的override指示符

C++11 中的 override 關鍵字,可以顯式的聲明派生類的虛函數,如果我們使用override標記了某個函數,但該函數並沒有覆蓋已存在的虛函數,此時編譯器將報錯。

class Base {
public:
    virtual void f1() const;
    virtual void f2(unsigned  int x);
};

class Derived: public Base {
public:
    virtual void f1() override;
    virtual void f2(unsigned int x) override;
};

這樣,即使不小心漏寫了虛函數重寫的某個苛刻條件,也可以通過編譯器的報錯,快速改正錯誤。

34.通過定義類爲final來阻止繼承

有時候我們會定義這樣一種類,我們不希望其他類繼承它,或者不想考慮它是否適合作爲一個基類。所以,c++11新標準提供了一種防止繼承發生的方法,即在類名後跟一個關鍵字 final。之後任何嘗試覆蓋該函數的操作都將引發錯誤。

class Base final
{
};
 
// 錯誤,Derive不能從Base派生。
class Derive: public Base
{
};

class Base2
{
public:
	virtual void Fun() final{}
};
 
class Derive2: public Base2
{
public:
    // 錯誤,不能覆蓋基類的函數。
    virtual void Fun() override{ }
};

35.模板類型別名

由於模板不是一個類型,我們不能定義一個typedef引用一個模板。即,無法定義一個typedef引用的Blob< T >。但是,新標準允許我們爲類模板定義一個類型別名。

template<typename T> using twin = pair<T, T>;
twin<string> authors; // author 是一個pair<string, string> 

36.尾置返回類型與類型轉換

template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
	return *beg; //返回序列中一個元素的引用
}

此例中通知編譯器fcn的返回類型與解引用beg參數的結果類型相同。

37.tuple類型

tuple是類似pair的模板。每個pair的成員類型都不相同,但每個pair都恰好有兩個成員。不同的tuple類型的成員類型也不相同,但一個tuple可以有任意數量的成員。定義在tuple頭文件中。

tuple<size_t, size_t, size_t> threeD; //三個成員的設置都爲0
tuple<string, vector<double>, int, list<int>> someVal("hello",{3.14,2.718},42,{0,1,2,3,4,5})

tuple的構造函數是explicit的,必須採用直接初始化語法。

tuple<size_t, size_t, size_t> threeD = {1, 2, 3}; //錯誤
tuple<size_t, size_t, size_t> threeD{1, 2, 3}; //正確
//也可以使用make_pair生成對象
auto item = make_tuple{"0-999-789-X", 3, 20.00};

要訪問一個tuple成員。就要使用一個名爲get的標準庫函數模板。

auto book = get<0> (item);  //返回item的第一個成員
auto book = get<1> (item)  ;//返回item的第二個成員
auto book = get<2> (item);  //返回item的第三個成員
get<2>(item) *= 0.8;  //打折20%

typedef decltype(item) trans; //trans是item類型
//返回trans類型對象中成員的數量
size_t sz = tuple_size<trans>::value; //返回3
//cnt的類型與item中第二成員相同
tuple_element<1, trans>::type cnt = get<1>(item); //cnt是一個int

38.有作用域的enum

枚舉屬於字面值常量類型。c++有兩種枚舉:限定作用域的和不限定作用域的。c++11標準庫引入了限定作用域的枚舉類型。形式:關鍵字enum class(或者等價的使用enum struct),隨後是枚舉類型名字以及用花括號括起來的以逗號分隔的枚舉成員,最後是一個分號。

//限定作用域的枚舉類型
enum class open_modes{
	intput,
	output,
	append
};

//不限定作用域的枚舉類型
enum color{
	red,
	yellow,
	green
};

在限定作用域的枚舉類型中,枚舉成員的名字遵循常規的作用域準則,並且在枚舉類型的作用域外是不可訪問的。而在不限定作用域的枚舉類型中,枚舉成員的作用域與類型本身的作用域相同。

enum color {red, yellow, green};   //不限定作用域的枚舉類型
enum stoplight{red, yellow, green}; //錯誤:重複定義了枚舉成員
enum class peppers{red, yellow, green}; //正確:枚舉成員被隱藏了

color eyes = green; //正確:不限定作用域的枚舉類型的枚舉成員位於有效的作用域中
peppers p = green; //錯誤:peppers的枚舉成員不在有效的作用域中 corlor::color

color hair = color::red; //正確:允許顯示的訪問枚舉成員
peppers p2 = peppers::red; //正確:使用peppers的red

enum是由某種類型表示的。在c++11中,可以在enum的名字後面加上冒號以及我們想在enum中使用的類型。

enum intValues: unsigned long long{
	charType = 255, shortType = 65535, intType = 65535,
	longType = 4294967295UL,
	long_longType = 1844865612314567UL
};

默認情況下,限定作用域的enum成員類型是int。對於不限定作用域的枚舉類型,其枚舉成員不存在默認類型,我們只知道成員的潛在類型足夠大,肯定能夠容納枚舉值。

發佈了56 篇原創文章 · 獲贊 18 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章