日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

一文总结现代 C++ 中的初始化

發布時間:2024/4/6 windows 49 coder
生活随笔 收集整理的這篇文章主要介紹了 一文总结现代 C++ 中的初始化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文嘗試回答:

  • 現代 C++ 有哪幾種初始化形式?分別能夠用于什么場景?有什么限制?
  • MyClass obj(); 為什么沒有調用默認無參構造函數創建一個對象?
  • new intnew int() 有什么區別?
  • 直接初始化、拷貝初始化、列表初始化、默認初始化、值初始化、類內初始值、構造函數初始值列表的區別與聯系?
  • 初始化和賦值的區別?
  • 類成員有幾種初始化方式,其初始化順序是由什么決定的?
  • 初始化相關的注意事項及最佳實踐?

1. 內置類型和類類型

正式開始介紹初始化之前,先要區分 C++ 中的兩種數據類型:內置類型類類型

  • 內置類型:char、bool、short、int、float、double、指針等 C++ 語言支持的最基礎的數據類型
  • 類類型:標準庫以及我們自己定義的各種類、模板類等,如 MyClassstd::vector<T>std::stringstd::unique_ptr<T>...

2. C++ 初始化的 4 種形式

初始化是指在創建對象(為特定類型的變量申請存儲空間)的同時賦予初始值。現代 C++ 中,一共有 4 種初始化形式:

  1. 等號 =...
  2. 等號+花括號 ={...}
  3. 花括號 {...}
  4. 圓括號 (...)

無論是內置類型還是類類型,都支持這 4 種形式的初始化:

int i1=0;   // (1)
int i2={0}; // (2)
int i3{0};  // (3)
int i4(0);  // (4)

std::string s1="hello";   // (1)
std::string s2={"hello"}; // (2)
std::string s3{"hello"};  // (3)
std::string s4("hello");  // (4)

3. 初始化和賦值

前兩種初始化雖然在形式上都用了等號 =,但初始化的等號和賦值的等號具有不同的含義。C++ 中賦值和初始化是兩種完全不同的操作,只是恰巧都用了等號 =。就好比乘法和解引用都用了 *,含義卻完全不同。

  • 初始化:為變量申請存儲空間,創建新的變量。如果是類類型,將調用類的構造函數
  • 賦值:把一個現有變量的值用另一個值替代,不創建新的變量。如果是類類型,將調用類的賦值運算符 operator=()
int a = 1; // 初始化
a = 2;     // 賦值

MyClass obj1;              // 初始化,調用 MyClass() 構造函數
MyClass obj2{42, "hello"}; // 初始化,調用 MyClass(int, string) 構造函數
obj1 = obj2;               // 賦值,調用 operator=(const MyClass&)

4. 拷貝初始化和直接初始化

int i1=0;   // (1) 拷貝初始化
int i2={0}; // (2) 拷貝初始化
int i3{0};  // (3) 直接初始化
int i4(0);  // (4) 直接初始化

std::string s1="hello";   // (1) 拷貝初始化
std::string s2={"hello"}; // (2) 拷貝初始化
std::string s3{"hello"};  // (3) 直接初始化
std::string s4("hello");  // (4) 直接初始化

C++ 初始化的 4 種形式中,前兩種初始化形式 (1)(2) 使用了等號,叫做拷貝初始化,后兩種 (3)(4) 沒有等號,叫做直接初始化。無論是拷貝初始化,還是直接初始化,都是初始化,不是賦值!對于類類型,都是調用構造函數,不會調用賦值運算符!在絕大多數情況下(TODO:補充反例),拷貝初始化和直接初始化除了形式上多一個/少一個等號之外,底層代碼上沒有任何區別。

注意:雖然叫做拷貝初始化,但構造 s1、s2 的過程中,不存在“拷貝”!底層代碼和 s3、s4 完全相同,都是直接調用 string 的構造函數(不信可以去 cppinsights.io 自行驗證)。

5. 列表初始化

列表初始化(list initialization):使用花括號 {} 形式的初始化。C++ 的 4 種初始化形式中的 (2)(3) 都屬于列表初始化。列表初始化在 C++11 中得到全面應用,其最大的特點在于可以防止窄化轉換:如果列表初始化存在信息丟失的風險, 編譯器將報錯。不僅如此,列表初始化還能用于各種初始化場景,包括類內初始值以及 Most Vexing Parse 場景。

