C++中列表初始化,你知多少?
一、何為列表初始化
C++中的列表初始化是一種用一對花括號(hào) {} 來進(jìn)行對象初始化的語法。它被引入主要是為了提供一種統(tǒng)一的初始化方式,適用于各種不同的數(shù)據(jù)類型和數(shù)據(jù)結(jié)構(gòu),包括基本類型、數(shù)組、結(jié)構(gòu)體、類、STL 容器等。列表初始化在 C++11 標(biāo)準(zhǔn)中被引入,是現(xiàn)代 C++ 編程風(fēng)格的一部分。
基本語法
Type variable = {value1, value2, ...};
- 使用一對花括號(hào) {} 來初始化對象。
- 列表初始化對于類型轉(zhuǎn)換更為嚴(yán)格,不允許縮窄轉(zhuǎn)換(請看下面何為窄轉(zhuǎn)化部分)。
示例
- 基本類型:
int x = {42};
double y = {3.14};
- 數(shù)組:
int arr[] = {1, 2, 3, 4, 5};
- 結(jié)構(gòu)體:
struct Point {
int x;
int y;
};
Point p = {10, 20};
- 類:
class MyClass {
public:
int data;
double value;
MyClass(int d, double v) : data(d), value(v) {}
};
MyClass obj = {42, 3.14};
- STL 容器:
#include <vector>
#include <map>
std::vector<int> vec = {1, 2, 3, 4, 5};
std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};
- 自定義類型:
class MyType {
public:
int x;
double y;
MyType(int a, double b) : x(a), y(b) {}
};
MyType myVar = {5, 2.5};
二、何為窄轉(zhuǎn)化
窄轉(zhuǎn)化(Narrowing Conversion)指的是將一個(gè)具有較大范圍的值轉(zhuǎn)換為較小范圍的類型時(shí)可能丟失信息的情況。這種轉(zhuǎn)換可能導(dǎo)致截?cái)嗷蚴д?,因?yàn)槟繕?biāo)類型的表示范圍比源類型小。在 C++ 中,窄轉(zhuǎn)化是一種不安全的類型轉(zhuǎn)換,因?yàn)樗赡軐?dǎo)致數(shù)據(jù)丟失或意外的行為。
以下是一些示例說明窄轉(zhuǎn)化:
- 從浮點(diǎn)數(shù)到整數(shù):
double myDouble = 3.14;
int myInt = myDouble; // 窄轉(zhuǎn)化,可能會(huì)截?cái)嘈?shù)部分
- 從長整型到整數(shù):
long long myLong = 1000000000000;
int myInt = myLong; // 窄轉(zhuǎn)化,可能會(huì)截?cái)嗷蛞绯?/code>
- 從大范圍的整數(shù)類型到小范圍的整數(shù)類型:
long long myLong = 1000000000000;
int myInt = static_cast<int>(myLong); // 窄轉(zhuǎn)化,可能會(huì)截?cái)嗷蛞绯?/code>
窄轉(zhuǎn)化是需要小心處理的,因?yàn)樗赡軐?dǎo)致數(shù)據(jù)的損失和不確定的行為。在需要進(jìn)行類型轉(zhuǎn)換時(shí),最好使用安全的轉(zhuǎn)換方式,例如使用 static_cast 并在可能丟失信息的地方進(jìn)行顯式的檢查和處理。在 C++11 引入的列表初始化中,提供了對縮窄轉(zhuǎn)換的更嚴(yán)格的檢查,不允許在列表初始化時(shí)發(fā)生縮窄轉(zhuǎn)換,從而幫助程序員避免潛在的問題。
三、列表初始化規(guī)則和特點(diǎn)
列表初始化有一些規(guī)則和特點(diǎn),主要包括以下幾個(gè)方面:
1. 不允許縮窄轉(zhuǎn)換
列表初始化對類型轉(zhuǎn)換更為嚴(yán)格,不允許發(fā)生縮窄轉(zhuǎn)換,即不允許將一個(gè)精度更高的類型賦值給一個(gè)精度較低的類型。
int x = {3.14}; // 錯(cuò)誤,嘗試縮窄轉(zhuǎn)換
2. 對于數(shù)組,列表初始化的大小由元素個(gè)數(shù)決定
int arr[] = {1, 2, 3}; // 合法,數(shù)組大小為3
3. 類型不匹配時(shí)可能調(diào)用構(gòu)造函數(shù)
當(dāng)列表初始化的類型和目標(biāo)類型不匹配時(shí),如果存在適當(dāng)?shù)臉?gòu)造函數(shù),編譯器會(huì)嘗試調(diào)用構(gòu)造函數(shù)進(jìn)行初始化。
class MyClass {
public:
int data;
double value;
MyClass(int d, double v) : data(d), value(v) {}
};
MyClass obj = {42, 3.14}; // 合法,調(diào)用構(gòu)造函數(shù)
4. 空列表初始化
在某些情況下,可以使用空的花括號(hào) {} 進(jìn)行初始化,這會(huì)被解釋為對應(yīng)類型的默認(rèn)值。
int x = {}; // x 被初始化為 0
double y = {}; // y 被初始化為 0.0
5. 對于類類型,構(gòu)造函數(shù)的匹配規(guī)則
當(dāng)進(jìn)行列表初始化時(shí),編譯器會(huì)根據(jù)構(gòu)造函數(shù)的參數(shù)匹配規(guī)則選擇相應(yīng)的構(gòu)造函數(shù)。
class Example {
public:
Example(int a, double b);
Example(std::string str);
};
Example obj1 = {42, 3.14}; // 調(diào)用構(gòu)造函數(shù) Example(int, double)
Example obj2 = {"Hello"}; // 調(diào)用構(gòu)造函數(shù) Example(std::string)
6. 嵌套初始化
可以使用嵌套的列表初始化來初始化嵌套的數(shù)據(jù)結(jié)構(gòu)。
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
總體來說,列表初始化提供了一種簡潔且直觀的初始化語法,同時(shí)對類型匹配和轉(zhuǎn)換有著更為嚴(yán)格的規(guī)定,減少了一些初始化時(shí)可能發(fā)生的錯(cuò)誤。
四、列表初始化的好處
列表初始化(Uniform Initialization)在 C++ 中引入的好處主要有以下幾點(diǎn):
- 一致性:列表初始化提供了一種一致的初始化語法,可以用于初始化各種類型的對象,包括基本類型、數(shù)組、結(jié)構(gòu)體、類、STL 容器等。這種一致性使得代碼更加清晰和易讀。
int x = {42}; // 初始化基本類型
int arr[] = {1, 2, 3}; // 初始化數(shù)組
Point p = {10, 20}; // 初始化結(jié)構(gòu)體
MyClass obj = {42, 3.14}; // 初始化類
std::vector<int> vec = {1, 2, 3}; // 初始化容器
- 防止窄化轉(zhuǎn)換:列表初始化對類型轉(zhuǎn)換更為嚴(yán)格,不允許發(fā)生縮窄轉(zhuǎn)換,從而減少了一些可能引入 bug 的情況。
int x = {3.14}; // 錯(cuò)誤,嘗試縮窄轉(zhuǎn)換
- 構(gòu)造函數(shù)匹配:當(dāng)進(jìn)行列表初始化時(shí),如果存在適當(dāng)?shù)臉?gòu)造函數(shù),編譯器會(huì)嘗試調(diào)用構(gòu)造函數(shù)進(jìn)行初始化。這提高了代碼的靈活性,使得用戶定義的類型更容易進(jìn)行初始化。
class MyClass {
public:
int data;
double value;
MyClass(int d, double v) : data(d), value(v) {}
};
MyClass obj = {42, 3.14}; // 調(diào)用構(gòu)造函數(shù)
- 簡潔性:列表初始化的語法相對簡潔,通過一對花括號(hào){}就可以完成初始化,避免了傳統(tǒng)的各種初始化方式可能導(dǎo)致的歧義。
int arr[] = {1, 2, 3}; // 合法,簡潔
- 避免 most vexing parse: 傳統(tǒng)的初始化語法在某些情況下可能會(huì)導(dǎo)致 most vexing parse,而列表初始化語法避免了這一問題。
"Most Vexing Parse" 是一個(gè)有趣而令人困擾的 C++ 編程問題,它通常發(fā)生在類的對象聲明上,導(dǎo)致程序員可能不是按照他們預(yù)期的方式初始化對象。這個(gè)問題的名字來源于這種情況的令人迷惑和難以理解。
Most Vexing Parse 主要發(fā)生在下面這樣的情況:
class MyClass {
public:
MyClass() {}
};
int main() {
MyClass obj(); // 最令人迷惑的解析,聲明了一個(gè)函數(shù)而不是對象
// ...
return 0;
}
在上述代碼中,MyClass obj(); 被編譯器解釋為聲明一個(gè)返回 MyClass 類型的函數(shù)而不是創(chuàng)建一個(gè) MyClass 類型的對象。這是因?yàn)樵?C++ 中,如果聲明一個(gè)函數(shù)的時(shí)候帶有空括號(hào),編譯器會(huì)將其解釋為一個(gè)函數(shù)聲明而不是一個(gè)對象定義。
為了避免 most vexing parse,可以使用以下兩種方式之一:
- 使用花括號(hào)初始化:
MyClass obj{}; // 使用花括號(hào)初始化,避免 most vexing parse
- 使用括號(hào)初始化:
MyClass obj(); // 編譯器會(huì)將其解釋為函數(shù)聲明
MyClass obj{}; // 使用括號(hào)初始化,避免 most vexing parse
這個(gè)問題是由 C++ 語法規(guī)則引起的,對于初學(xué)者來說可能會(huì)令人困擾。因此,在聲明和初始化對象時(shí),特別是在有可能發(fā)生 most vexing parse 的地方,建議使用花括號(hào)初始化或括號(hào)初始化,以避免潛在的問題。
五、不適用列表初始化的情況
什么是聚合類型
1、類型是一個(gè)普通數(shù)組,如int[5],char[],double[]等
2、類型是一個(gè)類,且滿足以下條件:
- 沒有用戶聲明的構(gòu)造函數(shù)
- 沒有用戶提供的構(gòu)造函數(shù)(允許顯示預(yù)置或棄置的構(gòu)造函數(shù))
- 沒有私有或保護(hù)的非靜態(tài)數(shù)據(jù)成員
- 沒有基類
- 沒有虛函數(shù)
- 沒有{}和=直接初始化的非靜態(tài)數(shù)據(jù)成員
- 沒有默認(rèn)成員初始化器
雖然列表初始化是一種很方便和清晰的初始化方式,但有一些情況下不適合或者不能使用列表初始化:
- 不支持聚合初始化的類列表初始化主要用于聚合類型的初始化,而對于不支持聚合初始化的類,不能使用列表初始化。一個(gè)類如果有用戶自定義的構(gòu)造函數(shù)、私有/受保護(hù)的非靜態(tài)數(shù)據(jù)成員,或者基類沒有默認(rèn)構(gòu)造函數(shù),那么該類就不再是聚合類型。
class NotAggregate {
public:
int x;
int y;
NotAggregate(int a, int b) : x(a), y(b) {}
};
NotAggregate obj = {1, 2}; // 錯(cuò)誤,NotAggregate 不是聚合類型
- 不能進(jìn)行窄化轉(zhuǎn)換的地方:列表初始化不允許發(fā)生窄化轉(zhuǎn)換,因此在需要執(zhí)行窄化轉(zhuǎn)換的地方不能使用列表初始化。
double myDouble = 3.14;
int myInt = myDouble; // 合法,但列表初始化會(huì)報(bào)錯(cuò)
- 需要避免 most vexing parse 的地方:在可能發(fā)生 most vexing parse(最令人迷惑的解析)的地方,列表初始化可能不適用。這通常發(fā)生在類的默認(rèn)構(gòu)造函數(shù)被誤解為函數(shù)聲明的情況下。
class MyClass {
public:
MyClass() {}
};
MyClass obj(); // 最令人迷惑的解析,聲明了一個(gè)函數(shù)而不是對象
MyClass obj{}; // 正確的初始化方式
總之,雖然列表初始化是一種很便捷和安全的初始化方式,但在某些情況下,特別是對于非聚合類型和可能導(dǎo)致 most vexing parse 的地方,可能需要考慮其他的初始化方式。