列表初始化(List Initialization)
當(dāng)初始化列表與內(nèi)置類型的變量一起使用時(shí)汰瘫,這種初始化形式具有一個(gè)重要屬性:如果初始化器可能導(dǎo)致信息丟失狂打,編譯器將不允許我們列出內(nèi)置類型的初始化變量。
Code:
long double ld = 3.1415926536;
int a{ld}, b = {ld}; // error:narrowing conversion required
int c(ld), d = ld; // ok: but value will be truncated
編譯器拒絕初始化a
和b
混弥,因?yàn)槭褂?code>long double初始化int
可能會(huì)丟失數(shù)據(jù)趴乡。至少ld
的小數(shù)部分將被截?cái)啵硗?code>ld中的整數(shù)部分可能太大而不適合int
蝗拿。
這一新特性似乎與C++98區(qū)別不大晾捏,畢竟我們不太可能直接使用long double
初始化int
。然而在“模版編程和范型編程”中哀托,可能無意中發(fā)生這樣的初始化惦辛!
空指針(Null Pointers)
初始化指針最直接的方法是使用新標(biāo)準(zhǔn)引入的字面量nullptr初始化指針。 nullptr是一個(gè)具有特殊類型的字面量仓手,可以轉(zhuǎn)換為任何其他指針類型胖齐。 或者我們可以使用字面量0初始化一個(gè)指針。
較老的程序有時(shí)使用預(yù)處理程序變量NULL初始化指針嗽冒。該變量在cstdlib
頭文件中被定義為0呀伙。
現(xiàn)代C++程序通常應(yīng)該避免使用NULL,而應(yīng)該使用nullptr作為替代添坊。
Code:
int *p1 = nullptr; // equivalent to int *p1 = 0;
int *p2 = 0; // directly initializes p2 from the literal constant 0
// #include <cstdlib>
int *p3 = NULL; // equivalent to int *p3 = 0;
將int
型變量賦給指針是非法的剿另,即使該int
變量的值恰好為0。
Code:
int zero = 0;
int *pi = zero; // error: cannot assign an int to a pointer
初始化指針的注意事項(xiàng)
未初始化的指針是運(yùn)行時(shí)錯(cuò)誤(run-time errors)的常見原因贬蛙。與任何其他未初始化的變量一樣雨女,當(dāng)我們使用未初始化的指針時(shí)會(huì)發(fā)生什么是未定義的。 使用未初始化的指針幾乎總會(huì)導(dǎo)致運(yùn)行時(shí)崩潰阳准。然而調(diào)試這種崩潰往往非常困難氛堕。
我們建議初始化所有變量特別是指針。 如果可能僅在定義了欲指向的對(duì)象之后定義指針野蝇。如果指針沒有要指向的對(duì)象岔擂,那么就將指針初始化為nullptr
或0位喂,這樣程序就可以判斷這個(gè)指針沒有指向任何對(duì)象浪耘。
constexpr變量(constexpr Variables)
常量表達(dá)式是一個(gè)表達(dá)式乱灵,其值不能更改,并且可以在編譯時(shí)進(jìn)行計(jì)算七冲。
Code:
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
const int sz = get_size(); // sz is not a constant expression
雖然sz
是一個(gè)const int
類型的變量痛倚,但它不是一個(gè)常量表達(dá)式,因?yàn)樗某跏蓟鞯闹抵挥性谶\(yùn)行時(shí)才能獲得澜躺。
在新標(biāo)準(zhǔn)下蝉稳,我們可以通過使用constexpr聲明一個(gè)變量,從而要求編譯器去驗(yàn)證該變量是否為常量表達(dá)式掘鄙。
Code:
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
雖然我們不能使用普通函數(shù)作為constexpr變量的初始化函數(shù)耘戚,但新標(biāo)準(zhǔn)允許我們將某些函數(shù)定義為constexpr。 這些函數(shù)必須足夠簡單操漠,以便編譯器可以在編譯時(shí)對(duì)它們進(jìn)行評(píng)估收津。 我們可以在constexpr變量的初始化器中使用constexpr函數(shù)。
通常浊伙,最好將constexpr用于打算用作常量表達(dá)式的變量撞秋。
類型別名(Type Aliases)
通常我們使用typedef
來定義類型別名,新標(biāo)準(zhǔn)引入了另一種通過別名聲明來定義類型別名的方法:
Code:
using SI = Sales_item; // SI is a synonym for class Sales_item
別名聲明以關(guān)鍵字using
開頭嚣鄙,后跟別名和=
吻贿。 別名聲明將=
左側(cè)的名稱定義為右側(cè)顯示的類型的別名。類型別名是類型名稱哑子,可以出現(xiàn)在類型名稱出現(xiàn)的任何位置:
Code:
SI item; // same as Sales_item item
auto類型說明符(The auto Type Specifier)
auto類型說明符是C++11全新引入的舅列。
編寫程序過程中我們想要將表達(dá)式的值存儲(chǔ)在變量中并不罕見。 要聲明變量卧蜓,我們必須知道該表達(dá)式的類型帐要。 當(dāng)我們編寫程序時(shí),確定表達(dá)式的類型可能會(huì)非常困難烦却,有時(shí)甚至是不可能的宠叼。 在新標(biāo)準(zhǔn)下,通過使用auto類型說明符我們可以讓編譯器為我們找出類型其爵。 與類型說明符(例如double)命名特定類型不同冒冬,auto告訴編譯器要從初始化程序中推斷出類型。 因?yàn)榫幾g器對(duì)于auto說明符需要推斷的依據(jù)摩渺,所以使用auto作為其類型說明符的變量必須具有初始化器:
Code:
// the type of item is deduced from the type of the result of adding val1 and val2
auto item = val1 + val2; // item initialized to the result of val1 + val2
此處編譯器將根據(jù)val1 + val2
返回的類型來推斷item
的類型简烤。如果val1
和val2
是類Sales_item
的對(duì)象,那么item
將具有類型Sales_item
摇幻。如果val1
和val2
的類型是double
横侦,那么item
將是類型double
的變量挥萌。
與任何其他類型說明符一樣,我們可以使用auto定義多個(gè)變量枉侧。 因?yàn)槁暶髦簧婕皢蝹€(gè)基類型引瀑,所以聲明中所有變量的初始化器必須具有彼此一致的類型:
Code:
auto i = 0, *p = &i; // ok: i is int and p is a pointer to int
auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi
符合類型,const和auto
編譯器推斷auto的類型并不總是與初始化程序的類型完全相同榨馁。 相反憨栽,編譯器會(huì)調(diào)整類型以符合常規(guī)初始化規(guī)則。
首先翼虫,當(dāng)我們使用引用時(shí)屑柔,我們實(shí)際上正在使用被引用的對(duì)象的一個(gè)引用。 特別是當(dāng)我們使用引用作為初始化器時(shí)珍剑,初始化器是相應(yīng)的對(duì)象掸宛。 編譯器將該對(duì)象的類型用于auto的類型推導(dǎo):
Code:
int i = 0, &r = i;
auto a = r; // a is an int (r is an alias for i, which has type int)
其次,auto通常會(huì)忽略頂層const招拙。 但初始化時(shí)會(huì)保持低層const唧瘾,例如初始化器是指向const的指針時(shí):
Code:
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
如果我們想要推理出保留頂層const的類型,需要進(jìn)行顯示聲明:
Code:
const auto f = ci; // deduced type of ci is int; f has type const int
我們還可以指定對(duì)auto推理出的類型的引用迫像。 通常的初始化規(guī)則仍然適用:
Code:
auto &g = ci; // g is a const int& that is bound to ci (can't ignore the top-level const in ci)
auto &h = 42; // error: we can't bind a plain reference to a literal
const auto &j = 42; // ok: we can bind a const reference to a literal
當(dāng)我們要求引用由auto推理出的類型時(shí)劈愚,初始化器中的頂層const不會(huì)被忽略。 通常當(dāng)我們將引用綁定到初始化器時(shí)闻妓,const就不是頂層的了菌羽。
當(dāng)我們?cè)谕粋€(gè)語句中定義多個(gè)變量時(shí),重要的是要記住引用或指針是特定聲明符的一部分由缆,而不是聲明的基本類型的一部分注祖。 通常初始化器必須提供一致的自動(dòng)推導(dǎo)類型:(注:auto用于推導(dǎo)基本類型)
Code:
auto k = ci, &l = i; // k is int; l is int&
auto &m = ci, *p = &ci; // m is a const int&;p is a pointer to const int
// error: type deduced from i is int; type deduced from &ci is const int
auto &n = i, *p2 = &ci;
注:使用過程中應(yīng)該時(shí)刻注意由auto推理出的類型均唉!
decltype類型說明符(The decltype Type Specifier)
有時(shí)我們想要定義一個(gè)變量是晨,該變量具有編譯器從表達(dá)式推導(dǎo)出的類型,但不希望使用該表達(dá)式來初始化變量舔箭。 對(duì)于這種情況罩缴,新標(biāo)準(zhǔn)引入了第二個(gè)類型說明符decltype
,它返回其操作數(shù)的類型层扶。 編譯器分析表達(dá)式以確定其類型箫章,但不評(píng)估表達(dá)式:
Code:
decltype(f()) sum = x; // sum has whatever type f returns
這里,編譯器不調(diào)用函數(shù)f()
镜会,但它使用函數(shù)f()
返回的類型作為sum的類型檬寂。
decltype
處理頂層const
和引用的方式與auto
的處理方式略有不同。 當(dāng)我們對(duì)一個(gè)變量應(yīng)用decltype
時(shí)戳表,decltype
返回該變量的類型桶至,包括頂層const
和引用:
Code:
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized
值得注意的是昼伴,decltype
是唯一一個(gè)定義為引用的變量不被視為它所引用的對(duì)象的同義詞的說明符。
decltype和引用
當(dāng)我們將decltype
應(yīng)用于不是變量的表達(dá)式時(shí)镣屹,我們得到該表達(dá)式返回的類型圃郊。某些表達(dá)式將導(dǎo)致decltype
生成引用類型, 通常decltype
為該類表達(dá)式返回一個(gè)引用類型野瘦,這些表達(dá)式產(chǎn)生的對(duì)象可以作為左值:
Code:
// decltype of an expression can be a reference type
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized
此處r
是一個(gè)引用描沟,所以decltype(r)
是一個(gè)引用類型。如果我們想要獲得r
引用的類型鞭光,我們可以將r
應(yīng)用到表達(dá)式中(如:r + 0
),這個(gè)表達(dá)式將產(chǎn)生一個(gè)不是引用類型的值泞遗。
另一方面惰许,解除引用運(yùn)算符是decltype
返回引用的表達(dá)式的一個(gè)例子。 正如我們所見史辙,當(dāng)我們解引用指針時(shí)汹买,我們得到指針指向的對(duì)象。 而且聊倔,我們可以對(duì)該對(duì)象進(jìn)行賦值晦毙。 因此,由decltype(*p)
推導(dǎo)出的類型是int&
耙蔑,而不是無格式的int
见妒。
decltype
和auto
之間的另一個(gè)重要區(qū)別是decltype
所做的推論取決于其給定表達(dá)式的形式。 令人困惑的是將變量名用括號(hào)括起來將會(huì)影響decltype
返回的類型甸陌。 當(dāng)我們將decltype
應(yīng)用于沒有任何括號(hào)的變量時(shí)须揣,我們得到該變量的類型。 如果我們將變量的名稱包裝在一組或多組括號(hào)中钱豁,編譯器會(huì)將變量及括號(hào)計(jì)算為一個(gè)表達(dá)式耻卡。 變量是一個(gè)可以作為左值進(jìn)行賦值的表達(dá)式。結(jié)果是decltype
作用于這樣的表達(dá)式上將產(chǎn)生一個(gè)引用:
Code:
// decltype of a parenthesized variable is always a reference
decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int
decltype((variable))
decltype(variable)
variable
參考文獻(xiàn)
[1] Lippman S B , Josée Lajoie, Moo B E . C++ Primer (5th Edition)[J]. 2013.