1. 程式人生 > >C++基礎教程面向物件(學習筆記5(1))

C++基礎教程面向物件(學習筆記5(1))

建構函式初始化列表

在上一課中的學習過程中,為簡單起見,我們使用賦值運算子在建構函式中初始化了類成員資料。例如:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something()
    {
        // 這些全都是賦值而不是初始化
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

執行類的建構函式時,將建立m_value1,m_value2和m_value3。然後執行建構函式的主體,其中成員資料變數被賦值。這類似於非面向物件的C ++中以下程式碼的流程:

int m_value1;
double m_value2;
char m_value3;
 
m_value1 = 1;
m_value2 = 2.2;
m_value3 = 'c';

雖然這在C ++語言的語法中是有效的,但它沒有表現出良好的風格(並且可能初始化效率低)。

但是,正如您在前面的課程中學到的,某些型別的資料(例如const和引用變數)必須在宣告它們的行上初始化。請考慮以下示例:

class Something
{
private:
    const int m_value;
 
public:
    Something()
    {
        m_value = 1; // 錯誤: const 變數不能被賦值
    } 
};

這會生成類似於以下內容的程式碼:

const int m_value; //錯誤: const變數一定要用一個值去初始化
m_value = 5; //  錯誤: const 變數不能被賦值

在某些情況下,在建構函式體中為const或引用成員變數賦值顯然是不夠用的。

成員初始化列表

為了解決這個問題,C ++提供了一種方法,用於通過成員初始化列表(通常稱為“成員初始化列表”)初始化類成員變數(而不是在建立它們之後為它們賦值)。不要將這些與我們可以用來為陣列賦值的類似命名的初始化列表混淆。 初始化和賦值中,您瞭解到可以通過三種方式初始化變數:複製,直接初始化和通過統一初始化(僅限C ++ 11)。

int value1 = 1; // 賦值
double value2(2.2); // 直接初始化
char value3 {'c'} // 統一初始化

使用初始化列表幾乎與直接初始化(或C ++ 11中的統一初始化)相同。

這是通過示例最好地學習的東西。重新審視我們在建構函式體中執行賦值的程式碼:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something()
    {
        // 這裡全部都是賦值,不是初始化
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

現在讓我們使用初始化列表編寫相同的程式碼:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // 哈哈,這就是直接初始化
    {
    // 這裡就不再需要賦值的程式碼了
    }
 
    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
};
 
int main()
{
    Something something;
    something.print();
    return 0;
}

這列印:

Soomething(1,2.2,c) 在建構函式引數之後插入成員初始值設定項列表。它以冒號(:)開頭,然後列出要初始化的每個變數以及用逗號分隔的該變數的值。

注意,我們不再需要在建構函式體中執行賦值,因為初始化列表替換了該功能。另請注意,初始化列表不以分號結尾。

當然,當我們允許呼叫者傳入初始化值時,建構函式更有用:

#include <iostream>
 
class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something(int value1, double value2, char value3='c')
        : m_value1(value1), m_value2(value2), m_value3(value3) // 直接初始化我莪們的成員變數
    {
    // 這裡不需要賦值
    }
 
    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
 
};
 
int main()
{
    Something something(1, 2.2); // value1 = 1, value2=2.2, value3 的預設值是 'c'
    something.print();
    return 0;
}

這列印:

Something(1,2.2,c) 注意,您可以使用預設引數來提供預設值,以防使用者忘記。

這是一個具有const成員變數的類的示例:

class Something
{
private:
    const int m_value;
 
public:
    Something(): m_value(5) // 直接初始化我們的靜態成員變數
    {
    } 
};

這是有效的,因為我們可以初始化const變數(但不能分配給它們!)。

規則使用成員初始值設定項列表初始化類成員變數而不是賦值

C ++ 11中的統一初始化

在C ++ 11中,可以使用統一初始化而不是直接初始化:

class Something
{
private:
    const int m_value;
 
public:
    Something(): m_value { 5 } // 統一初始化成員變數
    {
    } 
};

我強烈建議您開始使用這種新語法(即使您沒有使用const或引用成員變數),因為在進行組合和繼承時需要初始化列表(我將在稍後介紹)。

規則:如果編譯器與C ++ 11相容,則優先於直接初始化進行統一初始化

使用成員初始化列表初始化陣列成員

考慮一個帶有陣列成員的類:

class Something
{
private:
    const int m_array[5];
 
};

在C ++ 11之前,您只能通過成員初始化列表將陣列成員歸零:

class Something
{
private:
    const int m_array[5];
 
public:
    Something(): m_array {} // 將成員陣列歸零
    {
        // 如果我們希望陣列有值,我們必須在這裡使用賦值
    }
 
};

