帶你掌握C++中三種類成員初始化方式


摘要:在C++11之后,聲明時初始化->初始化列表->構造函數初始化。

本文分享自華為雲社區《如何編寫高效、優雅、可信代碼系列(3)——類成員初始化的三種方式》,原文作者:我是一顆大西瓜。

首先,先得了解一下C++支持哪幾種類成員初始化的方式,你常用的又是哪一種。

  • 初始化方式一:初始化列表
class A
{
public:
    int a; // 初始化列表
    A(int a_):a(a_){}
};
  • 初始化方式二:構造函數初始化
class A
{
public:
    int a; // 初始化列表
    A(int a_, bool b) { a = a_; }
};
  • 初始化方式三:聲明時初始化(也稱就地初始化,c++11后支持)
class A
{
public:
    int a = 1; // 聲明時初始化
    A() {}
};

在C++98中,支持了在類聲明中使用等號“=”加初始值的方式,來初始化類中靜態成員常量。這種聲明方式我們也稱之為“就地”聲明。就地聲明在代碼編寫時非常便利,不過C++98對類中就地聲明的要求卻非常高。如果靜態成員不滿足常量性,則不可以就地聲明,而且即使常量的靜態成員也只能是整型或者枚舉型才能就地初始化。而非靜態成員變量的初始化則必須在構造函數中進行。比如,如下代碼在c++98中編譯

class Init
{
public:
    Init(): a(0) []
    Init(int d): a(d) {}
private:
    int a;
    const static int b = 0;

    int c = 1;           // member, cannot pass build
    static int d = 0;    // member, cannot pass build

    static const double e = 1.3;      // not int or enum type, cannot pass build
    stati const char* const f = "e";  // not int or enum type, cannot pass build
}

這非常不方便,所以在C++11中,標准允許非靜態成員變量的初始化有多種形式。具體而言,除了初始化列表外,在C++11中,標准還允許使用等號= 或者 花括號{} 進行就地的非靜態成員變量初始化。

struct init {
    int a = 1;
    double b {1.2};
};

大家知道,有幾種情況下推薦優先使用列表初始化

  • const成員變量只能用成員初始化列表來完成初始化,而不能在構造函數內賦值
  • 初始化的數據成員是對象
  • 需要初始化引用成員數據

具體的原因這里不細述,大家可以去看一下《C++ Primer》。

構造函數初始化的本質是賦值操作("="),這個方法存在兩個問題,一個是比起初始化列表和就地初始化,此方式的效率偏低;第二個是可能存在錯誤隱患。

先說第一個,賦值過程中會產生臨時對象,臨時對象的構造析構會造成效率損耗,初始化列表的方式就避免了產生臨時對象縮帶來的問題。

第二個是,如果你沒有重寫或者禁止賦值構造函數,c++會悄悄的加上默認的賦值構造函數,這個時候也有可能帶來問題。

從C++11之后,這三種初始化的方法都可以使用,並不會存在沖突,但是,他們之間是有優先級順序的,這個優先級來源於他們在初始化的時間順序,后面初始化的會把前面的覆蓋掉,成員變量的初始化順序是

聲明時初始化->初始化列表->構造函數初始化

因此假如三種初始化方式同時存在的話,那么最后保留的成員變量值肯定是構造函數中初始化的值。

#include <iostream>
using namespace std;
class A 
{
public:
    int a = 1;
    A(int a_) :a(2) { a = 3; }
};

int main()
{
    A a;
    cout << "a.a=" << a.a << endl;
    return 0;
}

// a.a=3

既然初始化方式這么多,那么什么時候適用哪種呢?

1. 聲明時初始化的使用場景

  • 一個優點是直觀,你在聲明的時候順便給一個初始值,bravo,別人在看你代碼的時候,點一下調到聲明也能看到你賦予的初始值,不用再去看構造函數那里給的什么值
  • 第二個優點更有用了,比如你要定義多個構造函數,每個構造函數都用列表初始化的方法初始化,多麻煩呀,請看下面的例子,媽媽看了再也不用擔心我想用其他初始化方法了
class Group {
public:
    Group() {}
    Group(int a): data(a) {}
    Group(Mem m): mem(m) {}
    Group(int a, Mem m, string n): data(a), mem(m), name(n) {}
private:
    int data = 1;
    Mem mem{0};
    string name{"Group"};
};

2. 列表初始化的使用場景

前面說過了三個場景,這里贅述一下

  • const成員變量只能用成員初始化列表來完成初始化,而不能在構造函數內賦值
  • 初始化的數據成員是對象
  • 需要初始化引用成員數據

但是,需要注意列表初始化的順序,不過IDE會提示你的

3. 構造函數初始化的使用場景

  • 第一個就是拷貝和賦值構造函數里(不然怎么叫賦值構造函數呢)
  • 第二個就是比較無聊的情況了,比如你想把幾個成員函數都初始化成一個值,請看下面例子
class Group {
public:
    Group() {data1 = data2 = data3 = 0;}
private:
    int data1;
    int data2;
    int data3;
};

一言以蔽之,優先就地初始化和列表初始化。

 

點擊關注,第一時間了解華為雲新鮮技術~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM