這篇文章以《C++ Primer》(第五版)為基礎换途,結合自己的理解,將C++11的新特性加以總結、概括军拟,以加深印象同時方便自己查閱剃执。
1、long long類型
C++語言規(guī)定懈息,一個int
至少和一個short
一樣大肾档;一個long
至少和一個int
一樣大,一個long long
至少和一個long
一樣大辫继,其中數(shù)據(jù)類型long long
是在C++11中新定義的阁最。下表列出了C++標準規(guī)定的尺寸的最小值,同時允許編譯器賦予這些類型更大的尺寸骇两。
2、列表初始化
C++語言定義了初始化的好幾種不同形式姜盈,例如想要定義一個名為uints_sold的int變量并初始化為0低千,以下四條語句都可以做到:
int units_sold = 0;
int units_sold = {0}馏颂; //C++11新特性
int units_sold{0}示血; //C++11新特性
int units_sold(0);
int *pia = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
作為C++11新標準的一部分救拉,用花括號來初始化變量得到了全面的應用难审,而在此之前,這種初始化的形式僅在某些受限的場合下才能使用亿絮,這種初始化的形式稱為列表初始化告喊。無論是初始化對象還是某些時候為對象賦新值,都可以使用這樣一組由花括號括起來的初始值了派昧。
列表初始化還可以應用于為vector
等容器對象元素賦初值的方法:
vector<string> articles = {"a", "an", "the"};
list<string> authors = {"Milton", "Shakespeare"};
//關聯(lián)容器也適用:
set<string> exclude = {"the", "but", "and", "or"};
map<string, string> authors = { {"Joyce", "James"}, {"Austen", "Jane"} };
pair<string, int> word_count();
word_count.insert({str, 1}); //在新標準下黔姜,創(chuàng)建一個pair最簡方法就是利用列表初始化。
C++11新標準允許使用花括號括起來的初始化列表作為賦值語句的右側運算對象:
vector<int> v;
v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
同時蒂萎,C++11新標準還規(guī)定秆吵,函數(shù)可以返回花括號包圍的值的列表:
vector<string> process() {
......
return {"functionX", "okay"};
}
pair<string int> process(...) {
......
if(...)
return pair<string, int>(); //隱式構造返回值
else
return {str, str.size()}; //列表初始化
}
當用于內置類型的變量時,這種初始化形式有一個重要特點:如果使用列表初始化且初始值存在丟失信息的風險五慈,編譯器將報錯:
long double pi = 3.14159265;
int a{pi}, b = {pi}; //錯誤:轉換未執(zhí)行纳寂,因為存在丟失信息的危險
int c(pi), d = (pi); //正確:轉化執(zhí)行痛阻,且確實丟失了部分值
使用long double
的值初始化int變量可能丟失數(shù)據(jù)鸟召,所以編譯器拒絕了a
和b
的初始化請求。其中窒朋,至少pi
的小數(shù)部分會丟失聪轿,而且int也可能存不下pi
的整數(shù)部分爷肝。
3、nullptr常量
一下列出幾個生成空指針的方法:
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;
在過去的程序中,會用到一個名為NULL
的預處理變量來給指針賦值灯抛,這個變量在頭文件cstdlib中定義金赦,它的值就是0。當用到一個預處理變量時对嚼,預處理器會自動地將它替換為實際值夹抗,因此用NULL
初始化指針和用0初始化指針是一樣的。這種處理方式將會導致C++中重載特性發(fā)生混亂:
void fun(char *);
void fun(int);
對于上述兩個函數(shù)來說纵竖,因為NULL被定義為0漠烧,那么fun(NULL)
將會去調用void fun(int);
,從而違反直觀靡砌。為了解決上述問題已脓,C++11引入了nullptr
關鍵字,專門用來區(qū)分NULL
和0通殃。nullptr
的類型為nullptr_t
度液,能夠隱式的轉換為任何指針或成員指針的類型,也能和他們進行相等或者不等的比較画舌。
在新標準下堕担,現(xiàn)在的C++程序最好使用nullptr,同時盡量避免使用NULL曲聂。
4霹购、constexpr變量、constexpr函數(shù)及字面值常量類
constexpr變量
C++11新標準規(guī)定朋腋,允許將變量聲明為constexpr
類型以便由編譯器來驗證常量的值是否是一個常量表達式齐疙。聲明為constexpr
的變量一定是一個常量,而且必須用常量表達式初始化:
constexpr int a = 20; //20是常量表達式
constexpr int b = a + 1; //a + 1是常量表達式
constexpr int c = fun1(); //只有當fun1()是一個constexpr函數(shù)時旭咽,才是一條正確的語句
ps:一個constexpr
指針的初始值必須是nullptr
剂碴、NULL
或者是存儲于某個固定地址中的對象。
constexpr函數(shù)
constexpr
函數(shù)需要遵循幾項約定:函數(shù)的返回類型及所有形參的類型都得是字面值類型(算術類型轻专、引用和指針類型忆矛、枚舉類型、字面值常量類)请垛,且函數(shù)體中必須有且只有一條return
語句催训。
constexpr int fun1() { return 1; }
當fun1()
的聲明如上時,上文的c
的聲明語句正確宗收,因為編譯器能在程序編譯時驗證fun1()
函數(shù)返回的是常量表達式漫拭。在執(zhí)行該初始化任務時,編譯器把對constexpr
函數(shù)的的調用替換成其結果值混稽。為了能在編譯過程中隨時展開采驻,constexpr
函數(shù)被隱式地指定為內聯(lián)函數(shù)审胚。
同時,我們允許constexpr
函數(shù)的返回值并非一個常量:
constexpr size_t fun2(int cnt) { return fun1() * cnt; }
當fun2()
的實參是常量表達式時礼旅,它的返回值也是常量表達式膳叨,反之則不然:
int arr1[fun(2)]; //正確:fun2(2)是常量表達式,此時編譯器用相應的結果替換對正確:fun2(2)函數(shù)的調用
int i = 2;
int arr2[fun(i)]; //錯誤:fun2(i)不是常量表達式痘系,編譯出錯
ps:constexpr
函數(shù)不一定返回常量表達式菲嘴。
5、字面值常量類
和其他類不同汰翠,字面值類型的類可能含有constexpr
函數(shù)成員龄坪。這樣的成員必須符合constexpr
函數(shù)的所有要求,他們是隱式const
的复唤。
數(shù)據(jù)成員都是字面值類型的聚合類(所有成員都是public
的健田;沒有定義任何構造函數(shù);沒有類內初始值佛纫;沒有基類抄课、也沒有virtual
函數(shù))是字面值常量類。如果一個類不是聚合類雳旅,但他符合下述要求,則也是一個字面值常量類:
- 數(shù)據(jù)成員都必須是字面值類型
- 類內至少含有一個
constexpr
構造函數(shù) - 如果一個數(shù)據(jù)成員含有類內初始值间聊,則內置類型成員的初始值必須是一條常量表達式攒盈;或者如果成員屬于某種類類型,則初始值必須使用成員自己的
constexpr
構造函數(shù) - 類必須使用析構函數(shù)的默認定義哎榴,該成員負責銷毀的對象
constexpr構造函數(shù)
盡管構造函數(shù)不能是const
的(當我們創(chuàng)建類的一個const
對象時型豁,直到構造函數(shù)完成初始化過程,對象才能真正取得其“常量”屬性尚蝌。因此迎变,構造函數(shù)在const
對象的構造過程中可以向其寫值),但是字面值常量類的構造函數(shù)可以是constexpr
函數(shù)。事實上飘言,一個字面值常量類必須至少提供一個constexpr
構造函數(shù)衣形。
constexpr
構造函數(shù)可以聲明成=default(下文有詳細的解釋)的形式。否則姿鸿,constexpr
構造函數(shù)就必須既符合構造函數(shù)的要求(意味著不能包含返回語句)谆吴,又符合constexpr
函數(shù)的要求(意味著它能擁有的唯一可執(zhí)行語句就是返回語句)。綜合上述兩點可知苛预,constexpr
構造函數(shù)一般來說應該是空的句狼。我們通過前置constexpr
關鍵字就可以聲明一個constexpr
構造函數(shù)了:
class Debug{
private:
bool hw;
bool io;
bool other;
public:
constexpr Debug(bool b = true): hw(b), io(b), other(b) {}
constexpr Debug(bool h, bool i, bool o): hw(h), io(i), other(o) {}
}
constexpr
構造函數(shù)必須初始化所有數(shù)據(jù)成員,初始值或者使用constexpr
構造函數(shù)热某,或者是一條常量表達式腻菇。
constexpr
構造函數(shù)用于生成constexpr
對象以及constexpr
函數(shù)的參數(shù)或返回類型胳螟。
const
適用于變量,并防止它們在代碼中被修改筹吐。
constexpr
告訴編譯器糖耸,這個表達式產(chǎn)生一個編譯時常量(根據(jù)編譯器的不同行為,常量又分為編譯時常量和運行時常量骏令,編譯時常量一定是運行時常量蔬捷,只是編譯時常量在編譯的時候就被計算執(zhí)行計算,并帶入到程序中一切可能用到它的計算式中榔袋。)周拐,所以它可以用在像數(shù)組長度,賦值給const
變量等等凰兑。**
6妥粟、類型別名聲明
新標準規(guī)定了一種新的方法,使用別名聲明來定義類型的別名吏够,這種方法用關鍵字using
作為別名聲明的開始勾给,其后緊跟別名和等號,其作用是把等號左側的名字規(guī)定成等號右側類型的別名锅知。用法與typedef
類似:
typedef double wages; //wages是double的同義詞
wages hourly, weekly; //等價于double hourly, weekly
using wages = double; //wages是double的同義詞
wages hourly, weekly; //等價于double hourly, weekly
由于模板不是一個類型播急,所以我們不能定義一個typedef
引用一個模板,但是新標準允許我們?yōu)轭惸0宥x一個類型別名:
template<typename T> using twin = pair<T, T>;
twin<string> authors; //authors是一個pair<string, string>
7售睹、auto類型指示符與尾指返回類型
一桩警、auto類型指示符
編程時常常需要把表達式的值賦給變量,這就要求在聲明變量的時候清楚地知道表達式的類型昌妹。C++11引入auto
類型說明符捶枢,用它就能讓編譯器替我們分析表達式所屬的類型。auto
讓編譯器通過初始值來推算變量的類型飞崖,因此auto
定義的變量必須有初始值烂叔。
auto item = val1 + val2;
編譯器將根據(jù)val1
和val2
相加的結果判斷item
的類型。如果val1
和val2
是類A
的對象固歪,則item
的類型就是A
蒜鸡;如果這兩個變量的類型是double
,則item
的類型就是double
牢裳,以此類推术瓮。
使用auto
也能在一條語句中聲明多個變量。因為一條聲明語句只能有一個基本數(shù)據(jù)類型贰健,所以該語句中所有變量的初始基本數(shù)據(jù)類型都必須一樣:
auto i = 0, *p = &i; //正確:i是整數(shù)胞四、p是整型指針
auto sz = 0, pi = 3.14; //錯誤:sz和pi的類型不一致
使用auto
進行類型推導的一個最為常見而且顯著的例子就是迭代器。在以前我們需要這樣來書寫一個迭代器:
for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)
而有了auto
之后可以:
// 由于cbegin()將返回vector<int>::const_iterator伶椿,所以itr也應該是vector<int>::const_iterator類型
for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);
注意:auto
不能用于函數(shù)傳參导狡,因此下面的做法是無法通過編譯的(考慮重載的問題约巷,我們應該使用模板):
int add(auto x, auto y);
此外独郎,auto
還不能用于推導數(shù)組類型。
其次氓癌,auto
一般會忽略掉頂層const
,底層const
會被保留下來疲迂。
二尤蒿、尾置返回類型
首先,我們思考一個問題:一個函數(shù)如何返回數(shù)組指針?
想要得到答案,我們需要明白下面的含義:
int array[10]; //array是一個含有10個整數(shù)的數(shù)組
int *p1[10]; //p1是一個含有10個指針的數(shù)組
int (*p2)[10] = &array; //p2是一個指針,它指向含有10個整數(shù)的數(shù)組
因此演怎,如果我們想定義一個返回數(shù)組指針的函數(shù)甘桑,則數(shù)組的維度必須跟在函數(shù)名字之后跑杭。返回數(shù)組指針的函數(shù)形式如下所示:
Type (*function(parameter_list)) [dimension]
//具體例子:
int (*func(int i)) [10];
在C++11中,我們可以使用尾置返回類型來簡化上述聲明方法窄做。任何函數(shù)的定義都能使用尾置返回類型组砚,但這種形式對于返回類型比較復雜的函數(shù)最有效。尾置返回類型跟在形參列表后面并以一個->
符號開頭。為了表示函數(shù)真正的返回類型跟在形參列表之后陈肛,我們在本應該出現(xiàn)的地方放置一個auto
:
//上述示例可以改寫為:
auto fun(int i) -> int (*) [10];
同時,上述方法還可以通過decltype
關鍵字進行改寫:
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
decltype(odd) *arrPtr(int i) {
return (i % 2) ? &odd : &even;
}
因為odd
是數(shù)組,所有arrPtr
返回一個指向含有5個整數(shù)的數(shù)組的指針啃匿。decltype
并不負責把數(shù)組類型轉換成對應的指針,所以decltype
的結果是個數(shù)組,想要表示arrPtr
返回指針還必須在函數(shù)聲明時加一個*
光稼。具體decltype
細節(jié)將在下面說明。
8、decltype類型指示符
C++11新標準引入了第二種類型說明賦decltype
播演,他的作用是選擇并返回操作數(shù)的數(shù)據(jù)類型翼闽。在此過程中,編譯器分析表達式并得到它的類型,卻不實際計算表達式的值:
decltype(fun()) sum = x; //sum的類型就是函數(shù)f的返回類型
編譯器并不實際調用函數(shù)fun()
撑毛,而是使用當調用發(fā)生時f的返回值類型作為sum
斩个。
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是一個引用,必須初始化
你可能會思考太闺,auto 能不能用于推導函數(shù)的返回類型莺奸∥卵В考慮這樣一個例子加法函數(shù)的例子逃延,在傳統(tǒng) C++ 中我們必須這么寫:
template<typename R, typename T, typename U>
R add(T x, U y) {
return x+y檩电;
}
這樣的代碼其實變得很丑陋料按,因為程序員在使用這個模板函數(shù)的時候,必須明確指出返回類型闷盔。但事實上我們并不知道add()
這個函數(shù)會做什么樣的操作坠非,獲得一個什么樣的返回類型盟迟。
在C++11中這個問題得到解決。雖然你可能馬上回反應出來使用decltype
推導x+y
的類型,寫出這樣的代碼:
decltype(x+y) add(T x, U y);
但事實上這樣的寫法并不能通過編譯凹炸。這是因為在編譯器讀到decltype(x+y)
時,x
和y
尚未被定義离赫。為了解決這個問題,C++11還引入了一個叫做尾置返回類型(trailing return type),利用auto
關鍵字將返回類型后置:
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
從C++14開始是可以直接讓普通函數(shù)具備返回值推導,因此下面的寫法變得合法:
template<typename T, typename U>
auto add(T x, U y) {
return x+y;
}
decltype
和auto
都可以用來推斷類型,但是二者有幾處明顯的差異:
- 1.
auto
忽略頂層const
而克,decltype
保留頂層const
拣度; - 2.對引用操作筋帖,
auto
推斷出原有類型,decltype
推斷出引用; - 3.對解引用操作,
auto
推斷出原有類型蛤高,decltype
推斷出引用沟涨; - 4.
auto
推斷時會實際執(zhí)行喜庞,decltype
不會執(zhí)行,只做分析。
總之在使用中過程中和const、引用和指針結合時需要特別小心。
9海蔽、使用auto或decltype推斷string::size_type類型
對于string
類的size
函數(shù)來說域仇,返回一個int
或是一個unsigned
都是合情理的泼掠,但是size
函數(shù)實際上返回的是一個string::size_type
類型的的值以體現(xiàn)標準庫類型與機器無關的特性括改。
盡管不太清楚string::size_type
類型的細節(jié)吝梅,但是有一點可以肯定,它是一個無符號類型的值装蓬,而且能足夠放下任何string
對象的大小乳蛾。
在C++11新標準中忆首,允許編譯器通過auto
或decltype
來推斷變量的類型:
auto len_1 = line.size();
decltype(line.szie()) len_2 = line.size();
ps:由于size
函數(shù)返回的是一個無符號整型筛欢,因此切記,如果表達式中混用了有符號數(shù)和無符號數(shù)將可能產(chǎn)生意想不到的結果。例如,假設n
是一個具有負值的int
娜遵,則表達式s.size() < n
的判斷結果幾乎肯定是true
。這是因為負值n
會自動轉化成一個比較大的無符號值。
10骡和、類內初始化
C++11新標準規(guī)定钮科,可以為數(shù)據(jù)成員提供一個 類內初始值 休里。創(chuàng)建對象時,類內初始值將用于初始化數(shù)據(jù)成員。沒有初始值的成員將被默認初始化。
class Window_mgr {
private:
//默認情況下,一個Window_mgr包含一個標準尺寸的空白Screen
std::vector<Screen> screens{Screen(24, 80, "")};
};
如我們之前所知的,類內初始值必須使用=
的初始化形或者花括號括起來的直接初始化形式。
ps:類內初始化對struct
和class
關鍵字都適用砂豌。實際上struct
關鍵字和class
關鍵字僅僅是形式上有所不同结借,我們可以用這個兩個關鍵字中的任何一個定義一個類咖熟,唯一的區(qū)別是薪韩,他們的默認訪問權限不太一樣。
11、范圍for語句
C++11新標準提供了一種語句:范圍for(range for)語句。這種語句遍歷給定序列中的每個元素并對序列中的每個值執(zhí)行某種操作,其語法是:
for(declaration: expression)
statement
其中驻粟,expression
部分是一個對象根悼,用于表示一個序列。declaration
部分負責定義一個變量蜀撑,該變量將被用于訪問序列中的基礎元素。每次迭代酷麦,declaration
部分的變量會被初始化為expression
部分的下一個元素值:
string str("hello world");
for(auto c : str) //使用auto關鍵字讓編譯器來決定c的類型矿卑,這里是char類型
cout << c << endl;
如果想要改變string
對象中字符的值,必須把循環(huán)變量定義成引用類型沃饶,以把整個string
對象轉換成大寫為例:
string str("hello world");
for (auto &c : str)
c = toupper(c);
cout << str << endl;
此外范圍for語句還適用于容器遍歷:
vector<int> v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (auto &r : v)
r *= 2; //將v中每個元素的值翻倍
注意:使用new
語句得到的動態(tài)數(shù)組不能使用范圍for語句母廷,因為動態(tài)分配的內存不是一個數(shù)組類型(維度是數(shù)組類型的一部分,而我們通過new
得到的只是數(shù)組指針)轻黑。
12、定義vector對象的vector(向量的向量)
早期版本的C++標準中琴昆,如果vector
的元素還是vector
(或者其他模板類型)氓鄙,則其定義的形式與現(xiàn)在的C++11新標準略有不同。過去业舍,必須在外層vector
對象的右尖括號和其元素類型之間添加一個空格抖拦,如應該寫成vector<vector<int> >
而非vector<vector<int>>
。
13舷暮、容器的cbegin和cend函數(shù)
begin
和end
返回的具體類型由對象是否為常量決定的态罪,如果對象是常量,begin
和end
返回cosnt_iterator
;如果不是常量脚牍,則返回iterator
:
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); //it1的類型是vector<int>::iterator
auto it2 = cv.begin(); //it2的類型是vector<int>::const_iterator
有時候這種默認的行為并非我們所需要的向臀,如果對象只需要讀操作而無需寫操作的話最好使用常量類型巢墅。為了便于專門得到const_iterator
類型的返回值诸狭,C++11新標準引入了cbegin
和cend
兩個新函數(shù):
auto it3 = v.cbegin(); //it3的類型也是vector<int>::const_iterator
14、數(shù)組的begin和end函數(shù)
為了讓數(shù)組指針的使用更加簡單君纫、更加安全驯遇,C++11新標準引入了兩個名為begin
和end
的函數(shù),這兩個函數(shù)與容器中的同名函數(shù)功能類似蓄髓,不過數(shù)組畢竟不是類類型叉庐,因此這兩個函數(shù)不是成員函數(shù):
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
int *beg = begin(ia); //指向ia首元素的指針
int *last = end(ia); //指向ia尾元素的下一個位置的指針
這兩個函數(shù)定義在iterator
頭文件中。下面程序負責找到arr
中的第一個負數(shù):
int *p_beg = begin(arr), *p_end = end(arr);
while(p_beg != p_end && *p_beg >= 0)
p_beg++;
return {"functionX", "okay"};
注意:使用new
語句得到的動態(tài)數(shù)組不能使用begin()
或end()
語句会喝,因為動態(tài)分配的內存不是一個數(shù)組類型(維度是數(shù)組類型的一部分,而我們通過new
得到的只是數(shù)組指針)陡叠。
15、除法的舍入規(guī)則
在除法運算中肢执,如果兩個運算對象的符號相同則商為正(如果不為0的話)枉阵,否則商為負。C++語言的早期版本允許結果為負值的商向上或向下取整预茄,C++11新標準規(guī)定商一律向0取整(直接切除小數(shù)部分)兴溜。
16罪郊、標準庫initializer_list類和可變參數(shù)模板
為了編寫能處理不同數(shù)量實參的函數(shù)校仑,C++11新標準提供了兩種新方法:
一、如果所有的實參類型相同鸠真,可以傳遞一個名為initializer_list
的標準庫類型诗宣,該類型是一種標準庫類型膘怕,用于表示某種特定類型的值的數(shù)組,其定義在同名的頭文件中召庞,提供以下操作:
initializer_list<T> lst; //默認初始化淳蔼,T類型元素的空列表
initializer_list<T> lst{a, b, c...} //lst的元素和初始值一樣多侧蘸;lst的元素是對應初始值的副本;列表中的元素是const的
lst2(lst) //拷貝復制鹉梨,拷貝后原始列表和副本共享元素
lst2 = lst //拷貝復制讳癌,拷貝后原始列表和副本共享元素
lst.size()
lst.begin()
lst.end()
和vecotr
一樣,initializer_list
也是一種模板類型存皂,定義時必須說明模板的類型晌坤。和vector
不一樣的是initializer_list
對象中的元素永遠是常量值,我們無法改變initializer_list
對象中的元素值旦袋。用法如下:
void error_msg(ErrCode e, initializer_list<string> il) {
......
}
error_msg(ErrCode(41), {"functionX", "okay"});
二骤菠、如果實參的類型不同,我們可以編寫一種特殊的函數(shù)疤孕,也就是所謂的可變參數(shù)模板商乎。
一個可變參數(shù)模板就是一個接受可變數(shù)目參數(shù)的模板函數(shù)或模板類〖婪В可變數(shù)目的參數(shù)被稱為參數(shù)包鹉戚。存在兩種參數(shù)包:模板參數(shù)包,表示零個或多個模板參數(shù)专控;函數(shù)參數(shù)包抹凳,表示零個或多個函數(shù)參數(shù)。
我們用一個省略號來支出一個模板參數(shù)或函數(shù)參數(shù)表示一個包伦腐。在一個模板參數(shù)列表中赢底,class...
或typename...
支出接下來的參數(shù)表示零個或多個類型的列表;一個類型名后面跟一個省略號表示零個或多個給定類型的非類型參數(shù)的列表柏蘑。在函數(shù)參數(shù)列表中幸冻,如果一個參數(shù)的類型是一個模板參數(shù)包,則此參數(shù)也是一個函數(shù)參數(shù)包:
//Args是一個模板參數(shù)包咳焚;rest是一個函數(shù)參數(shù)包
//Args表示零個或多個模板類型參數(shù)
//rest表示零個或多個函數(shù)參數(shù)
template <typename T, typename... Args>
void foo(const T &t, const Args... rest);
編譯器從函數(shù)的實參推斷模板參數(shù)類型洽损。對于一個可變參數(shù)模板,編譯器還會推斷包中參數(shù)的數(shù)目:
int i = 0; double d = 3.14; string s = "hello world";
foo(i, s ,42, d); //包中有個3參數(shù)黔攒,實例化為:void foo(const int&, const string&, const int&, const double&);
foo(s, 42, "hi"); //包中有個2參數(shù)趁啸,實例化為:void foo(const string&, const int&, const char[3]&);
foo(d, s); //包中有個1參數(shù),實例化為:void foo(const double&, const string&);
foo("hi"); //包中有個0參數(shù)督惰,實例化為:void foo(const char[3]&);
可變參數(shù)函數(shù)通常是遞歸的不傅。第一把調用處理包中的第一個實參,然后用剩余實參調用自身赏胚。
//用來終止遞歸并打印最后一個元素的函數(shù)
//此函數(shù)必須在可變參數(shù)版本的print定義之前聲明
template<typename T>
ostream &print(ostream &os, const T &t) {
return os << t; //包中最后一個元素之后不打印分隔符
}
//包中處理最后一個元素之外的其他元素都會調用這個版本的print
template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest) {
os << t << ","; //打印第一個實參
return print(os, rest...); //遞歸調用访娶,打印其他實參
}
給定print(cout, i, s, 42);
遞歸會執(zhí)行如下:
1、調用print(cout, i ,s, 42)
觉阅;t = i
崖疤;rest... = s, 42
2秘车、調用print(cout, s, 42)
;t = s
劫哼;rest... = 42
3叮趴、調用print(cout, 42)
,非可變參數(shù)版本的print
17、= default和 = delete的用法
= default
對于一個類來說:
class Sales_data {
Sales_data() = default;
};
因為該構造函數(shù)不接受任何實參权烧,所以它是一個默認構造函數(shù)眯亦,定義這個函數(shù)的目的是因為我們既需要其他形式的構造函數(shù),也需要默認的構造函數(shù)般码。如果我們需要默認的行為妻率,那么可以通過在參數(shù)列表后面寫上= default
來要求編譯器生成構造函數(shù),使用這種方式生成的構造函數(shù)比用戶定義的默認構造函數(shù)具有更高的效率板祝。= default
既可以和聲明一起出現(xiàn)在類的內部宫静,也可以作為定義出現(xiàn)在類的外部券时,出現(xiàn)在內部時會被默認為內聯(lián)的革为。
= delete
在新標準下套才,我們可以通過將拷貝構造函數(shù)和拷貝賦值運算符定義為刪除的函數(shù)來阻止拷貝疾掰。刪除的函數(shù)是這樣一種函數(shù):我們雖然聲明了他們父阻,但不能以任何方式使用他們。在函數(shù)的參數(shù)列表后面加上= delete
來指出我們希望將它定義為刪除的:
class NoCopy {
NoCopy() = default; //默認構造函數(shù)
NoCopy(const NoCopy &) = delete; //阻止拷貝
NoCopy &operator=(const NoCopy &) = delete; //阻止賦值
~NoCopy() = default; //合成的析構函數(shù)
}
與= default
不同唐片,= delete
必須出現(xiàn)在函數(shù)第一次聲明的時候逻翁,這個差異與這些聲明的含義在邏輯上是吻合的拱烁。一個默認的成員只影響為這個成員而生成的代碼噩翠,因此= default
直到編譯器生成代碼時才需要。而另一方面淹魄,編譯器需要知道一個函數(shù)是刪除的甲锡,以便禁止師徒使用它的操作羽戒,
與= default
的另一個不同之處是缤沦,我們可以對任何函數(shù)指定= default
(我們只能對編譯器可以合成的默認構造函數(shù)或拷貝控制成員使用= default
)。雖然刪除函數(shù)的主要用途是禁止拷貝控制成員易稠,但當我們希望引導函數(shù)匹配過程時缸废,刪除函數(shù)有時也是有用的。但是謹記,我們不能刪除析構函數(shù)企量。
18测萎、委托構造函數(shù)
C++11新標準擴展了構造函數(shù)初始值的功能,使我們可以定義所謂的委托構造函數(shù)届巩。一個委托構造函數(shù)使用它所屬類的其他構造函數(shù)執(zhí)行自己的初始化過程硅瞧,或者說它把它自己的一些(或全部)職責委托給了其他構造函數(shù):
class Sales_data {
public:
//非委托構造函數(shù)使用對應的實參初始化成員
Sales_data(std::string s, unsigned cnt, double price):
bookNo(s), units_sold(cnt), revenue(cnt * price) {}
//其余構造函數(shù)全部委托給另一個構造函數(shù)
Sales_data(): Sales_data("", 0, 0) {}
Sales_data(std::string s): Sales_data(s, 0, 0) {}
Sales_data(std::istream &is): Sales_data() { read(is, *this); }
......
};
19、用string對象處理文件流對象
ifstream in(ifile);
ofstream out;
在新的C++11標準中恕汇,文件名既可以是庫類型string
對象腕唧,也可以是C風格的字符數(shù)組。舊版本的標準庫只允許C風格的字符數(shù)組瘾英。
20枣接、array和forward_list容器
array
和forward_list
是C++新標準增加的類型。與內置數(shù)組相比缺谴,array
是一種更安全月腋、更容易使用的數(shù)組類型。與內置數(shù)組類似瓣赂,array
對象的大小是固定的榆骚。因此,array
不支持添加和刪除元素以及改變容器大小的操作煌集。forward_list
的設計目標是達到與最好的手寫的單向鏈表數(shù)據(jù)結構相當?shù)男阅芗酥R虼藳]有size
操作,因為保存或計算其大小就會比手寫鏈表多出額外的開銷苫纤。對其他容器而言碉钠,size
保證是一個快速的常量時間的操作。
ps:新標準庫容器比舊版本快的多卷拘。新標準庫容器的性能幾乎肯定與最精心優(yōu)化過的同類數(shù)據(jù)結構一樣好(通常會更好)『胺希現(xiàn)代C++程序應該使用標準庫容器,而不是更原始的數(shù)據(jù)結構栗弟,如內置數(shù)組污筷。通常使用vector
是最好的選擇,除非你有很好的理由選擇其他容器乍赫。
21瓣蛀、容器的非成員函數(shù)swap
swap
操作交換兩個相同類型的容器內容。調用swap
之后雷厂,兩個容器中的元素將會交換:
vecotr<string> s1(10); //10個元素
vecotr<string> s2(24); //25個元素
swap(s1, s2);
調用swap
后惋增,s1
將有24個元素,而s2
將有10個元素改鲫。處array
外诈皿,交換兩個容器內容的操作抱枕會很快————元素本身并未交換林束,swap
只是交換了兩個容器的內部數(shù)據(jù)結構。
ps:除array
外稽亏,swap
不對任何元素進行拷貝诊县、刪除或插入操作,因此可以保證在常數(shù)時間內完成措左。
在新標準中依痊,容器既提供了成員函數(shù)版本的swap
,也提供了非成員函數(shù)版本的swap
怎披。非成員版本的swap
在泛型變成中是非常重要的胸嘁。統(tǒng)一使用非成員版本的swap
是一個好習慣。
22凉逛、容器的insert成員返回類型
在新標準中性宏,接受元素個數(shù)或范圍的insert
版本返回指向第一個新加入元素的迭代器。在舊標準中状飞,這些操作返回void
毫胜。如果范圍為空,不插入任何元素诬辈,了insert
操作會將第一個參數(shù)返回酵使。
23、容器的emplace成員
C++新標準引入了三個新成員————emplace_front
焙糟、emplace
口渔、emplace_back
,這些操作構造元素而不是拷貝元素穿撮,分別對應與push_ront
缺脉、push
、push_back
悦穿。當我們調用一個emplace
成員函數(shù)時攻礼,則是將參數(shù)傳遞給元素類型的構造函數(shù)。emplace
成員使用這些參數(shù)在容器管理的內存空間中直接構造元素:
//在c的末尾構造一個Sales_data對象
c.emplace_back("987-0590353403", 25, 15.99); //正確:將會自動調用構造函數(shù)
c.push_back("987-0590353403", 25, 15.99); //錯誤:需要顯示調用構造函數(shù)生成一個對象再添加元素
c.push_back(Sales_data("987-0590353403", 25, 15.99));//正確:創(chuàng)建一個零食的Sales_data對象傳遞給push_back
24栗柒、管理容器的成員函數(shù)
//shrink_to_fit只適用于vector礁扮、string和deque
//capacity和reserve只適用于vector和string
c.shrink_to_fit(); //請求將capacity()減小為與size()相同大小
c.capacity(); //不重新分配內存的話,c可以保存多少元素
c.reserve(); //分配至少能容納n個元素的內存空間
在新標準中傍衡,我們可以調用shrink_to_fit
來要求特定容器退回不需要的內存空間深员。此函數(shù)支出我們不再需要任何多余的內存空間。但是蛙埂,具體的實現(xiàn)可以選擇忽略次請求。也就是說遮糖,調用shrink_to_fit
也并不一定保證退回內存空間绣的。
25、string的數(shù)值轉換
新標準引入了多個函數(shù),可以實現(xiàn)數(shù)值數(shù)據(jù)與標準庫string
之間的轉換:
int i =42;
string s = to_string(i);
double d = stod(s);
要轉換為數(shù)值的string
中第一個非空白符必須是數(shù)值中可能出現(xiàn)的字符:
string s2 = "pi = 3.14159265358";
d = stod(s2.substr(s2.find_first_of("+-.0123456789)));
string
和數(shù)值之間的轉換有以下函數(shù):
to_string(val); //一組重載函數(shù)屡江,返回val的string表示芭概,val可以是任何算數(shù)類型
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); //float
stod(s, p); //double
stold(s, p); //long double
ps:如果string
不能轉換一個數(shù)值,這些函數(shù)會拋出一個invalid_argument
異常惩嘉。
26罢洲、lambda表達式
匿名函數(shù)有函數(shù)體,但沒有函數(shù)名文黎。匿名函數(shù)是很多高級語言都支持的概念惹苗,如lisp語言在1958年首先采用匿名函數(shù)。正因為如此耸峭,C++11也同樣引入了lambda函數(shù)桩蓉。在C++11中,你可以在源碼中內聯(lián)一個lambda函數(shù),這就使得創(chuàng)建快速的劳闹、一次性的函數(shù)變得簡單了院究。
相同類似功能我們也可以使用函數(shù)對象或者函數(shù)指針實現(xiàn):函數(shù)對象能維護狀態(tài),但語法開銷大本涕,而函數(shù)指針語法開銷小业汰,卻沒法保存范圍內的狀態(tài)。lambda表達式正是結合了兩者的優(yōu)點菩颖。
聲明Lambda表達式
[capture list] (params list) mutable exception-> return type { function body };
[capture list] (params list) -> return type {function body};
[capture list] (params list) {function body};
[capture list] {function body};
capture list:捕獲外部變量列表
params list:形參列表
mutable指示符:用來說用是否可以修改捕獲的變量
exception:異常設定
return type:返回類型
function body:函數(shù)體
簡單的例子
在C++中我們對STL庫中的sort()運用十分頻繁蔬胯,接下來就是關于他的一個例子:
bool compare(int a,int b){
return a<b;
}
{
vector<int> vec{1,0,9,5,3,3,7,8,2};
sort(vec.begin(),vec.end(),compare);
//在C++11之前,我們使用STL的sort函數(shù)位他,可以提供一個謂詞函數(shù)來為sort改變其排序判斷標準
}
接下來是lambda表達式的形式:
{
vector<int> vec{1,0,9,5,3,3,7,8,2};
sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; }); // Lambda表達式
}
lambda不僅提高了代碼可讀性氛濒,且在例子中,這一個提供判斷依據(jù)的函數(shù)是只需調用一次鹅髓,在這時舞竿,lambda表達式就顯示出它的“即用即扔”的特點,很適合這種不需要重復調用且運用區(qū)域單一的情景(而不是去多寫一個compare的函數(shù))窿冯。當然我們也可以定義一個可調用對象用來代表lambda表達式
{
vector<int> vec{1,0,9,5,3,3,7,8,2};
auto f = [](int a, int b) -> bool { return a < b; };
sort(lbvec.begin(), lbvec.end(), f);
}
捕獲外部變量
Lambda表達式可以捕獲外面變量骗奖,但需要我們提供一個謂詞函數(shù)([capture list]在聲明表達式最前)。類似參數(shù)傳遞方式:值傳遞醒串、引入傳遞执桌、指針傳遞。在Lambda表達式中芜赌,外部變量捕獲方式也類似:值捕獲仰挣、引用捕獲、隱式捕獲缠沈。
值捕獲
int a = 123;
auto f = [a] { cout << a << endl; };
f(); // 輸出:123
a = 321;
f(); // 輸出:123
值捕獲和參數(shù)傳遞中的值傳遞類似膘壶,被捕獲的值在Lambda表達式創(chuàng)建時通過值拷貝的方式傳入错蝴,因此Lambda表達式函數(shù)體中不能修改該外部變量的值;同樣颓芭,函數(shù)體外對于值的修改也不會改變被捕獲的值顷锰。
引用捕獲
當以引用的方式捕獲一個變量時,必須保證在lambda執(zhí)行時變量是存在的亡问。
int a = 123;
auto f = [&a] { cout << a << endl; };
a = 321;
f(); // 輸出:321
引用捕獲的變量使用的實際上就是該引用所綁定的對象官紫,因此引用對象的改變會改變函數(shù)體內對該對象的引用的值。
隱式捕獲
除了顯式列出我們希望使用的來自所在函數(shù)的變量之外州藕,還可以讓編譯器根據(jù)lambda體中的代碼來推斷我們要使用那些變量束世。隱式捕獲有兩種方式,分別是
[=]:以值補獲的方式捕獲外部所有變量
[&]:表示以引用捕獲的方式捕獲外部所有變量慎框。
int a = 123 ,b=321;
auto df = [=] { cout << a << b << endl; }; // 值捕獲
auto rf = [&] { cout << a << b << endl; }; // 引用捕獲
其他捕獲方式
-
[ ]
: 不捕獲任何變量(無參函數(shù)) -
[變量1,&變量2, …]
: 值(引用)形式捕獲指定的多個外部變量 -
[this]
: 值捕獲this指針 -
[=, &x]
: 變量x以引用形式捕獲良狈,其余變量以傳值形式捕獲
Lambda表達式的參數(shù) - 參數(shù)列表中不能有默認參數(shù)
- 不支持可變參數(shù)
- 所有參數(shù)必須有參數(shù)名
參考文獻:https://blog.csdn.net/qq_43265890/article/details/83218413**
27、標準庫bind函數(shù)
對于捕獲局部變量的lambda表達式來說笨枯,很容易滿足謂詞的要求(無論一元謂詞還是二元謂詞)薪丁,但是如果想用函數(shù)的形式,解決謂詞長度的問題馅精,我們就需要用到bind
標準庫函數(shù)严嗜,它定義在functional
中≈薷遥可以將bind
函數(shù)看成一個通用的函數(shù)適配器漫玄,它接受一個可調用對象,生成一個新的可調用對象來適應原對象的參數(shù)列表压彭。調用bind
的一般形式為:
auto newCallable = bind(callable, arg_list);
其中睦优,newCallable
本身是一個可調用對象,arg_list
是一個逗號分隔的參數(shù)列表壮不,對應給定的callable
的參數(shù)汗盘。即,當我們調用newCallable
時询一,newCallable
會調用callable
并傳遞給它arg_list
中的參數(shù)隐孽。
arg_list
中的參數(shù)可能包括形如_n
的名字,其中n是一個整數(shù)健蕊。這些參數(shù)是占位符
菱阵,表示newCallable
的參數(shù),他們占據(jù)了傳遞給newCallable
的參數(shù)的“位置”缩功。數(shù)值n表示生成的可調用對象中參數(shù)的位置:_1
為newCallable
的第一個參數(shù)晴及,_2
為第二個參數(shù),以此類推:
auto check = bind(check_size, _1, 6);
bool b = check(s); //check(s)會調用check_size(s, 6);
//我們可以將原來基于lambda表達式的find_if調用替換為check_size的bind版本:
auto wc = find_if(words.begin(), words.end(), [sz](const string &a) {...})掂之;
auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));
名字_n
都定義在一個名為placeholders
的命名空間中抗俄,而這個命名空間本身定義在std
命名空間中脆丁,因此對_1
對應的using
聲明為:using std::placeholders::_1;
或者使用using namespace std::placeholders;
28世舰、無序容器
新標準定義了4個無序關聯(lián)容器(unordered associative container)动雹。這些容器不是使用比較運算符來組織元素,而是使用哈希函數(shù)和關鍵字類型的==
運算符跟压。
29胰蝠、智能指針
下一篇博客有詳細的說明,這里不在贅述震蒋。
30茸塞、右值引用
在看《C++ Primer》中有關右值方面的問題時,對右值的應用場景不是很理解查剖,所以在網(wǎng)上找了下有關右值的文章钾虐,其中一篇文章《4行代碼看看右值引用》覺得寫得不錯。本節(jié)以下內容是原文笋庄,轉載于https://www.cnblogs.com/qicosmos/p/4283455.html效扫。
概述
右值引用的概念有些讀者可能會感到陌生,其實他和C++98/03中的左值引用有些類似直砂,例如菌仁,c++98/03中的左值引用是這樣的:
int i = 0;
int& j = i;
這里的int&是對左值進行綁定(但是int&卻不能綁定右值),相應的静暂,對右值進行綁定的引用就是右值引用济丘,他的語法是這樣的A&&,通過雙引號來表示綁定類型為A的右值洽蛀。通過&&我們就可以很方便的綁定右值了摹迷,比如我們可以這樣綁定一個右值:
int&& i = 0;
這里我們綁定了一個右值0,關于右值的概念會在后面介紹郊供。右值引用是C++11中新增加的一個很重要的特性峡碉,他主是要用來解決C++98/03中遇到的兩個問題,第一個問題就是臨時對象非必要的昂貴的拷貝操作颂碘,第二個問題是在模板函數(shù)中如何按照參數(shù)的實際類型進行轉發(fā)异赫。通過引入右值引用,很好的解決了這兩個問題头岔,改進了程序性能塔拳,后面將會詳細介紹右值引用是如何解決這兩個問題的。
和右值引用相關的概念比較多峡竣,比如:右值靠抑、純右值、將亡值适掰、universal references颂碧、引用折疊荠列、移動語義肯尺、move語義和完美轉發(fā)等等排截。很多都是新概念蘸吓,對于剛學習C++11右值引用的初學者來說鹿霸,可能會覺得右值引用過于復雜晰洒,概念之間的關系難以理清茬腿。
右值引用實際上并沒有那么復雜殿怜,其實是關于4行代碼的故事系枪,通過簡單的4行代碼我們就能清晰的理解右值引用相關的概念了睬澡。本文希望帶領讀者通過4行代碼來理解右值引用相關的概念固额,理清他們之間的關系,并最終能透徹地掌握C++11的新特性--右值引用煞聪。
四行代碼的故事
第一行代碼的故事
int i = getVar();
上面的這行代碼很簡單斗躏,從getVar()函數(shù)獲取一個整形值,然而昔脯,這行代碼會產(chǎn)生幾種類型的值呢啄糙?答案是會產(chǎn)生兩種類型的值,一種是左值i栅干,一種是函數(shù)getVar()返回的臨時值迈套,這個臨時值在表達式結束后就銷毀了,而左值i在表達式結束后仍然存在碱鳞,這個臨時值就是右值桑李,具體來說是一個純右值,右值是不具名的窿给。區(qū)分左值和右值的一個簡單辦法是:看能不能對表達式取地址贵白,如果能,則為左值崩泡,否則為右值禁荒。
所有的具名變量或對象都是左值,而匿名變量則是右值角撞,比如呛伴,簡單的賦值語句:
int i = 0;
在這條語句中,i 是左值谒所,0 是字面量热康,就是右值。在上面的代碼中劣领,i 可以被引用姐军,0 就不可以了。具體來說上面的表達式中等號右邊的0是純右值(prvalue),在C++11中所有的值必屬于左值奕锌、將亡值著觉、純右值三者之一。比如惊暴,非引用返回的臨時變量饼丘、運算表達式產(chǎn)生的臨時變量、原始字面量和lambda表達式等都是純右值缴守。而將亡值是C++11新增的葬毫、與右值引用相關的表達式镇辉,比如屡穗,將要被移動的對象、T&&函數(shù)返回值忽肛、std::move返回值和轉換為T&&的類型的轉換函數(shù)的返回值等村砂。關于將亡值我們會在后面介紹,先看下面的代碼:
int j = 5;
auto f = []{return 5;};
上面的代碼中5是一個原始字面量屹逛, []{return 5;}是一個lambda表達式础废,都是屬于純右值,他們的特點是在表達式結束之后就銷毀了罕模。
通過地行代碼我們對右值有了一個初步的認識评腺,知道了什么是右值,接下來再來看看第行代碼淑掌。
第二行代碼的故事
T&& k = getVar();
第二行代碼和第一行代碼很像蒿讥,只是相比第一行代碼多了“&&”,他就是右值引用抛腕,我們知道左值引用是對左值的引用芋绸,那么,對應的担敌,對右值的引用就是右值引用摔敛,而且右值是匿名變量,我們也只能通過引用的方式來獲取右值全封。雖然第二行代碼和第一行代碼看起來差別不大马昙,但是實際上語義的差別很大,這里刹悴,getVar()產(chǎn)生的臨時值不會像第一行代碼那樣行楞,在表達式結束之后就銷毀了,而是會被“續(xù)命”颂跨,他的生命周期將會通過右值引用得以延續(xù)敢伸,和變量k的聲明周期一樣長。
右值引用的第一個特點
通過右值引用的聲明恒削,右值又“重獲新生”池颈,其生命周期與右值引用類型變量的生命周期一樣長尾序,只要該變量還活著,該右值臨時量將會一直存活下去躯砰。讓我們通過一個簡單的例子來看看右值的生命周期每币。如下代碼所示。
#include <iostream>
using namespace std;
int g_constructCount=0;
int g_copyConstructCount=0;
int g_destructCount=0;
struct A
{
A(){
cout<<"construct: "<<++g_constructCount<<endl;
}
A(const A& a)
{
cout<<"copy construct: "<<++g_copyConstructCount <<endl;
}
~A()
{
cout<<"destruct: "<<++g_destructCount<<endl;
}
};
A GetA()
{
return A();
}
int main() {
A a = GetA();
return 0;
}
為了清楚的觀察臨時值琢歇,在編譯時設置編譯選項-fno-elide-constructors用來關閉返回值優(yōu)化效果兰怠。
輸出結果:
construct: 1
copy construct: 1
destruct: 1
copy construct: 2
destruct: 2
destruct: 3
從上面的例子中可以看到,在沒有返回值優(yōu)化的情況下李茫,拷貝構造函數(shù)調用了兩次揭保,一次是GetA()函數(shù)內部創(chuàng)建的對象返回出來構造一個臨時對象產(chǎn)生的,另一次是在main函數(shù)中構造a對象產(chǎn)生的魄宏。第二次的destruct是因為臨時對象在構造a對象之后就銷毀了秸侣。如果開啟返回值優(yōu)化的話,輸出結果將是:
construct: 1
destruct: 1
可以看到返回值優(yōu)化將會把臨時對象優(yōu)化掉宠互,但這不是c++標準味榛,是各編譯器的優(yōu)化規(guī)則。我們在回到之前提到的可以通過右值引用來延長臨時右值的生命周期予跌,如果上面的代碼中我們通過右值引用來綁定函數(shù)返回值的話搏色,結果又會是什么樣的呢?在編譯時設置編譯選項-fno-elide-constructors券册。
int main() {
A&& a = GetA();
return 0;
}
輸出結果:
construct: 1
copy construct: 1
destruct: 1
destruct: 2
通過右值引用频轿,比之前少了一次拷貝構造和一次析構,原因在于右值引用綁定了右值汁掠,讓臨時右值的生命周期延長了略吨。我們可以利用這個特點做一些性能優(yōu)化,即避免臨時對象的拷貝構造和析構考阱,事實上翠忠,在c++98/03中,通過常量左值引用也經(jīng)常用來做性能優(yōu)化乞榨。上面的代碼改成:
const A& a = GetA();
輸出的結果和右值引用一樣秽之,因為常量左值引用是一個“萬能”的引用類型,可以接受左值吃既、右值考榨、常量左值和常量右值。需要注意的是普通的左值引用不能接受右值鹦倚,比如這樣的寫法是不對的:
A& a = GetA();
上面的代碼會報一個編譯錯誤河质,因為非常量左值引用只能接受左值。
右值引用的第二個特點
右值引用獨立于左值和右值。意思是右值引用類型的變量可能是左值也可能是右值掀鹅。比如下面的例子:
int&& var1 = 1;
var1類型為右值引用散休,但var1本身是左值,因為具名變量都是左值乐尊。
關于右值引用一個有意思的問題是:T&&是什么戚丸,一定是右值嗎?讓我們來看看下面的例子:
template<typename T>
void f(T&& t){}
f(10); //t是右值
int x = 10;
f(x); //t是左值
從上面的代碼中可以看到扔嵌,T&&表示的值類型不確定限府,可能是左值又可能是右值,這一點看起來有點奇怪痢缎,這就是右值引用的一個特點胁勺。
右值引用的第三個特點
T&& t在發(fā)生自動類型推斷的時候,它是未定的引用類型(universal references)牺弄,如果被一個左值初始化姻几,它就是一個左值;如果它被一個右值初始化势告,它就是一個右值,它是左值還是右值取決于它的初始化抚恒。
我們再回過頭看上面的代碼咱台,對于函數(shù)template<typename T>void f(T&& t)
,當參數(shù)為右值10的時候俭驮,根據(jù)universal references的特點回溺,t被一個右值初始化,那么t就是右值混萝;當參數(shù)為左值x時遗遵,t被一個左值引用初始化,那么t就是一個左值逸嘀。需要注意的是车要,僅僅是當發(fā)生自動類型推導(如函數(shù)模板的類型自動推導,或auto關鍵字)的時候崭倘,T&&才是universal references翼岁。再看看下面的例子:
template<typename T>
void f(T&& param);
template<typename T>
class Test {
Test(Test&& rhs);
};
上面的例子中,param是universal reference司光,rhs是Test&&右值引用琅坡,因為模版函數(shù)f發(fā)生了類型推斷,而Test&&并沒有發(fā)生類型推導残家,因為Test&&是確定的類型了榆俺。
正是因為右值引用可能是左值也可能是右值,依賴于初始化,并不是一下子就確定的特點茴晋,我們可以利用這一點做很多文章迂求,比如后面要介紹的移動語義和完美轉發(fā)。
這里再提一下引用折疊晃跺,正是因為引入了右值引用揩局,所以可能存在左值引用與右值引用和右值引用與右值引用的折疊,C++11確定了引用折疊的規(guī)則掀虎,規(guī)則是這樣的:
- 所有的右值引用疊加到右值引用上仍然還是一個右值引用凌盯;
- 所有的其他引用類型之間的疊加都將變成左值引用。
第三行代碼的故事
T(T&& a) : m_val(val){ a.m_val=nullptr; }
這行代碼實際上來自于一個類的構造函數(shù)烹玉,構造函數(shù)的一個參數(shù)是一個右值引用驰怎,為什么將右值引用作為構造函數(shù)的參數(shù)呢?在解答這個問題之前我們先看一個例子二打。如下代碼所示县忌。
class A
{
public:
A():m_ptr(new int(0)){cout << "construct" << endl;}
A(const A& a):m_ptr(new int(*a.m_ptr)) //深拷貝的拷貝構造函數(shù)
{
cout << "copy construct" << endl;
}
~A(){ delete m_ptr;}
private:
int* m_ptr;
};
int main() {
A a = GetA();
return 0;
}
輸出:
construct
copy construct
copy construct
這個例子很簡單,一個帶有堆內存的類继效,必須提供一個深拷貝拷貝構造函數(shù)症杏,因為默認的拷貝構造函數(shù)是淺拷貝,會發(fā)生“指針懸掛”的問題瑞信。如果不提供深拷貝的拷貝構造函數(shù)厉颤,上面的測試代碼將會發(fā)生錯誤(編譯選項-fno-elide-constructors),內部的m_ptr將會被刪除兩次凡简,一次是臨時右值析構的時候刪除一次逼友,第二次外面構造的a對象釋放時刪除一次,而這兩個對象的m_ptr是同一個指針秤涩,這就是所謂的指針懸掛問題帜乞。提供深拷貝的拷貝構造函數(shù)雖然可以保證正確,但是在有些時候會造成額外的性能損耗筐眷,因為有時候這種深拷貝是不必要的黎烈。比如下面的代碼:
上面代碼中的GetA函數(shù)會返回臨時變量,然后通過這個臨時變量拷貝構造了一個新的對象a浊竟,臨時變量在拷貝構造完成之后就銷毀了怨喘,如果堆內存很大的話,那么振定,這個拷貝構造的代價會很大必怜,帶來了額外的性能損失。每次都會產(chǎn)生臨時變量并造成額外的性能損失后频,有沒有辦法避免臨時變量造成的性能損失呢梳庆?答案是肯定的暖途,C++11已經(jīng)有了解決方法,看看下面的代碼膏执。如下代碼所示驻售。
class A
{
public:
A() :m_ptr(new int(0)){}
A(const A& a):m_ptr(new int(*a.m_ptr)) //深拷貝的拷貝構造函數(shù)
{
cout << "copy construct" << endl;
}
A(A&& a) :m_ptr(a.m_ptr)
{
a.m_ptr = nullptr;
cout << "move construct" << endl;
}
~A(){ delete m_ptr;}
private:
int* m_ptr;
};
int main(){
A a = Get(false);
}
//輸出:
construct
move construct
move construct
代碼3和2相比只多了一個構造函數(shù),輸出結果表明更米,并沒有調用拷貝構造函數(shù)欺栗,只調用了move construct函數(shù),讓我們來看看這個move construct函數(shù):
A(A&& a) :m_ptr(a.m_ptr)
{
a.m_ptr = nullptr;
cout << "move construct" << endl;
}
這個構造函數(shù)并沒有做深拷貝征峦,僅僅是將指針的所有者轉移到了另外一個對象迟几,同時,將參數(shù)對象a的指針置為空栏笆,這里僅僅是做了淺拷貝类腮,因此,這個構造函數(shù)避免了臨時變量的深拷貝問題蛉加。
上面這個函數(shù)其實就是移動構造函數(shù)蚜枢,他的參數(shù)是一個右值引用類型,這里的A&&表示右值针饥,為什么厂抽?前面已經(jīng)提到,這里沒有發(fā)生類型推斷打厘,是確定的右值引用類型修肠。為什么會匹配到這個構造函數(shù)?因為這個構造函數(shù)只能接受右值參數(shù)户盯,而函數(shù)返回值是右值,所以就會匹配到這個構造函數(shù)饲化。這里的A&&可以看作是臨時值的標識莽鸭,對于臨時值我們僅僅需要做淺拷貝即可,無需再做深拷貝吃靠,從而解決了前面提到的臨時變量拷貝構造產(chǎn)生的性能損失的問題硫眨。這就是所謂的移動語義,右值引用的一個重要作用是用來支持移動語義的巢块。
需要注意的一個細節(jié)是礁阁,我們提供移動構造函數(shù)的同時也會提供一個拷貝構造函數(shù),以防止移動不成功的時候還能拷貝構造族奢,使我們的代碼更安全姥闭。
我們知道移動語義是通過右值引用來匹配臨時值的,那么越走,普通的左值是否也能借助移動語義來優(yōu)化性能呢棚品,那該怎么做呢靠欢?事實上C++11為了解決這個問題,提供了std::move方法來將左值轉換為右值铜跑,從而方便應用移動語義门怪。move是將對象資源的所有權從一個對象轉移到另一個對象,只是轉移锅纺,沒有內存的拷貝掷空,這就是所謂的move語義。如下圖所示是深拷貝和move的區(qū)別囤锉。
再看看下面的例子:
{
std::list< std::string> tokens;
//省略初始化...
std::list< std::string> t = tokens; //這里存在拷貝
}
std::list< std::string> tokens;
std::list< std::string> t = std::move(tokens); //這里沒有拷貝
如果不用std::move坦弟,拷貝的代價很大,性能較低嚼锄。使用move幾乎沒有任何代價减拭,只是轉換了資源的所有權。他實際上將左值變成右值引用区丑,然后應用移動語義拧粪,調用移動構造函數(shù),就避免了拷貝沧侥,提高了程序性能可霎。如果一個對象內部有較大的對內存或者動態(tài)數(shù)組時,很有必要寫move語義的拷貝構造函數(shù)和賦值函數(shù)宴杀,避免無謂的深拷貝癣朗,以提高性能。事實上旺罢,C++11中所有的容器都實現(xiàn)了移動語義旷余,方便我們做性能優(yōu)化。
這里也要注意對move語義的誤解扁达,move實際上它并不能移動任何東西正卧,它唯一的功能是將一個左值強制轉換為一個右值引用。如果是一些基本類型比如int和char[10]定長數(shù)組等類型跪解,使用move的話仍然會發(fā)生拷貝(因為沒有對應的移動構造函數(shù))炉旷。所以,move對于含資源(堆內存或句柄)的對象來說更有意義叉讥。
第4行代碼故事
template <typename T>void f(T&& val){ foo(std::forward<T>(val)); }
C++11之前調用模板函數(shù)時窘行,存在一個比較頭疼的問題,如何正確的傳遞參數(shù)图仓。比如:
template <typename T>
void forwardValue(T& val)
{
processValue(val); //右值參數(shù)會變成左值
}
template <typename T>
void forwardValue(const T& val)
{
processValue(val); //參數(shù)都變成常量左值引用了
}
都不能按照參數(shù)的本來的類型進行轉發(fā)罐盔。
C++11引入了完美轉發(fā):在函數(shù)模板中,完全依照模板的參數(shù)的類型(即保持參數(shù)的左值透绩、右值特征)翘骂,將參數(shù)傳遞給函數(shù)模板中調用的另外一個函數(shù)壁熄。C++11中的std::forward正是做這個事情的,他會按照參數(shù)的實際類型進行轉發(fā)碳竟〔萆ィ看下面的例子:
void processValue(int& a){ cout << "lvalue" << endl; }
void processValue(int&& a){ cout << "rvalue" << endl; }
template <typename T>
void forwardValue(T&& val)
{
processValue(std::forward<T>(val)); //照參數(shù)本來的類型進行轉發(fā)。
}
void Testdelcl()
{
int i = 0;
forwardValue(i); //傳入左值
forwardValue(0);//傳入右值
}
輸出:
lvaue
rvalue
右值引用T&&是一個universal references莹桅,可以接受左值或者右值昌执,正是這個特性讓他適合作為一個參數(shù)的路由,然后再通過std::forward按照參數(shù)的實際類型去匹配對應的重載函數(shù)诈泼,最終實現(xiàn)完美轉發(fā)懂拾。
我們可以結合完美轉發(fā)和移動語義來實現(xiàn)一個泛型的工廠函數(shù),這個工廠函數(shù)可以創(chuàng)建所有類型的對象铐达。具體實現(xiàn)如下:
template<typename… Args>
T* Instance(Args&&… args)
{
return new T(std::forward<Args >(args)…);
}
這個工廠函數(shù)的參數(shù)是右值引用類型岖赋,內部使用std::forward按照參數(shù)的實際類型進行轉發(fā),如果參數(shù)的實際類型是右值瓮孙,那么創(chuàng)建的時候會自動匹配移動構造唐断,如果是左值則會匹配拷貝構造。
總結
通過4行代碼我們知道了什么是右值和右值引用杭抠,以及右值引用的一些特點脸甘,利用這些特點我們才方便實現(xiàn)移動語義和完美轉發(fā)。C++11正是通過引入右值引用來優(yōu)化性能偏灿,具體來說是通過移動語義來避免無謂拷貝的問題丹诀,通過move語義來將臨時生成的左值中的資源無代價的轉移到另外一個對象中去,通過完美轉發(fā)來解決不能按照參數(shù)實際類型來轉發(fā)的問題(同時翁垂,完美轉發(fā)獲得的一個好處是可以實現(xiàn)移動語義)铆遭。
本節(jié)以上內容轉載自https://www.cnblogs.com/qicosmos/p/4283455.html
31、可調用對象與function
C++語言中有幾種可調用對象:函數(shù)沿猜、函數(shù)指針疚脐、lambda
表達式、bind
創(chuàng)建的對象以及重載了函數(shù)調用運算符的類邢疙。不同類型的可調用對象可共享同一種調用形式。調用形式指明了調用返回的類型以及傳遞給調用的實參類型望薄∨庇危考慮下列不同類型的可調用對象:
int add(int i, int j) { return i + j; } //普通函數(shù)
auto mod = [](int i, int j) { return i % j; } //lambda表達式
class device { //函數(shù)對象類
int operator()(int denominator, int divisor) {
return denominator / divisor;
}
}
上面這些可調用對象分別對其參數(shù)執(zhí)行了不同的算術運算,盡管他們類型各不相同痕支,但是共享同一種調用形式:
int (int, int)
假設我們需要使用這些可調用對象構建一個簡單的桌面計算器颁虐。為實現(xiàn)這個目的,我們可以使用map
定義一個函數(shù)表用于存儲并指向這些可調用對象的“指針”卧须。當程序需要執(zhí)行某個特定和操作時另绩,從表中查找該調用的函數(shù)儒陨。
//構建從運算符到函數(shù)指針的映射關系,其中函數(shù)接受兩個int笋籽,返回一個int
map<string, int(*)(int, int)> binops;
//{"+", add}是一個pair
binops.insert({"+", add}); //正確:add是一個指向正確類型的函數(shù)指針
binops.insert({"%", mod}); //錯誤:mod不是一個函數(shù)指針
問題在于mod
是個lambda
表達式蹦漠,而每個lambda
表達式有它自己的類型,該類型與存儲在binops
中的值的類型不匹配车海。
我們可以使用一個名為function
的新標準庫類型解決上述問題笛园。function
是一個模板:
function<int(int, int)>
在這里我們聲明了一個function
類型,它表示可以接受兩個int
侍芝,返回一個int
的可調用對象研铆。因此,我們可以用這個新聲明的類型表示任意一種桌面計算器用到的類型:
function<int(int, int)> f1 = add;
function<int(int, int)> f2 = divide();
function<int(int, int)> f3 = [](int i, int j){ return i * j; };
//重新定義map
map<string, function<int(int, int)>> binops = {
{"+", add},
{"-", std::minus<int>()},
{"/", divide()},
{"*", [](int i, int j){ return i * j; }},
{"%", mod}
};
binops["+"](10, 5); //調用add(10, 5)
binops["-"](10, 5); //調用minus<int>對象的調用運算符
binops["/"](10, 5); //調用divide對象的調用運算符
binops["*"](10, 5); //調用lambda函數(shù)對象
binops["%"](10, 5); //調用lambda函數(shù)對象
但是對于重載函數(shù):
int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
map<string, function<int(int, int)>> binops;
binops.insert({"+", add}); //錯誤
解決上述二義性問題的途徑是存儲函數(shù)的指針而非函數(shù)的名字:
int (*fp)(int, int) = add;
binops.insert({"+", fp})州叠;
32棵红、顯式的類型轉換運算符
為了防止隱式類型轉換自動發(fā)生而產(chǎn)生意想不到的結果,C++11引入了顯式的類型轉換運算符:
class SamllInt {
public:
//編譯器不會自動執(zhí)行這一類型的轉換
explicit operator int() const { return val; }
}
33咧栗、虛函數(shù)的override指示符
C++11新標注允許派生類顯式地注明它使用某個成員函數(shù)覆蓋了它繼承的虛函數(shù)逆甜。具體做飯是在形參列表后面、或在const
成員函數(shù)的const
關鍵詞后面楼熄、或在引用成員函數(shù)的引用限定符后面添加一個關鍵字override
忆绰。
這么做的好處是在使得程序員的意圖更加清晰的同時讓編譯器為我們發(fā)現(xiàn)一些錯誤。
class B {
virtual void f1(int) const;
virtual void f2();
void f3();
}
clss D : B {
void f1(int) const override; //正確:與基類中的匹配
void f2(int) override; //錯誤:B中沒有匹配的函數(shù)
void f3() override; //錯誤:f3不是虛函數(shù)
void f4() override; //錯誤:B中沒有f4
}
我們使用override
所表達的意思是我們希望能覆蓋基類中的虛函數(shù)而實際上并未做到可岂,所以編譯器會保存错敢。
34、通過final阻止繼承
為實現(xiàn)一個類不被其他類繼承缕粹,C++11提供了final
關鍵字阻止繼承的發(fā)生:
class NoDerived fianl { /* */ }; //此類不看作為基類
我們還可以把某個函數(shù)指定為final
稚茅,如果我們以及把函數(shù)定義為final
了,則之后的任何嘗試覆蓋該函數(shù)的操作都將引發(fā)錯誤平斩。
class B {
void f1(int) const fianl;
}
class D : B {
void f1(int) const; //錯誤:B中的f1定義為final
}
35亚享、繼承的構造函數(shù)
C++11新標準中,派生類能夠重用其直接基類定義的構造函數(shù)绘面,方法是提供一條注明了(直接)基類名的using
聲明語句:
class Bulk_quote : public Disc_quote {
using Disc_quote::Disc_quote; //繼承Disc_quote的構造函數(shù)
}
通常情況下欺税,using
語句只在當前作用域可見,但是當用于構造函數(shù)時,using
則會使編譯器產(chǎn)生代碼。對于基類的每個構造函數(shù)补疑,編譯器都在派生類中生成一個形參列表完全相同的構造函數(shù)。繼承的構造函數(shù)等價于:
Bulk_quote(const std::string& book, double price, std::size_t qty, double disc) : Disc_quote(book, price, qty, disc) { }
36歼秽、tuple類型
當我們希望將一些數(shù)據(jù)組合成單一對象,但又不想麻煩地定義一個新數(shù)據(jù)結構來表示這些數(shù)據(jù)時情组,tuple
是非常有用的燥筷。tuple
類型及器伴隨類型和函數(shù)都定義在tuple
頭文件中箩祥。
不同tuple
類型的成員類型也不相同,但是一個tuple
可以有任意數(shù)量的成員肆氓。每個確定的tuple
類型的成員數(shù)目是固定的袍祖,但一個tuple
類型的成員數(shù)目可以與另一個tuple
類型不同。
定義和初始化tuple
:
tuple<size_t, size_t, size_t> threeD; //三個成員都設置為0
tuple<string, vector<double>, int, list<int>> someVal("constants", {3.14, 2.78}, 42, {0, 1, 2, 3, 4});
使用tuple
的默認構造函數(shù)做院,會對每個成員進行值初始化盲泛;也可以為每個成員提供一個初始值。tuple
的這個構造函數(shù)也是explicit
的键耕,因此我們必須使用直接初始化語法:
tuple<size_t, size_t, size_t> threeD = {1, 2, 3}; //錯誤
tuple<size_t, size_t, size_t> threeD{1, 2, 3}; //正確
標準庫定義了make_tuple
函數(shù)寺滚,我們還可以用它來生成tuple
對象:
auto item = make_tuple("hello world", 1, 2.0);
//item的類型為tuple<const char*, int, double>
要訪問一個tuple
成員,就要使用一個名為get
的標準庫函數(shù)模板屈雄。為了使用get
村视,我們必須指定一個顯示模板實參,它支出我們想要訪問第幾個成員酒奶。我們傳遞給get
一個tuple
對象蚁孔,它返回指定成員的引用:
auto book = get<0>(item); //返回第一個成員
auto cnt = get<1>(item); //返回第二個成員
auto price = get<2>(item) / cnt; //返回最后一個成員
get<2>(item) *= 0.8; //打折20%
尖括號中的值必須是一個整形常量表達式。與往常一樣惋嚎,我們從0開始計數(shù)杠氢,意味著get<0>
是第一個成員。
如果不知道一個tuple
準確的類型細節(jié)信息另伍,可以用兩個輔助類模板來查詢tuple
成員的數(shù)量和類型:
typedef decltype(item) trans; //trans是item的類型
//返回trans類型對象中成員的數(shù)量
size_t sz = tuple_size<trans>::value;
//cnt的類型與item中第二個成員相同
tuple_element<1, trans>::type cnt = get<1>(item)
為了使用tuple_size
或tuple_element
鼻百,我們需要知道一個tuple
對象的類型。tuple
的關系和相等運算符的行為類似容器的對應操作摆尝。這些運算符逐對比較左側tuple
和右側tuple
的成員温艇。只有兩個tuple
具有相同數(shù)量的成員時,我們才可以比較他們堕汞。
tuple
最常見用途是從一個函數(shù)返回多個值勺爱。
37、bitset類型
標準庫還定義了bitset
類讯检,使得位運算的使用更為容易琐鲁,并且能夠處理超過最長整形類型大小的位集合。bitset
類定義在頭文件bitset
中人灼。當我們定義一個bitset時绣否,需要聲明它包含多少個二進制位:
bitset<32> bitvec(1U); //32位;低位為1挡毅,其他位為0
大小必須是一個常量表達式。bitset
中的二進制位也是未命名的暴构,我們通過位置來訪問它們跪呈。二進制位尾指是從0開始編號的段磨。因此,bitvec
包含編號從0到31的32個二進制位耗绿。編號從0開始的二進制位被稱為低位苹支,編號到31結束的二進制位被稱為高位:
//bitvec1比初始值小误阻;初始值中的高位被丟棄
bitset<13> bitvec1(0xbeef); //二進制位序列為1111011101111
//bitvec2比初始值大债蜜;它的高位被置為0
bitset<20> bitvec2(0xbeef); //二進制位序列為00001011111011101111
//在64位機器中,long long 0ULL是64個0比特究反,因此~0ULL是64個1
bitset<128> bitvec3(~0ULL); //0~63位為1;63~127位為0
bitset<32> bitvec4("1100"); //2寻定、3兩位為1,剩余兩位為0
38精耐、正則表達式
regex
類表示一個正則表達式狼速。除了初始化和賦值操作之外,regex
還支持regex_match
卦停、regex_repalce
向胡、regex_search
、sregex_iterator
等操作惊完。C++正則表達式庫定義在頭文件regex
中僵芹。
39、隨機數(shù)引擎和隨機數(shù)分布類
C和C++都依賴與一個簡單的C庫函數(shù)rand
來生成隨機數(shù)小槐。此函數(shù)生成均勻分布的偽隨機整數(shù)拇派,每個隨機數(shù)的范圍在0和一個系統(tǒng)相關的最大值之間。
rand
函數(shù)有一些問題:一些程序需要不同范圍的隨機數(shù)本股。一些應用需要隨機浮點數(shù)攀痊。一些程序需要非均勻分布的數(shù)。而程序員為了解決這些問題而師團轉換rand
生成的隨機數(shù)的范圍拄显、類型或分布時苟径,常常會引入非隨機性。
定義在頭文件random
中的隨機數(shù)庫通過一組協(xié)作的類來解決這些問題:隨機數(shù)引擎類和隨機數(shù)分布類躬审。一個引擎類可以生成unsigned
隨機數(shù)序列棘街,一個分布類使用一個引擎類生成指定類型的遭殉、在給定范圍內的险污、服從特定概率分布的隨機數(shù)。
40拯腮、浮點數(shù)格式控制
操縱符scientific
改變流的狀態(tài)來使用科學計數(shù)法蚁飒。操縱符fixed
改變流的狀態(tài)來使用定點十進制淮逻。在新標準庫中,通過使用hexfloat
也可以強制浮點數(shù)使用十六進制格式爬早。新標準庫還提供另一個名為defaultfloat
的操縱符哼丈,它將劉恢復到默認狀態(tài)——根據(jù)要打印的值選擇計數(shù)法。
cout << "default format:" << 100 * sqrt(2.0) << '\n'
<< "scientific:" << scientific << 100 * sqrt(2.0) << '\n'
<< "fixed decimal:" << fixed << 100 * sqrt(2.0) << '\n'
<< "hexadecimal:" << hexfloat << 100 * sqrt(2.0) << '\n'
<< "yse defaults:" << defaultfloat << 100 * sqrt(2.0) << '\n'
//輸出結果:
default format:141.421
scientific:1.414214e+002
fixed decimal:141.421356
hexadecimal:0x1.1ad7bcp+7
use defaults:141.421
41凸椿、noexcept異常說明
對于用戶及編譯器來說削祈,預先知道某個函數(shù)不會拋出異常顯然大有裨益。首先知道函數(shù)不會拋出異常有助于簡化調用該函數(shù)的代碼脑漫;其次髓抑,如果編譯器確認函數(shù)不會拋出異常,它就能執(zhí)行某些特殊的優(yōu)化操作优幸,而這些優(yōu)化操作并不適用于可能出錯的代碼吨拍。
在C++11中,我們可以通過noexcept
關鍵字說明某個函數(shù)不會拋出異常:
void recoup(int) noexcept;
對于一個函數(shù)來說网杆,noexcept
說明要么出現(xiàn)在函數(shù)的所有聲明和定義語句中羹饰,要么一次也不出現(xiàn)。該說明應該在函數(shù)的尾置返回類型之前碳却。在typedef
或類型別名中則不能出現(xiàn)noexcept
队秩。在成員函數(shù)中,noexcept
說明符需要跟在const
及引用限定符之后昼浦,而在final
馍资、override
或虛函數(shù)的= 0
之前。
noexcept
說明符的實參常常與noexcept
運算符混合使用关噪。noexcept
運算符是一個一元運算符使兔,它返回值為一個bool
類型的右值常量表達式熊经,用于表示給定的表達式是否會拋出異常:
noexcept(recoup(i)) //如果recoup不拋出異常則結果為true
//更普通的形式是:
noexcept(e)
當e
調用的所有函數(shù)都做了不拋出說明且e
本身不含有throw
語句時悉盆,上述表達式為true
宏粤,否則為false
绍哎。我們可以使用noexcept
運算符得到如下的異常說明:
void f() noexcept(noexcept(g())); //f和g的異常說明一致
如果函數(shù)g
承諾了不會拋出異常,則f
也不會拋出異常海诲。