()和{}初始化的用法
- 大括號初始化可以應用的語境最為寬泛。可以避免令人苦惱的解析語法、可以阻止隱式窄化型別轉換
- 建構函式過載決議期間,只要有任何可能,大括號初始化物就會與帶有std::initializer_list型別的形參想匹配,即使其他過載版本更合適
- 使用兩個實參來建立std::vector<數值型別>結果會大相徑庭。這是大括號與小括號之間的一個明顯不同的例子
我們指定初始化的方式包括使用小括號、使用等號或者使用大括號。
int x(0); //使用小括號初始化
int y = 0; //使用等號初始化
int z{0}; //使用大括號初始化
int z = {0}; //等號加大括號的用法等同於使用大括號
很多人喜歡使用等號來書寫初始化語句,新手往往會認為這裡面會發生一次賦值,但是實際上是沒有的。我們使用int等內建型別不需要區分的這麼開,但是當我們使用自定義型別的時候,就必須區分開初始化和賦值的概念了。
Widget w1; //呼叫的是預設建構函式
Widget w2 = w1; //是初始化而並非賦值,呼叫的是複製建構函式
w1 = w2; //並非賦值,呼叫的是複製賦值運算子
C++11中引入了統一初始化:單一的、至少從概念上可以用於一切場合、表達一切意思的初始化。它的基礎是大括號形式。
大括號初始化可以表達之前無法表達之事。
使用大括號來指定容器的初始內容非常簡單:
std::vector<int> v{1,3,5}; //v的初始內容為1,3,5
大括號初始化可以為非靜態成員指定預設初始化值,C++11中也可以使用“=”初始化語法,但是不能使用小括號:
class Widget{
private:
int x{0};
int y = 0;
//int z(0); //錯誤
};
不可複製的物件可以採用大括號和小括號來初始化,但是不能使用“=”
std::atomic<int> ai1{0};
std::atomic<int> ai2(0);
//std::atomic<int> ai3 = 0; //錯誤
通過上述情況我們可以看到,這三種初始化方法中,只有大括號初始化方法適用所有場合。
不過大括號初始化有一項新特性,就是它禁止內建型別之間進行隱式窄化型別轉換。如果大括號內的表示式無法保證能夠採用進行初始化的物件來表達,則程式碼不能通過編譯,不過小括號和等號可以:
double x,y,z;
//int sum{x + y + z}; //錯誤,double型別之和可能無法通過int表達
int sum2(x + y + z);
int sum3 = x+y+z;
大括號初始化的一項特徵是,它對於C++的最令人苦惱的解析語法免疫。C++規定:任何能夠解析為宣告的都要解析為宣告,而這會帶來副作用。程式設計師本來想要以預設方式構造一個物件,結果卻不小心宣告瞭一個函式。這個錯誤的根本原因在於建構函式呼叫語法。
//以傳遞引數方式呼叫建構函式
Widget w1(10); //呼叫Widget的建構函式,傳入形參10
//如果呼叫一個沒有形參的Widget建構函式的話,結果卻變成了宣告一個函式而非物件
Widget w2(); //宣告瞭一個名為w2,返回一個Widget型別物件的函式
由於函式宣告不能使用大括號來指定形參列表,所以使用大括號來完成物件的預設構造沒有這個問題:
Widget w3{}; //呼叫沒有形參的建構函式
大括號初始化存在一些缺陷,伴隨大括號初始化有時會出現意外行為。這種行為源於大括號初始化物、std::initializer_list以及建構函式過載決議之間的糾結關係。這幾者之間的相互作用可以使程式碼看起來要做某一件事,實際上卻在做另一件事。比如使用大括號初始化物來初始化一個使用auto宣告的變數,那麼推匯出來的型別就會變成std::initializer_list。
在建構函式被呼叫時,只要形參中沒有任何一個具備std::initializer_list型別,那麼小括號和大括號的意義就沒有區別。如果一個或多個建構函式宣告瞭任何一個具備std::initializer_list型別的形參,那麼採用大括號初始化語法的呼叫語句會優先選用帶有std::initializer_list型別形參的過載版本。即使是平常會執行復制或移動的建構函式也會被帶有std::initializer_list型別形參的建構函式劫持:
//建構函式沒有std::initializer_list型別,大括號和小括號初始化沒有區別
class Widget{
public:
Widget(int i,bool b);
Widget(int i,double d);
};
Widget w1(10,true); //呼叫第一個建構函式
Widget w2{10,true}; //呼叫第一個建構函式
Widget w3(10,5.0); //呼叫第二個建構函式
Widget w4{10,5.0}; //呼叫第二個建構函式
//建構函式一旦有std::initializer_list形參,那麼大括號初始化一定會選用這個建構函式
class Widget{
public:
Widget(int i,bool b);
Widget(int i,double d);
Widget(std::intializer_list<long double> il);
operator float() const ; //強制轉換成float型別
};
Widget w1(10,true); //呼叫第一個建構函式
Widget w2{10,true}; //使用大括號,呼叫第三個建構函式,10和true被強制轉換為long double
Widget w3(10,5.0); //呼叫第二個建構函式
Widget w4{10,5.0}; //使用大括號,呼叫第三個建構函式,10和5.0被強制轉換為long double
Widget w5(w4); //使用小括號,呼叫的是複製建構函式
Widget w6{w4}; //使用大括號,呼叫第三個建構函式
//w4的返回值被強制轉換float,而float又被強制轉換為long double
Widget w7(std::move(w4)); //使用小括號,呼叫的是移動建構函式
Widget w8{std::move(w4)}; //使用大括號,呼叫第三個構造,和w6結果相同
這種優先呼叫是很強烈的,即使最優選的呼叫std::initializer_list的建構函式無法被呼叫,編譯器還是會選擇這個。只有在找不到任何辦法把大括號初始化物中的實參轉化成std::initializer_list模板中的型別時,編譯器才會去檢查普通的過載函式。
class Widget{
public:
Widget(int i,bool b);
Widget(int i,double d);
Widget(std::initializer_list<bool> il);
};
Widget w{10,5.0}; //無法通過編譯,無法把10和5.0窄化為bool
class Widget{
public:
Widget(int i,bool b);
Widget(int i,double d);
Widget(std::initializer_list<std::string> il);
};
Widget w{10,5.0}; //呼叫第二個建構函式,因為編譯器無法把int和double轉換成string型別,所以尋找過載函式
對於std::initializer_list還有個小問題,當我們使用一對空大括號來構造一個物件,而該物件既支援預設的構造,也支援帶有std::initializer_list型別引數的構造。此時的這對空大括號的意思是“沒有實參”而不是“空的std::initializer_list”。如果我們想要傳入一個空的std::initializer_list,可以通過把空大括號作為建構函式實參的方式實現,即把一對空大括號放入一對小括號或大括號。
class Widget{
public:
Widget(); //預設構造
Widget(std::initializer_list<int> il);
};
Widget w1; //呼叫預設構造
Widget w2{}; //呼叫預設構造
Widget w3(); //解析語法,變成函式宣告而不是呼叫構造
Widget w4({}); //呼叫帶有std::initializer_list引數的構造,傳入空的std::initializer_list
Widget w5{{}}; //同上
大括號初始化物、std::initializer_list、建構函式過載決議,這些內容不注意會有很大的影響。直接影響到的就是std::vector類。std::vector類中有一個形參中沒有std::initializer_list型別的建構函式,它允許我們指定容器的初始尺寸,以及一個初始化時讓所有元素擁有的值。但是它還有一個帶有一個std::initializer_list型別形參的建構函式,允許我們逐個指定容器中的元素值。如果我們要建立一個元素為數值型別的std::vector,並傳遞了兩個實參給建構函式的話,用小括號還是大括號結果會大相徑庭:
//小括號,呼叫了形參中沒有一個具備std::initializer_list型別的建構函式
//結果是建立了一個含有10個元素的std::vector,所有的元素值都是20
std::vector<int> v1(10,20);
//大括號,呼叫了形參中含有std::initializer_list型別的建構函式
//結果是建立了一個含有2個元素的std::vector,元素的值分別為10和20
std::vector<int> v2{10,20};
所以從某種程度上說vector的設計是有缺陷的。我們自己在設計一個類的時候,我們需要意識到自己撰寫的一組過載建構函式中只要有std::initializer_list形參,則使用大括號初始化的客戶程式碼只會發現這些構造的過載版本。
相關文章
- @Async的用法和示例
- HttpWebRequest和WebClient的用法HTTPWebclient
- 物件的初始化和清理物件
- Mybatis的初始化和結合Spring Framework後初始化的MyBatisSpringFramework
- Laravel 模型的 scope 普通用法和全域性用法Laravel模型
- react useMemo 和 useCallBack 的用法React
- Java變數的宣告和初始化Java變數
- Mybatis的初始化和結合Spring Framework後初始化的原始碼探究MyBatisSpringFramework原始碼
- CSS裡的BFC和IFC的用法CSS
- JS中的!=、== 、!==、=== 的用法和區別JS
- Java中ThreadLocal的用法和原理Javathread
- C#中?和??及?:的用法C#
- js中try和catch的用法JS
- 淺談Invoke 和 BegionInvoke的用法
- 淺克隆和深克隆的用法
- oracle中的CURRVAL和NEXTVAL用法Oracle
- Promise && async/await的理解和用法PromiseAI
- React中的Context和Portals用法ReactContext
- select into from 和 insert into select 的用法和區別
- not in 和 not exists 比較和用法
- VBox和HBox的用法及用例
- sql devloper 用法的和SQL 編寫SQLdev
- Java 中 this 和 super 的用法詳解Java
- SQLserver-MySQL的區別和用法ServerMySql
- js中!和!!的區別與用法JS
- js 新增事件 attachEvent 和 addEventListener 的用法JS事件dev
- JS中排序的用法和例項JS排序
- sql中union和union all的用法SQL
- React 中 getDerivedStateFromProps 的用法和反模式React模式
- C語言巨集中"#"和"##"的用法C語言
- Python中__init__的用法和理解Python
- Linq中 AsQueryable(), AsEnumerable()和ToList()的區別和用法
- C++中break和continue的用法和區別C++
- 4_Spring Bean的初始化和銷燬SpringBean
- php的lavarel框架中join和orWhere的用法PHP框架
- fstream中ifstream和ofstream的簡單用法
- Lombok中@Builder和@SuperBuilder註解的用法LombokUI
- 認識jupyter notebook 和 Matplotlib的基本用法