但是,在C ++ 11中,您可以使用統一初始化完全初始化成員陣列:


class Something
{
private:
    const int m_array[5];
 
public:
    Something(): m_array { 1, 2, 3, 4, 5 } // 用同一初始化出事我們的成員變數
    {
    }
 
};

初始化作為類的成員變數 成員初始化列表也可用於初始化類的成員。


#include <iostream>
 
class A
{
public:
    A(int x) { std::cout << "A " << x << "\n"; }
};
 
class B
{
private:
    A m_a;
public:
    B(int y)
         : m_a(y-1) // 呼叫 A(int) 建構函式來初始化成員 m_a
    {
        std::cout << "B " << y << "\n";
    }
};
 
int main()
{
    B b(5);
    return 0;
}

這列印:

A 4 B 5 構造變數b時,將使用值5呼叫B(int)建構函式。在建構函式的主體執行之前,初始化m_a,呼叫值為4的A(int)建構函式。這將列印“A 4”。然後控制返回到B建構函式,並執行B建構函式的主體,列印“B 5”。

格式化初始化列表

C ++為您提供瞭如何格式化初始化程式列表的靈活性,這取決於您希望如何繼續。但這裡有一些建議: 如果初始化列表與函式名稱在同一行上,那麼將所有內容放在一行上就可以了

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // 所有的初始化都在一行完成
    {
    }
};

如果初始化列表與函式名稱不在同一行,那麼它應該在下一行縮排。

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something(int value1, double value2, char value3='c') // 這行已經有很多的東西了,我們更習慣將它放在下一行
        : m_value1(value1), m_value2(value2), m_value3(value3) //所以我們可以把所有內容縮排到下一行
    {
    }
 
};

如果所有初始化程式都不適合單行(或初始化程式不重要),那麼您可以將它們分開,每行一個:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
    float m_value4;
 
public:
    Something(int value1, double value2, char value3='c', float value4=34.6) // 這行已經有很多的東西了
        : m_value1(value1), //一行一個,末尾逗號
        m_value2(value2),
        m_value3(value3),
        m_value4(value4) 
    {
    }
 
};

初始化列表順序 也許令人驚訝的是,初始化列表中的變數未按初始化列表中指定的順序初始化。相反,它們按照在類中宣告的順序進行初始化。 為了獲得最佳結果,應遵循以下建議: 1)不要使初始化成員變數依賴於首先初始化的其他成員變數(換句話說,即使初始化的順序不同,那也確保你的初始化成員正確的初始化)。 2)初始化初始化列表中的變數的順序與它們在類中宣告的順序相同。只要遵循先前的建議,這不是嚴格要求的,但如果您不這樣做並且您已開啟所有警告,您的編譯器可能會給您一個警告。

summary:

成員初始化列表允許我們初始化我們的成員,而不是為它們分配值。這是初始化在初始化時需要值的成員的唯一方法,例如const或引用成員,並且它比在建構函式體中賦值更具魅力。成員初始化列表既可以使用基本型別,也可以使用類本身的成員,例如A(int)。

Quiz time

1)編寫一個名為RGBA的類,它包含4個型別為std :: uint8_t的成員變數m_red,m_green,m_blue和m_alpha(#include cstdint訪問型別為std :: uint8_t)。將預設值0分配給m_red,m_green和m_blue,將255分配給m_alpha。建立一個使用成員初始化列表的建構函式,該列表允許使用者初始化m_red,m_blue,m_green和m_alpha的值。包含一個print()函式,用於輸出成員變數的值。

提示:如果print()函式無法正常工作,請確保將uint8_t轉換為int。

應執行以下程式碼:

int main()
{
	RGBA teal(0, 127, 127);
	teal.print();
 
	return 0;
}

併產生結果:

r = 0 g = 127 b = 127 a = 255

這裡我們給出解決方案:

#include<iostream>
#include<cstdint>//加入unit_8
using namespace std;

class RGBA
{
public:

	RGBA(uint8_t red=0, uint8_t green=0, uint8_t blue=0, uint8_t alpha=255) :m_red(red), m_green(green), m_blue(blue), m_alpha(alpha)
	{
	}
	void print();
private:
	uint8_t	m_red;
	uint8_t m_green;
	uint8_t m_blue;
	uint8_t m_alpha;

};

void RGBA::print()
{
	cout << "r=" << static_cast<int>(m_red)<< endl;//static_cast<int>為強制型別轉換
	cout << "g=" << static_cast<int>(m_green) << endl;
	cout << "b=" << static_cast<int>(m_blue) << endl;
	cout << "a:" << static_cast<int>(m_alpha) << endl;
}





int main()
{
	RGBA teal(0, 127, 127);
	teal.print();

	return 0;
}