a. 防止窄化轉換

long double ld = 3.1415;
int a{ld};    // 無法編譯,轉換存在信息丟失的風險
int b = {ld}; // 無法編譯,轉換存在信息丟失的風險
int c(ld);    // 可以編譯,但信息丟失
int d = ld;   // 可以編譯,但信息丟失

b. 避免 Most Vexing Parse

class MyClass {
public:
  MyClass();
  MyClass(int x);
  MyClass(int x, int y);
};

int main() {
  MyClass obj1(1);   // OK
  MyClass obj2{1};   // OK,列表初始化
  MyClass obj3(1,2); // OK
  MyClass obj4(1,2); // OK,列表初始化
  // 錯誤,obj5 被解析為函數聲明:參數為空,返回 MyClass
  MyClass obj5(); 
  MyClass obj6{};  // OK,列表初始化
  MyClass obj7;    // OK
}

注意:obj5 并不是創建一個默認構造的對象,而是被解析為一個函數聲明,參數為空,返回 MyClass。有的編譯期會給出警告 warning: empty parentheses were disambiguated as a function declaration [-Wvexing-parse]?

6. 默認初始化

默認初始化(default initialization):當對象未被顯式地賦予初值時執行的初始化行為。

默認初始化的例子:

int i;
std::string s;
MyClass* p = new MyClass;
double* pd = new double;
  • 類類型:由類的默認(無參)構造決定
  • 內置類型(指針、int、double、float、bool、char 等)及其數組
    • 全局(包括定義在任何函數之外、命名空間之內的)變量或局部靜態變量:初始化為 0(這種情況也叫值初始化
    • 局部非靜態變量或類成員:未定義(未初始化)

如果類沒有默認(無參)構造函數,則該類不支持默認初始化。

7. 值初始化

值初始化(value initialization):默認初始化的特殊情況,此時內置類型會被初始化為 0。

值初始化的場景:

  • STL 容器只指定元素數量,而不指定初值時,就會執行值初始化,如 vector<int> vec(10);:10 個 int,初始化為 0
  • 全局(包括定義在任何函數之外、命名空間之內的)變量或局部靜態變量:初始化為 0
  • new 類型,后面帶括號,如:new int()new string{}
  • 初始值列表為空 {},如 double d{};int *p{};

類類型沒必要區分是默認初始化還是值初始化:類類型的初始化總是由類的構造函數決定,與在函數內/外、全局/局部/類成員、靜態/非靜態、默認初始化/值初始化無關!如果類不含默認(無參)構造,則該類無法進行默認初始化/值初始化!

8. new 的初始化

// 對于類類型,有無括號沒區別
string *ps1 = new string;    // 默認初始化為空 string
string *ps2 = new string();  // 值初始化為空 string
string *ps3 = new string{};  // 值初始化為空 string

// 對于內置類型,有括號進行值初始化,沒有括號的值未定義!
int *pi1 = new int;          // 默認初始化,*pi1 值未定義!
int *pi2 = new int();        // 值初始化,*pi2 為 0
int *pi3 = new int{};        // 值初始化,*pi3 為 0

const int *pci1 = new const int(1024); // 分配并初始化一個 const int
const int *pci2 = new const int{1024}; // 分配并初始化一個 const int

9. 類的初始化

類成員有兩種初始化方式:類內初始值(成員初始化器,in-class member initializer)以及構造函數初始值列表(constructor initialize list)。

不要在構造函數體內部初始化數據成員,因為只有當類的所有成員初始化完成之后才開始執行構造函數體,此時并不是真正意義上的初始化,而是重新賦值!也正是因為如此,引用成員、const 成員只能通過類內初始值或者構造函數初始值列表初始化,而不能在構造函數體內部“初始化”。不僅如此,在構造函數體內部進行賦值,相比于內類初始值/構造函數初始化列表的只調用一次構造函數,多了一次賦值操作,效率更低。

注意:對于內置類型的數據成員,如果沒有對其進行顯式初始化,其值未定義!

9.1 類內初始值/成員初始化器

在類中聲明類的數據成員同時提供初始值,初始值可以是字面值、表達式甚至是函數調用。形式上可以用等號或者花括號,但是不能用圓括號。C++11 之后首選的初始化類成員方式。

class SalesData {
    unsigned unitsSold = 0;
    double revenue {0.0};
    std::string bookNo{"hello"};
    shared_ptr<int> sp={make_shared<int>(5)};
};

9.2 構造函數初始值列表

如果需要根據傳入構造函數的參數來初始化類成員,可以使用構造函數初始值列表。構造函數初始值列表的形式是在構造函數的形參列表之后,使用冒號分隔,接著是成員名字,然后使用圓括號或花括號來包裹初始化的表達式,多個成員之間通過逗號分隔。

class SalesData {
public:
    SalesData(const std::string &s) : bookNo(s) {}
    SalesData(const std::string &s, unsigned n, double p) : bookNo(s), unitsSold(n), revenue(p*n) {}
};

注意:類的數據成員初始化順序和構造函數初始化列表中的順序無關,而是由成員在類中聲明的順序決定

class X {
    int x;
    int y;
public:
    // 先用未初始化的 y 初始化 x,再用 val 初始化 y
    X(int val): y(val), x(y){}
};

上述 x 值未定義!一般編譯器會給出警告。

9.3 類成員的初始化順序

類的數據成員初始化順序由成員在類中聲明的順序決定,按照聲明的順序,依次構造每個成員,所有成員構造完成后才執行構造函數。

順便說一句,析構順序與初始化順序相反:先執行析構函數,再按照構造相反的順序依次析構每個成員。

10. 總結

現代 C++ 4 種初始化形式:

序號 形式 拷貝/直接初始化 可用于構造函數初始值列表 可用于類內初始值 備注
1 等號 = 拷貝初始化 ? ?
2 等號+花括號 ={} 拷貝初始化 ? ? 列表初始化
3 花括號 {} 直接初始化 ? ? 推薦!列表初始化,能用于各種初始化場景!
4 圓括號 () 直接初始化 ? ? 存在 Most Vexing Parse 問題、不可用于類內初始值及提供多個初始元素值的列表 vector<string> v("a", "an", "the");
  • 拷貝初始化:使用 = 形式的初始化。
  • 直接初始化:不使用 = 形式的初始化(使用 {}()形式初始化)
  • 列表初始化:使用 {} 形式的初始化,能夠用于各種初始化場景,也被稱為統一初始化
  • 默認初始化:未顯式指定初始值的初始化行為。類類型將調用默認無參構造函數;而內置類型可能被值初始化為 0,也可能未被初始化(值未定義)!
  • 值初始化:默認初始化的特殊情況,對于內置類型,其值將被初始化為 0。
  • 類內初始值/成員初始化器:聲明類成員的同時直接提供初值,C++11 之后的首選初始化類成員的方式。
  • 構造函數初始值列表:能夠根據傳入構造函數的參數進行初始類成員

11. 最佳實踐/核心指南

  1. 總是初始化內置類型的變量,如 int i{};。最好使用 auto,因為 auto 會強迫初始化:不提供初始值就無法推導類型。

  2. 推薦使用 {} 統一列表初始化,形式統一,能用于各種場景。

  3. 對于類成員的初始化,優先考慮類內初始值。如果需要根據傳入構造函數的參數來初始化成員,可以使用構造函數初始值列表,不要在構造函數體內部對類成員進行賦值。

  4. C++核心指南 C.45:如果只是初始化類的數據成員, 不需要專門定義構造函數,用類內初始值。

  5. C++核心指南 NR.5:不要兩步初始化,類的構造函數應該直接完成類的初始化工作,不要把初始化的任務轉移/強加給類的用戶(例如要求用戶在創建一個類的對象后,再額外調用一個 Init() 之類的函數)。

12. 擴展閱讀

  • C++ 何時調用默認構造、拷貝構造、移動構造、拷貝賦值、移動賦值、析構以及對象析構順序
  • C++ Primer 查漏補缺 —— C++ 中的各種初始化

總結

以上是生活随笔為你收集整理的一文总结现代 C++ 中的初始化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。