[筆記]static / const成員資料與函式|C++

non-const static成員資料無法在類別內初始化,也無法使用初始化列,那到底該如何初始化?static成員是什麼概念?可以將自己宣告為自己的成員資料嗎?如果可以,該如何實現?如果不行,會發生什麼問題?

還是很困惑嗎?文章裡有答案喔~😎

一、靜態 (static)與常數 (const)

(一)成員資料 (data member)

C++11:允許non-static成員資料在類別中直接初始化

  1. const static int m1 = 7; // ok
  2. const             int m2 = 7; // ok
  3.             static int m3 = 7; // error: not const
  4.                         int m4 = 7; // ok
那要如何初始化第三個例子 (non-const static 成員資料)呢?non-const static 成員資料無法在類別中初始化,只能在class外初始化,而且不能放在.h檔中,必須放在.cpp

方法1:在.cpp宣告

 宣告的時候,class外的變數不能加上static且前面要加上領域,如果不給值,預設就是0

一般而言,鏈結器會合併 C++ 樣版產生的函式或變數,所以我們可以利用這個特性宣告靜態資料成員,然後以 vector<void*>::count存取該變數:

  

template<>

class vector<void*>{

public:

    ...

    ...

private:

    std::vector<void*> vec;

    static int count; // .h檔宣告

};

 

int vector<void*>::count = 0// 要在.cpp檔宣告

 

方法2:使用內嵌變數 C++17

inline variable (C++17)

動機:因為編譯 C++ 函式庫通常必需處理很瑣碎的細節,所以一些函式庫作者傾向將整個實作都放在標頭檔,使用者只要引用標頭檔就能直接使用函式庫。這類函式庫我們通常稱為 Header-Only Library(標頭檔函式庫)。然而編寫 Header-Only Library 並不是一件簡單的事。


Inline Variable(內嵌變數)是以 inline 關鍵字修飾的變數(包含全域變數或靜態資料成員);以 constexpr 關鍵字修飾的靜態資料成員也是 Inline Variable

 

template<>

class vector<void*>{

public:

    ...

    ...

private:

    std::vector<void*> vec;

    inline static int count = 0; // Method 1

    inline static int count  {0}; // Method 2

    static int count; // Method 3.1

};

 

inline int vector<void*>::count = 0// Method 3.2 .h檔中宣告

(二)成員函式

static for class,static 成員函式和成員資料,不需要先宣告物件/實例,就可以直接匿名呼叫使用。non-static for instance,必須要先宣告物件/實例,才能使用non-static 成員函式和成員資料。

const 物件只能存取const成員函式,但non-const物件可以存取cons和non-const成員函式,const成員函式不能改變類別的成員資料,但可以存取類別的成員資料

(三)JAVA static物件

C++ const 相似於JAVA final,Java: final

  1. 類別:當宣告在類別上時,該類別就無法被繼承!
  2. 函數:當一個函數被宣告為final時,則繼承他的子類別無法覆寫
  3. 變數:當一個變數被宣告為final時,意思是他是一個常數,是無法被修改的。

在JAVA可以在class中宣告自己的static物件 (objects)/實例 (instances),也就是class中自帶自己的static物件。因此可以在class中宣告:

  1. public static final className variableName = new className(argument list); //合法
  2. public static            className variableName = new className(argument list); // 合法
  3. public              final className variableName = new className(argument list); // 無限遞迴
  4. public                         className variableName = new className(argument list); // 無限遞迴

重點在於static這個保留字,static可避免落入無限遞迴的陷阱中。因為宣告static的變數,在實例化的過程中,static變數早就已經初始化過了,故不會去再去初始化這個變數,因此就不會落入無限遞迴。

static變數的存取,不用真的實例化一個物件,即可直接匿名存取static變數,因為static變數早就已經初始化過了,故可以直接存取其值,例如:className.staticVar。而非static變數的存取,一定要先實例化後,才能存取此變數,因為尚未實例化也代表non-static variable尚未初始化,故沒有值也當然無法存取。

舉個例子,有個類別叫水果,水果類別有顏色和大小這兩個資料。如果顏色是static變數,且在定義時就給定此顏色為紅色。則我們在實例化時,就只需要提供大小去實例化紅色水果。我們可以實例化出紅色大芭樂、紅色中蘋果、紅色小葡萄,因此即使這三個實例尚未被實例化,我們也可以知道此水果類別的顏色是紅色。

而non-static大小就必須要實例化後,才能得知,因爲non-static大小不是所有物件共享的資料;而static 顏色是所有物件都共享的資料,也就是紅色是所有紅色水果共享的特徵。因此如果將水果類別的static 顏色改成黃色,所有的實例顏色全部皆為黃色,即黃色大芭樂、黃色中蘋果、黃色小葡萄。

因此static資料成員是被所有物件所共享的特徵,且此特徵在實例化前,就已經被初始化給值,故在實例化時,就不會理static資料成員,故不會落入無限遞迴。

即使JAVA能夠在類別內宣告自己的內別物件,但C++仍無法:

C++編譯器無法區分這是一個成員函數還是一個成員變數!

static const Type ch("char", static_cast<int>(Tag::_BASIC), 1); // 在Type類別內宣告一個Type物件1

Expected parameter declarator

但在C++中依舊會出錯!因為編譯器會認定這是一個不完整的結構!

static const Type ch = Type("char", static_cast<int>(Tag::_BASIC), 1); // 在Type類別內宣告一個Type物件2

Invalid use of incomplete type 'Type'

因為編譯到這裡時還沒有發現定義,不知道該類或者結構的內部成員,沒有辦法具體的構造一個對象,所以會報錯。

二、相關文章

留言