前言
轉載請注明出處,感謝!
C++11 的新特性
1 變量和基本類型
1.1 long long 類型
擴展精度浮點數,10位有效數字
1.2 列表初始化
初始化的幾種不同形式,其中用花括號來初始化變量稱為列表初始化;
比如:
int i = 0;
int i = {0};
int i{0};
int i(0);
需要注意的是,當用於內置類型的變量時,這種初始化形式有一個重要的特點:如果我們使用初始化且初始值存在丟失信息的風險,則編譯器報錯;
例如:
long double ld = 3.1414141414;
int a{ld}, b = {ld}; //報錯
int c(ld), d = ld; //正確
cout << "a:" << a << "b:" << b << "c:" << c << "d" << d << endl;
運行的時候,a,b則會提示報錯信息:error: type 'long double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing],這是因為使用long double的值初始化int變量時可能會丟失數據,所以拒絕a和b的初始化請求;雖然c,d雖然沒有報錯,但是確實丟失了數據;
[這是為什么?]
1.3 nullptr 常量
有幾種生成空指針的方法:
int *p1 = nullptr; // 等價於int *p1 = 0;
int *p2 = 0;
int *p3 = NULL; // 等價於int *p3 = 0;
在新標准下,建議盡量使用新標准nullptr,nullptr是一種特殊類型的字面值,它可以被轉換成任意其它的指針類型,雖然其它的方式也是可以的;
1.4 constexpr 變量
將變量聲明為constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式;
聲明為constexpr的變量一定是一個常量,而且必須用常量表達式來初始化,比如說下面的情況則是不正確的:
int t = 10;
constexpr int q = t + 20;
cout << "q" << q << endl;
需要將t聲明為 const
才是正確的;
一般來說,如果你認定變量是一個常量表達式,那就把它聲明為constexpr類型;
1.5 類型別名聲明
使用類型別名可以使復雜的類型名字變得更簡單明了,易於理解和使用;
現在有兩種方法可以用來定義類型別名,一種是 typedef
,另一種則是新標准中的 using
;
#include <iostream>
using namespace std;
int add(int val) {
return 10 + val;
}
int main() {
typedef double dnum;
// 字符指針
typedef char *pstring;
// 函數
// 返回值類型為int,參數類型為int的函數
typedef int func(int);
// 函數指針,指向返回值類型為int,參數類型為int的函數
typedef int (*pfunc)(int);
// 函數引用,指向返回值類型為int,參數類型為int的函數
typedef int (&tfunc)(int);
pfunc pfunc_add = nullptr;
pfunc_add = add;
cout << "函數指針,result is " << pfunc_add(10) << endl;
tfunc tfunc_add = add;
cout << "函數引用,result is " << tfunc_add(10) << endl;
func &func_add = add; //這里使用指針或者引用都可以
cout << "函數,result is " << func_add(10) << endl;
// 數組
// 元素類型為int,個數為10的數組
typedef int arr[10];
// 數組指針,指向元素類型為int,個數為10的數組
typedef int (*parr)[10];
// 數組引用,綁定到元素類型為int,個數為10的數組
typedef int (&tparr)[10];
using dnum2 = double;
using pstring2 = char*;
using func2 = int(int);
using pfunc2 = int(*)(int);
using arr2 = int[10];
using parr2 = int(*)[10];
using tparr2 = int(&)[10];
std::cout << "Hello, World!" << std::endl;
return 0;
}
但是需要注意的是,如果某個類型別名指代的是復合類型或者常量,那么就會產生意想不到的后果;
比如說
typedef char *pstring;
const pstring cstr = 0;
按照我們正常的理解就是,將char*替換掉pstring,得到 const char* cstr;然而事實是pstring是一個字符指針,其基本數據類型是個指針,此時用此字符指針去聲明cstr,得到的是一個常量的字符指針,但是按照本意是指向char的常量指針,其基本類型是char,也就是說兩者修飾的東西是不一樣的!
但是如果是引用變量則沒有關系。
參考文章
1.6 auto 類型指示符
auto讓編譯器通過初始值來推算變量的類型,所以,其定義的變量必須要有初始值;
使用auto也能在一條語句中聲明多個變量;因為一條聲明語句只能有一個基本數據類型,所以該語句中所有的變量的初始基本數據類型都必須是一樣的;
- 頂層const:指針本身是一個常量;
- 底層const:指針指向的對象是一個常量;
int main() {
int i = 0;
const int ci = i;
auto b = &i; // b是一個整形指針(整數的地址就是指向整數的指針)
auto c = &ci; // c是一個指向整數常量的指針(對常量對象取地址是一種底層const)
return 0;
}
1.7 decltype 類型指示符
作用:選擇並返回操作數的數據類型;
1.7.1 decltype 和 auto 的區別
-
處理頂層const和引用的方式
const int ci = 0, &cj = ci; decltype(ci) x = 0; decltype(cj) y = x; decltype(cj) z; //報錯,因為cj是一個引用,因此作為引用的 z 必須要進行初始化
引用從來都是作為其所指對象的同義詞出現,也就是根據其所指的對象決定,只有在decltype處是個例外;
-
decltype的結果類型與表達式形式密切相關
int i = 0; decltype((i)) a; //報錯,因為a類型為 int&,必須進行初始化 decltype(i) b; //正確
需要注意的是,decltype((variable))的結果永遠是引用,而decltype(variable)結果只有當variable本身就是一個引用時才是引用,其實就是根據它的類型決定;
1.8 類內初始化
建議初始化每一個內置類型的變量,為了保證初始化后程序安全
2 字符串、向量和數組
2.1 使用 auto 或 decltype 縮寫類型
注意的是,如果表達式中已經有了size函數就不要再使用int了,因為size函數返回的是unsigned,如果其和有符號數進行比較的時候,有符號數會自動的轉換成一個比較大的無符號數。
2.2 范圍 for 語句
string str("hello world");
for (auto c : str) {
cout << c;
}
2.3 定義vector對象的vector(向量的向量)
編譯器根據模版vector生成了三種不同的類型,分別是:
vector<int>,vector<vector<int>>, vector<classname>
新的版本已經不需要再添加一個空格
vector<vector<int> >
2.4 vector對象的列表初始化
放在花括號里
2.5 容器的cbegin 和 cend 函數
2.6 標准庫begin 和 end 函數
2.7 使用auto和decltype簡化聲明
3 表達式
3.1 除法的舍入規則
新標准中,一律向0取整(直接切除小數部分)
double a = 12/5;
cout << a << endl;
輸出結果為2,刪掉了小數部分;
3.2 用大括號包圍的值列表賦值
3.3 將sizeof用於類成員
使用作用域運算符來獲取類成員的大小是許可的;
- 對數組執行sizeof運算得到整個數組所占空間的大小,等價於對數組中
所有的元素各執行一次sizeof運算並將所得結果求和; - 對string對象或vector對象執行sizeof運算只返回該類型固定部分的大小,不會計算對象中的元素占用多少空間;
4 語句
4.1 范圍for語句
5 函數
5.1 標准庫 initializer_list類
可以處理不同數量實參的函數,前提是實參類型要相同;
其提供的操作包括:
- 默認初始化
- 列表初始化
- 拷貝對象
- size
- begin
- end
5.2 列表初始化返回值
函數可以返回花括號包圍的值的列表,如果列表為空,臨時量執行初始化,否則,返回的值由函數的返回類型決定;
vector<string> getAddress(int num) {
if (num == 0) {
return {};
}
return {"address1", "address2", "address3"};
}
5.3 定義尾置返回類型
任何函數的定義都能夠使用尾置來返回,最適用於返回類型比較復雜的情況;
比如:
// 返回類型為指針,該指針指向含有10個整數的數組
auto func(int i) -> int(*)[10] {
int arr[10] = {0};
return &arr;
}
5.4 使用decltype簡化返回類型定義
int odd[] = {1, 3, 5, 7, 9};
int even[] = {2, 4, 6, 8, 10};
// 返回一個指針,指向數組
decltype(odd) *getArr(int i) {
return (i % 2) ? &odd : &even;
}
decltype並不會因為獲取的是數組,則返回的是指針,還是需要在函數名前面加上 *
;
5.5 constexpr函數
是指能用於常量表達式的函數;
需要遵從幾項約定:
- 函數的返回類型以及所有形參的類型都是字面值類型(只能用它的值來稱呼它);
- 函數體中必須有且只有一條return語句(C++14不再做要求);
- 必須非virtual
constexpr int func2() {
return 10;
}
int main() {
int arr[func2() + 10] = {0};
return 0;
}
6 類
6.1 使用=default生成默認構造函數
如果實現了默認的構造函數,編譯器則不會自動生成默認版本;可以通過使用關鍵字 default
來控制默認構造函數的生成,顯示的指示編譯器生成該函數的默認版本;
class MyClass{
public:
MyClass()=default; //同時提供默認版本和帶參版本
MyClass(int i):data(i){}
private:
int data;
};
比如說如果想要禁止使用拷貝構造函數,則使用關鍵字 delete
;
class MyClass
{
public:
MyClass()=default;
MyClass(const MyClass& )=delete;
};
int main() {
MyClass my;
MyClass my2(my); //報錯
return 0;
}
但需要注意的是,析構函數是不允許使用delete的,否則無法刪除;
6.2 類對象成員的類內初始化
當提供一個類內初始值時,必須以符號=或者花括號表示
6.3 委托構造函數
一個委托構造函數使用它所屬類的其它構造函數執行它自己的初始化過程,或者說它把它自己的一些(或者全部)指責委托給了其它構造函數;
class MyClass
{
public:
MyClass() : MyClass(10) {}
MyClass(int d) : data(d){}
inline int getData() {return data;}
private:
int data;
};
int main() {
MyClass my;
cout << my.getData() << endl;
return 0;
}
6.4 constexpr構造函數
如果想要使得函數擁有編譯時計算的能力,則使用關鍵字 constexpr
同樣的編譯時使用對象:
class Square {
public:
constexpr Square(int e) : edge(e){};
constexpr int getArea() {return edge * edge;}
private:
int edge;
};
int main() {
Square s(10);
cout << s.getArea() << endl;
return 0;
}
如果成員函數標記為 constexpr
,則默認其是內聯函數,如果變量聲明為
constexpr
,則默認其是 const
;
參考文章
7 IO庫
7.1 用string對象處理文件名
新版本中增加了使用庫類型string對象作為文件名,之前只能使用C風格的字符數組;
ifstream infile("/hello.sh");
8 順序容器
補充:
順序容器的類型:
容器 | 描述 |
---|---|
vector | 可變大小數組。支持快速隨機訪問。在尾部之外的位置插入或者刪除元素時可能很慢 |
deque | 雙端隊列。支持快速隨機訪問。在頭尾位置插入/刪除速度很快 |
list | 雙向鏈表。只支持雙向雙向順序訪問。在list中任何位置進行插入/刪除操作 |
forward_list | 單向鏈表。只支持單向順序訪問。在鏈表任何位置進行插入/刪除操作都很快 |
array | 固定大小數組。支持快速隨即訪問。不能添加或刪除元素 |
string | 與vector相似的容器,但專門用於保存字符(字符數組,封裝了char而已)。隨機訪問速度快,在尾部插入、刪除速度快 |
總而言之,數組隨機訪問速度快,鏈表插入刪除速度快;
8.1 array和forward_list容器
-
array
#include <string> #include <iterator> #include <iostream> #include <algorithm> #include <array> int main() { // 使用聚合初始化來構造 std::array<int, 3> a1{ {1,2,3} }; // C++11中需要使用雙重花括號(而14中不需要) std::array<int, 3> a2 = {1, 2, 3}; // 不再需要等號了 std::array<std::string, 2> a3 = { {std::string("a"), "b"} }; // 支持基本的容器操作 std::sort(a1.begin(), a1.end()); std::reverse_copy(a2.begin(), a2.end(), std::ostream_iterator<int>(std::cout, " ")); // 支持范圍for for(auto& s: a3) std::cout << s << ' '; }
-
forward_list
和
list
的使用類似,就不貼代碼了;template < class T, class Alloc = allocator<T> > class forward_list;
Alloc,容器內部用來管理內存分配以及釋放的內存分配器的類型,默認使用的是
std::allocator<T>
,;
參考文章
8.2 容器的cbegin和cend函數
返回的是const的迭代器,當不需要寫訪問時,應使用cbegin和cend;
8.3 容器的列表初始化
8.4 容器的非成員函數swap
除了 array
外,swap不對任何元素進行拷貝、刪除或者插入操作,因此可以保證常數時間內完成;swap 只是交換了容器內部數據結構,不會交換元素,因此,除string
外,指向容器的迭代器、引用和指針在 swap 操作后都不會失效;
但是,array會真正的交換它們的元素。
8.5 容器insert成員的返回類型
在新標准下,接受元素個數或范圍的insert版本返回的是-指向第一個新加入元素的迭代器;
如果范圍為空,不插入任何元素,insert
操作會將第一個參數返回;
int main() {
list<string> lst;
auto itr = lst.begin();
string word;
while (cin >> word) {
itr = lst.insert(itr, word); //等價於push_front
}
return 0;
}
8.6 容器的emplace成員
當調用 push
或 insert
成員函數時,我們將元素類型的對象傳遞給它們,這些對象被拷貝到容器里面,但是,當我們調用一個 emplace
成員函數時,則是將函數傳遞給元素類型的構造函數,會使用這些參數在容器管理的內存中直接構造元素;包括三個函數:emplace_front
,emplace
,emplace_back
,分別對應着 push_front
,insert
,push_back
為頭部,指定位置,尾部;
class MyClass
{
public:
MyClass(int d) : data(d){}
inline int getData() {return data;}
private:
int data;
};
int main() {
vector<MyClass> mys;
mys.emplace_back(10);
mys.push_back(MyClass(20));
for (auto itr = mys.begin(); itr != mys.end(); itr++) {
cout << (*itr).getData() << endl;
}
return 0;
}
8.7 shrink_to_fit
調用該函數要求 deque
,vector
,string
退回不需要的內存空間;
int main() {
vector<int> nums;
nums.push_back(10);
nums.push_back(10);
nums.push_back(10);
nums.push_back(10);
nums.push_back(10);
cout << nums.capacity() << endl;
nums.shrink_to_fit();
cout << nums.capacity() << endl;
return 0;
}
返回結果為8,5
區別:
- reserve:預分配存儲區大小,即capacity的值
- resize:容器大小,即size的值
8.9 string的數值轉換函數
新標准中,引入多個函數實現數值數據和標准庫string之間的轉換:
函數 | 描述 |
---|---|
to_string(val) | 返回任意算術類型val的字符串 |
stoi(s, p, b) | int類型 |
stol(s, p, b) | long類型 |
stoul(s, p, b) | unsigned long類型 |
stoll(s, p, b) | long long類型 |
stoull(s, p, b) | unsigned long long類型 |
stof(s, p, b) | float類型 |
stod(s, p, b) | double類型 |
stold(s, p, b) | long double類型 |
9 泛型算法
9.1 lambda表達式
一個lambda具有一個返回類型、一個參數列表和一個函數體;
[capture list](parameter list) -> return type { function body }
9.2 lambda表達式中的尾置返回類型
9.3 標准庫bind函數
auto newCallable = bind(callable, arg_list);
例子(綁定類成員函數)
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;
class MyClass
{
public:
void fun1(void)
{
cout << "void fun1(void)" << endl;
}
int fun2(int i)
{
cout << "int fun2(int i)" << " i = " << i << endl;
return i;
}
};
int main()
{
MyClass my;
//使用類對象綁定
auto fun1 = bind(&MyClass::fun1, my);
fun1();
MyClass *p;
//使用類指針綁定,-1為占位符
auto fun2 = bind(&MyClass::fun2, p, _1);
int i = fun2(1);
cout << "i = " << i << endl;
cin.get();
return 0;
}
10. 關聯容器
關聯器類型
- map 關聯數組,保存關鍵字-值對
- set 關鍵字即值,即只保存關鍵字的容器
- multimap 關鍵字可重復出現的map
- multiset 關鍵字可重復出現的set
- unordered_map 用哈希函數組織的map
- unordered_set 用哈希函數組織的set
- unordered_multimap 用哈希函數組織的map,關鍵字可重復
- unordered_multiset 用哈希函數組織的set,關鍵字可重復
10.1 關聯容器的列表初始化
和順序容器差不多
10.2 列表初始化pair的返回類型
10.3 pair的列表初始化
10.4 無序容器
包括4個無序關聯容器(使用哈希函數和關鍵字類型的 == 運算符)
- unordered_map
- unordered_set
- unordered_multiset
- unordered_multimap
11 動態內存
已總結,略
11.1 智能指針
11.2 shared_ptr類
11.3 動態分配對象的列表初始化
11.4 auto和動態分配
11.5 unique_ptr類
11.6 weak_ptr類
11.7 范圍for語句不能應用於動態分配數組
因為分配的內存並不是一個數組類型
11.8 動態分配數組的列表初始化
可以對數組中的元素進行值初始化,方法是在大小之后跟一對括號;
int *pia = new int[10]; //10個未初始化的int
int *pia2 = new int[10](); //10個初始化為0的int
int *pia3 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
delete [] pia; //釋放
delete [] pia2;
delete [] pia3;
11.9 auto不能用於分配數組
雖然我們用空括號對數組中元素進行值初始化,但不能在括號中給出初始化器,這意味着不能用auto分配數組;
11.10 allocator::construct可使用任意構造函數
allocator分配的內存是未構造的,我們按需要在此內存中構造對象。在新標准庫中,construct成員函數接受一個指針和零個或多個額外參數,在給定位置構造一個元素。
int main() {
allocator<string> alloc;
string* str = alloc.allocate(4); //分配10個未初始化的string
auto itr = str;
alloc.construct(itr, "chengdu"); //chengdu
cout << str[0] << endl;
itr++;
alloc.construct(itr, "beijing"); //beijing
cout << str[1] << endl;
itr++;
alloc.construct(itr, 10, 'c'); //10個c組成的字符串
cout << str[2] << endl;
itr++;
alloc.construct(itr); //空字符串
cout << str[3] << endl;
for (int i = 0; i < 4; i++) {
cout << str[i] << endl;
}
// 銷毀
while (itr != str) {
alloc.destroy(itr--);
}
return 0;
}
為了使用allocate返回的內存,我們必須用construct構造對象,使用未構造的內存,其行為是未定義的;我們只有對真正構造了的元素進行destroy操作;
12 拷貝控制
12.1 將=default用於拷貝控制類對對象
可以類內或者類外修飾成員函數,如果是類內,合成的函數將隱式地聲明未內聯的,如果不希望這樣,則在類外聲明;
我們只能對具有合成版本的成員函數使用=default(即,默認構造函數或拷貝控制成員)
12.2 使用=delete用於拷貝控制成員
對於析構函數已刪除的類型,不能定義該類型的變量或釋放指向該類型動態分配的指針;
12.3 用移動類對象代替拷貝類對象
為了解決之前存在的性能問題,避免不必要的內存拷貝,新標准中有兩種方法來替代拷貝函數:移動函數和 move
移動構造函數
A(A && h) : a(h.a){
h.a = nullptr;
}
12.4 右值引用
右值引用就是必須綁定到右值的引用,通過 &&
來獲得右值引用;
12.4.1 區別左值和右值
-
左值:在賦值號左邊,可以被賦值的值,可以取地址;
-
右值:在賦值號右邊,取出值賦給其他變量的值;
-
左值引用:type & 引用名 = 左值表達式
-
右值引用:type && 引用名 = 右值表達式
int a = 0; //2
int b = a; //1
一個對象被用作右值時,使用的是它的內容(值),比如1中的 a
,被當作左值時,使用的是它的地址,比如2中的 a
,常規左值;
int main() {
int i = 1; //i為常規左值
int &r = i; //正確:r綁定到i上,r是一個引用
int &&rr = i; //錯誤:不能將一個右值引用綁定到左值i上
int &r2 = i * 1; //錯誤:等號右邊是一個右值,但左值引用只能綁定到左值上
int &&rr2 = i * 1; //正確:右值引用綁定到右值上
const int &r3 = i * 1; //正確:可以將一個const的左值引用綁定到右值上
return 0;
}
-
返回左值引用的函數、賦值、下標、解引用和前置遞增/遞減運算符,都是返回左值的表達式的例子,我們可以將一個左值引用綁定到這類表達式的結果上;
-
返回非引用類型的函數,連同算術、關系、位以及后置遞增/遞減運算符,都生成右值;我們不能將左值引用綁定到這類表達式上,但是我們可以將一個const的左值引用或者一個右值引用綁定到這類表達式之上;
12.5 標准庫move函數
我們可以銷毀一個移后源對象,也可以賦予它新值,但不能使用一個移后源對象的值
// 移動構造函數,參數 "arg.member" 是左值
A(A&& arg) : member(std::move(arg.member))
{
}
// 移動賦值函數
A& operator=(A&& other) {
member = std::move(other.member);
return *this;
}
12.6 移動構造函數和移動賦值
12.7 移動構造函數通常是noexcept
如果需要通知標准庫這是一個不會拋出異常的移動操作,可以在函數后面指明 noexcept
,如果在一個構造函數中,noexcept
出現在參數列表和初始化列表開始的冒號之間;
不拋出異常的移動構造函數和移動賦值運算符必須標記為noexcept
12.8 移動迭代器
新標准中可以通過調用標准庫的 make_move_iterator
函數將一個普通迭代器轉換為一個移動迭代器,這樣可以避免拷貝操作帶來的性能問題;
#include <iostream> // std::cout
#include <iterator> // std::make_move_iterator
#include <vector> // std::vector
#include <string> // std::string
#include <algorithm> // std::copy
int main() {
std::vector<std::string> foo(3);
std::vector<std::string> bar{ "one","two","three" };
std::copy(make_move_iterator(bar.begin()),
make_move_iterator(bar.end()),
foo.begin());
// bar now contains unspecified values; clear it:
bar.clear();
std::cout << "foo:";
for (std::string& x : foo) std::cout << ' ' << x;
std::cout << '\n';
return 0;
}
12.9 引用限定成員函數
引用限定符可以是 &
或者 &&
,分別指出 this
可以指向一個左值或者右值,引用限定符只能用於成員函數,而且必須同時出現在函數的聲明和定義中;
可以在參數列表之后使用引用限定符來指定this對象的左值與右值屬性;
- 若引用限定符為&,則表明this對象指向着左值對象;
- 若引用限定符為&&,則表明this對象指向着右值對象;
參考文章
13 重載運算與類型轉換
13.1 function類模版
function模版提供一種通用、多態的函數封裝。其實例可以對任何可調用的目標進行存儲、賦值和調用操作,這些目標包括函數、lambda表達式、綁定表達式以及其它函數對象等;
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i)
{
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const
{
std::cout << i << '\n';
}
};
int main()
{
// 保存自由函數
std::function<void(int)> f_display = print_num;
f_display(-9);
// 保存 lambda 表達式
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
// 保存 std::bind 的結果
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
// 保存成員函數
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
Foo foo(314159);
f_add_display(foo, 1);
// 保存成員函數和對象
using std::placeholders::_1;
std::function<void(int)> f_add_display2= std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);
// 保存成員函數和對象指針
std::function<void(int)> f_add_display3= std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);
// 保存函數對象
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
}
13.2 explicit類型轉換運算符
用來防止由構造函數定義的隱式轉換;
看看構造函數的隱式轉換,如果類的構造函數有一個參數,那么在編譯的時候就會有一個缺省的轉換操作,會將其參數轉化成類的對象;
class MyClass
{
public:
MyClass( int num );
}
// 編譯器會將10轉換成MyClass對象
MyClass obj = 10;
避免這種自動轉換的操作則需要 explicit
:
class MyClass
{
public:
explicit MyClass( int num );
}
MyClass obj = 10; //err,can't non-explict convert
其只能用在類內部的構造函數聲明上,不能用在類外部的函數定義上;
被聲明為explicit的構造函數通常比其non-explicit兄弟更受歡迎。因為它們禁止編譯器執行非預期(往往也不被期望)的類型轉換。除非我有一個好理由允許構造函數被用於隱式類型轉換,否則我會把它聲明為explicit。---Effective c++
14 面向對象程序設計
14.1 虛函數的override和final指示符
override
可以幫助程序員的意圖更加的清晰的同時讓編譯器可以為我們發現一些錯誤。其只能用於覆蓋基類的虛函數;
final
使得任何嘗試覆蓋該函數的操作都將引發錯誤,並不特指虛函數;
均出現在形參列表(包括任何const或者引用限定符)以及尾置返回類型之后
14.2 刪除的拷貝控制和繼承
如果函數在基類中被定義為是刪除的,則派生類對應的也是刪除的;
14.3 繼承的構造函數
委派和繼承構造函數是由C++11引進為了減少構造函數重復代碼而開發的兩種不同的特性;
a. 通過特殊的初始化列表語法,委派構造函數允許類的一個構造函數調用其它的構造函數
X::X(const string& name) : name_(name) {
...
}
X::X() : X("") { }
b. 繼承構造函數允許派生類直接調用基類的構造函數,一如繼承基類的其它成員函數,而無需重新聲明,當基類擁有多個構造函數時這一功能尤其有用:
class Base {
public:
Base();
Base(int n);
Base(const string& s);
...
};
class Derived : public Base {
public:
using Base::Base; // Base's constructors are redeclared here.
};
using聲明語句將令編譯器產生代碼,對於基類的每個構造函數,編譯器都在派生類中生成一個形參列表完全相同的構造函數;如果派生類含有自己的數據成員,則這些成員將會被默認初始化;
15 模版與泛型編程
15.1 聲明模版類型形參為友元
在新標准中,我們可以將模板類型參數聲明為友元:
template <typename Type> class Bar {
friend Type; //將訪問權限授予用來實例化Bar的類型
};
此處我們將用來實例化Bar的類型聲明為友元。因此,對於某個類型名Foo,Foo
將成 Bar<Foo>
的友元。
15.2 模版類型別名
新標准中允許我們為模版定義一個類型別名:
template<typename T> using twin = pair<T, T>;
twin<string> authors;
其中,twin是一個 pair<T,T>
;
15.3 模版函數的默認模版參數
template <typename T, typename F = less<T>>
int compare(const T &v1, const T &v2, F f= F()) {
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
就像之前能為函數參數提供默認實參一樣,compare有一個默認模版實參 less<T>
和一個默認函數實參 F()
參考文章
15.4 實例化的顯示控制
顯示實例化:在不發生函數調用的時候將函數模版實例化或者在不適用類模版的時候將類模版實例化,這可以避免實例化相同的模版所帶來的額外開銷;
- 函數模版實例化
template函數返回類型 函數模板名<實際類型列表>(函數參數列表)
- 類模版實例化
template class 類模板名<實際類型列表>
參考文章
15.5 模版函數與尾置返回類型
例子:
//尾置返回允許我們在參數列表之后聲明返回類型
template <typename T>
auto func(T beg, T end) -> decltype(*beg) {
return *beg; //返回序列中一個元素的引用
}
15.6 引用折疊規則
規則如下:
- 所有右值引用折疊到右值引用上仍然是一個右值引用,比如(A&& && 變成 A&&)
- 所有的其它引用類型之間的折疊都將會變成左值引用,比如(A& &變成A&,A& && 變成 A&,A&& & 變成 A&)
引用折疊只能應用於間接創建的引用的引用,如類型別名或者模版參數;
15.7 用static_cast將左值轉換為右值
可以通過類型轉換 static_cast<Type&&>
來將返回右值引用;
int s=101;
int&& foo(){
return static_cast<int&&>(s);
} //返回值為右值引用
int main() {
int i=foo(); //右值引用作為右值,在賦值運算符的右側
int&& j=foo(); //j是具名引用。因此運算符右側的右值引用作為左值
int* p=&j; //取得j的內存地址
}
參考文章
15.8 標准庫forward函數
右值引用至少解決了兩個問題:
- 實現
move
語義 - 完美轉發
完美轉發
有的時候,我們需要將一個函數中某一組參數原封不動的傳遞給另一個函數,這里不僅僅需要參數的值不變,而且需要參數的類型屬性(左值/右值)保持不變-完美轉發;
使用forward
- 原型:
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type& t );
template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t );
- 例子:
#include <iostream>
#include <utility>
#include <memory>
class A {
public:
A(int && n) { std::cout << "rvalue constructor -> n=" << n << std::endl;}
A(int& n) { std::cout << "lvalue constructor -> n=" << n << std::endl;}
};
template<class T, class U>
std::unique_ptr<T> make_unique1(U&& u) {
return std::unique_ptr<T>(new T(std::forward<U>(u)));
//return std::unique_ptr<T>(new T(u));
}
int main() {
int i = 24;
auto p1 = make_unique1<A>(666); // rvalue forwarding
auto p2 = make_unique1<A>(i); // lvalue forwarding
auto p3 = make_unique1<A>(std::move(i)); // rvalue forwarding
return 0;
}
但是如果我們沒有使用 forward
函數,結果則全部都調用的是 lvalue constructor;
參考文章
15.9 可變參數模版與轉發
右值引用+完美轉發+可變參數模版實現下面這個函數;
// args為右值引用,decltype用於返回值
template<class Function, class... Args>
auto FuncWrapper(Function && f, Args && ... args) -> decltype(f(std::forward<Args>(args)...))
{
return f(std::forward<Args>(args)...);
}
void test0()
{
cout <<"void"<< endl;
}
int test1()
{
return 1;
}
int test2(int x)
{
return x;
}
string test3(string s1, string s2)
{
return s1 + s2;
}
int main() {
int num = 10;
int && nnum = num + 10;
int & nnum2 = num;
FuncWrapper(test0); // 沒有返回值,打印1
cout << FuncWrapper(test1) << endl; // 沒有參數,有返回值,返回1
cout << FuncWrapper(test2, 1) << endl; // 有參數,有返回值,返回1
cout << FuncWrapper(test2, std::move(num)) << endl; // 有參數,有返回值,返回左值10
cout << FuncWrapper(test2, std::move(nnum2)) << endl; // 有參數,有返回值,返回左值引用10
cout << FuncWrapper(test2, nnum) << endl; // 有參數,有返回值,返回右值引用20
cout << FuncWrapper(test3, "aa", "bb") << endl; // 有參數,有返回值,返回"aabb"
return 0;
}
返回值分別為:
void
1
1
10
10
20
aabb
參考文章