C 統一初始化語法(列表初始化)

2021-10-05 15:27:34 字數 3818 閱讀 6463

不格**

要是世上不曾存在c++14和c++17該有多好!constexpr是好東西,但是讓編譯器開發者痛不欲生;新標準庫的確好用,但改語法細節未必是明智之舉,尤其是3年一次的頻繁改動。c++帶了太多歷史包袱,我們都是為之買賬的一員。

我沒那麼多精力考慮c++14/17的問題,所以本文基於c++11標準。

知其所以然,是學習c++越發複雜的語法的最佳方式。因此,我們從列表初始化的動機講起。

早在2023年,bjarne stroustrup就提出要統一c++中的初始化語法。這是因為在c++11以前,初始化存在一系列問題,包括:

雖然每乙個都有辦法解決,但加在一起將會變得非常複雜,對編譯器和開發者都是負擔。換句話說,唯一的需求就是一種統一的初始化語法,其適用範圍能涵蓋先前的各種問題。

於是,列表初始化誕生了。

正因為列表初始化是為解決初始化問題而生,列表初始化的適用範圍是任何初始化。你能想到的都寫寫看,寫對就是賺到。

當然,全憑感覺是行不通的,還是得講點道理。列表初始化分為兩類:直接初始化與拷貝初始化。

在直接初始化中,無論建構函式是否explicit,都有可能被呼叫:

t object ;,用arg1, arg2, ...構造t型別的物件object——引數可以是乙個值,也可以是乙個初始化列表,下同;

class ; };,構造member成員物件——花括號的優勢在這裡體現出來,因為如果是圓括號的話member會被看作乙個函式;

t,構造臨時物件;

new t,構造heap上的物件;

class::class() : member ;,與直接初始化中的1類似,除了explicit以外都相同,operator=不會被呼叫;

object =,賦值語句,呼叫operator=

class ; };,與直接初始化中的2類似,explicit同理;

function( ),構造函式引數;

return ;,構造返回值;

object[ ],構造operator的引數;

u( ),構造u建構函式的引數。

4~7可以概括為,在該有乙個物件的地方,可以用乙個列表來構造它。這句話不是很嚴謹,因為除了operator()operator以外,其他運算子的引數都不能用列表初始化。

還有乙個要注意的地方,是列表初始化不允許窄化轉換(narrowing conversion),即可能丟失資訊的轉換,如float轉換為int

#include #include struct test

explicit test(int, int, int)

void operator(std::pair)

void operator()(std::pair)

};test test();}

int main()

; test t1 = ;

test t2 = ; // error

t;t();

}

列表不是表示式,更不屬於任何型別,所以decltype()是非法的,這還適用於模板引數推導。但是在以下幾種情況中,列表可以轉換成std::initializer_list例項:

直接初始化中,對應構造函式引數型別為std::initializer_list

拷貝初始化中,對應引數型別為std::initializer_list

繫結到auto上(列表元素型別必須嚴格一致),包括範圍for(range for)迴圈——當繫結auto&&時,變數的實際型別為std::initializer_list&&,這是**引用的特例。

std::initializer_list是為列表初始化提供的特殊的工具,是乙個輕量級的陣列**(proxy),其元素型別為const t。雖然你能在中看到std::initializer_list類模板的實現,但它實際上是與編譯器內部繫結的,你無法用乙個自己寫的相似的類替換它(除非改編譯器)。

std::initializer_list有建構函式、sizebeginend函式,用法與其他stl順序容器類似。迭代器解引用得到const t&型別,元素是不能修改的。

std::initializer_list帶來的最明顯的進步就是stl容器可以用列表來初始化,無需再寫那麼多push_back了。

struct test

test(std::initializer_list)

};

如果我寫test,哪個建構函式會被呼叫呢?回答這個問題,需要對與列表相關的過載決議有所了解。

對於涉及到建構函式的列表初始化(不涉及到的包括聚合初始化等),各建構函式分兩個階段考慮:

如果有建構函式第乙個引數為std::initializer_list,沒有其他引數或其他引數都有預設值,則匹配該建構函式(這裡似乎允許窄化轉換,我測試起來也是如此)——std::initializer_list優先順序高

否則,所有建構函式參與過載決議,除了窄化轉換不允許,以及拷貝初始化與explicit的衝突依然有效。

所以上面那段程式中test會匹配第二個建構函式。

如果有多個std::initializer_list過載呢?眾所周知,過載決議中引數轉換有完美、提公升、轉換三個等級,std::initializer_list引數的轉換等級定義為所有元素中最差的(不允許窄化轉換),然後找出等級最高的呼叫,如果有多個則為二義呼叫。

如果沒有std::initializer_list過載呢?由於從列表到引數本身就是轉換,屬於最差的等級,如果有多個函式可以通過引數轉換後匹配,則該呼叫就是二義呼叫;只有當只有乙個函式可行時才合法。

列表初始化是一種萬能的初始化語法,適用範圍廣導致其規則比較複雜,我們應當結合其動機來理解標準規定的行為。

列表初始化包括直接初始化與拷貝初始化,後者涵蓋了引數與返回值等情形。當我們不想要隱式拷貝初始化時,要用explicit關鍵字來拒絕。

列表不屬於任何型別,但一些情況下可以轉換成std::initializer_list。在過載決議中,std::initializer_list有更高的優先順序。

初始化 1 預設初始化 列表初始化

初始化的基本概念 事實 初始化和賦值是兩個完全不同的操作。初始化,是建立變數時賦予其乙個初始值。賦值,是把物件的當前值擦除,用乙個新值代替。列表初始化 p39 作為c 11新標準的一部分,用花括號 來初始化變數得到了全面應用。出於某些原因,這種初始化的方式叫做列表初始化。現在,無論是初始化物件還是某...

C 統一初始化

1.c 中指定初始化值的方式有4種方式 1 小括號 int x 0 2 等號 int x 0 3 大括號 int x 4 等號和大括號 int x 2.c 11統一初始化,即使用大括號初始化方式,其使用場景主要有以下3種 1 類非靜態成員指定預設值 2 為容器賦值 vector vec 3 對不支援...

c 11 統一的初始化和初始化列表

在c 11以前,程式設計師,或者初學者經常會感到疑惑關於怎樣去初始化乙個變數或者是乙個物件。初始化經常使用括號,或者是使用大括號,或者是復賦值操作。因為這個原因,c 11提出了統一初始化,以為著使用這初始化列表,下面的做法都是正確的。int value std vectorvi std vector...