-
成員函式的宣告必須在類的內部,它的定義則既可以在類的內部也可以在類的外部,作為介面組成部分的非成員函式,它們的定義和宣告都在類的外部,
-
定義在類內部的函式是隱式的inline函式,
-
成員函式通過一個名為this的額外的隱式引數來訪問呼叫它的那個物件,當我們呼叫一個成員函式時,用請求該函式的物件地址初始化this(相當于Python中的self形參?),偽代碼示意:
Sales_data::isbn(&total),任何對類成員的直接訪問都被看做this的隱式參考,也就是說,當isbn使用bookNo時,它隱式地使用this指向的成員,就像我們書寫了this->bookNo一樣, -
對于我們來說,this形參是隱式定義的,實際上,任何自定義名為this的引數或變數的行為都是非法的,我們可以在成員函式體內部使用this,例:
std::string isbn() const { return this->bookNo; } -
因為this的目的總是指向“這個”物件,所以this是一個常量指標,我們不允許改變this中保存的地址,
-
默認情況下,this的型別是指向型別別非常量版本的常量指標,盡管this是隱式的,但它仍然需要遵循初始化規則,意味著(在默認情況下)我們不能把this系結到一個常量物件上,這一情況也就使得我們不能在一個常量物件上呼叫普通的成員函式,
-
緊跟在引數串列后面的const表示this是一個指向常量的指標,像這樣使用const的成員函式被稱作常量成員函式,
-
在常量成員函式中,因為this是指向常量的指標,所以常量成員函式不能改變呼叫它的物件的內容,
-
常量物件,以及常量物件的參考或指標都只能呼叫常量成員函式,
-
編譯器分兩步處理類:首先編譯成員的宣告,然后才輪到成員函式體(如果有的話),因此,成員函式體可以隨意使用類中的其他成員而無須在意這些成員出現的次序,
-
當我們在類的外部定義成員函式時,成員函式的定義必須與它的宣告匹配,例:
double Sales_data::avg_price() const {...},使用作用域運算子(::),告知編譯器剩余的代碼是位于類的作用域內的,(引數串列和函式體內不用再加作用域運算子) -
內置的賦值運算子把它的左側運算物件當成左值回傳,示例:回傳物件的參考
Sales_data& Sales_data::combine(const Sales_data &rhs) { units_sold += rhs.units_sold; // 把rhs的成員加到this物件的成員上 revenue += rhs.revenue; return *this; // 回傳呼叫該函式的物件 } -
一般來說,如果非成員函式是類介面的組成部分,則這些函式的宣告應該與類在同一個頭檔案中,
-
默認情況下,拷貝類的物件其實拷貝的是物件的資料成員,
-
建構式的名字和類名相同,建構式沒有回傳型別,類可以包含多個建構式,和其他多載函式差不多,不同的建構式之間必須在引數數量或引數型別上有所區別,建構式不能被宣告成const的,當我們創建類的一個const物件時,直到建構式完成初始化程序后,物件才能真正取得其“常量”屬性,因此,建構式在const物件的構造程序中可以向其寫值,
-
類通過一個特殊的建構式來控制默認初始化程序,這個函式叫做默認建構式,默認建構式無須任何實參,如果我們的類沒有顯式地定義建構式,那么編譯器就會為我們隱式地定義一個默認建構式,
-
編譯器創建的建構式又被稱為合成的默認建構式,對于大多數類來說,這個合成的默認建構式將按照如下規則初始化類的資料成員:
- 如果存在類內的初始值,用它來初始化成員,
- 否則,默認初始化該成員,
-
只有當類沒有宣告任何建構式時,編譯器才會自動地生成默認建構式,
-
如果類包含有內置型別或者復合型別(比如陣列和指標)的成員,則只有當這些成員全都被賦予了類內的初始值時(否則它們的值將是未定義的),這個類才適合于使用合成的默認建構式,
-
有的時候編譯器不能為某些類合成默認的建構式,例如:如果類中包含一個其他型別別的成員且這個成員的型別沒有默認建構式,那么編譯器將無法初始化該成員,對于這樣的類來說,我們必須自定義默認建構式,否則該類將沒有可用的默認建構式,
-
如果我們需要默認的行為,那么可以通過在引數串列后面寫上= default來要求編譯器生成建構式,其中,= default既可以和宣告一起出現在類的內部,也可以作為定義出現在類的外部,和其他函式一樣,如果= default在類的內部,則默認建構式是行內的;如果它在類的外部,則該成員默認情況下不是行內的,例:
Sales_data() = default; -
建構式初始值是成員名字的一個串列,每個名字后面緊跟括號括起來的(或者在花括號內的)成員初始值,不同成員的初始化通過逗號分隔開來,例:
Sales_data(const std::string &s): bookNo(s) {} -
當某個資料成員被建構式初始值串列忽略時,它將以與合成默認建構式相同的方式隱式初始化,如果你的編譯器不支持類內初始值,則所有建構式都應該顯式地初始化每個內置型別的成員,
-
沒有出現在建構式初始值串列中的成員將通過相應的類內初始值(如果存在的話)初始化,或者執行默認初始化,
-
物件在幾種情況下會被拷貝,如我們初始化變數以及以值的方式傳遞或回傳一個物件等,當我們使用了賦值運算子時會發生物件的賦值操作,當物件不再存在時執行銷毀的操作,如果我們不主動定義這些操作,則編譯器將替我們合成它們,一般來說,編譯器生成的版本將對物件的每個成員執行拷貝、賦值和銷毀操作,
total = trans; // 處理下一本書的資訊 // 它的行為與下面的代碼相同 total.bookNo = trans.bookNo; total.units_sold = trans.units_sold; total.revenue = trans.revenue; -
使用class和struct定義類唯一的區別就是默認的訪問權限,如果我們使用struct關鍵字,則定義在第一個訪問說明符之前的成員是public的;相反,如果過我們使用class關鍵字,則這些成員是private的,
-
友元宣告只能出現在類定義的內部,但是在類內出現的具體位置不限,友元不是類的成員也不受它所在區域訪問控制級別的約束,一般來說,最好在類定義開始或結束前的位置集中宣告友元,
-
盡管當類的定義發生改變時無需更改用戶代碼,但是使用了該類的源檔案必須重新編譯,
-
友元的宣告僅僅指定了訪問的權限,而非一個通常意義上的函式宣告,如果我們希望類的用戶能夠呼叫某個友元函式,那么我們就必須在友元宣告之外再專門對函式進行一次宣告,為了使友元對類的用戶可見,我們通常把友元的宣告與類本身放置在同一個頭檔案中(類的外部),
-
除了定義資料和函式成員之外,類還可以自定義某種型別在類中的別名,由類定義的型別名字和其他成員一樣存在訪問權限,可以是public或者private中的一種,用來定義型別的成員必須先定義后使用,這一點與普通成員有所區別,因此,型別成員通常出現在類開始的地方,
class Screen { public: typedef std::string::size_type pos; // using pos = std::string::size_type; private: pos cursor = 0; pos height = 0, width = 0; std::string contents; }; -
定義在類內部的成員函式是自動inline的,我們可以在類的內部把inline作為宣告的一部分顯式地宣告成員函式,同樣的,也能在類的外部用inline關鍵字修飾函式的定義,
-
雖然我們無須在宣告和定義的地方同時說明inline,但這么做其實是合法的,不過,最好只在類外部定義的地方說明inline,這樣可以使類更容易理解,和我們在頭檔案中定義inline函式的原因一樣,inline成員函式也應該與相應的類定義在同一個頭檔案中,
-
一個可變資料成員永遠不會是const,即使它是const物件的成員,因此,一個const成員函式可以改變一個可變成員的值,
class Screen { public: void some_member() const; private: // 該成員是個可變成員,因此任何成員函式,包括const函式在內都能改變它的值 mutable size_t access_ctr; // 即使在一個const物件內也能被修改 // 其他成員與之前的版本一致 }; void Screen::some_member() const { ++access_ctr; // 保存一個計數值,用于記錄成員函式被呼叫的次數 // 該成員需要完成的其他作業 } -
當我們提供一個類內初始值時,必須以符號=或者花括號表示(不能用圓括號,會被當成函式),
-
一個const成員函式如果以參考的形式回傳*this,那么它的回傳型別將是常量參考,
-
即使兩個類的成員串列完全一致,它們也是不同的型別,對于一個類來說,它的成員和其他任何類(或者任何其他作用域)的成員都不是一回事兒,
-
我們可以把類名作為型別的名字使用,從而直接指向型別別,或者,我們也可以把類名跟在關鍵字class或struct后面,
Sales_data item1; // 默認初始化Sales_data型別的物件 class Sales_data item1; // 一條等價的宣告 -
就像可以把函式的宣告和定義分離開來一樣,我們也能僅僅宣告類而暫時不定義它:
class Screen;這種宣告有時被稱為前向宣告,它向程式中引入了名字Screen并且指明Screen是一種型別別,在它宣告之后定義之前是一個不完全型別(不清楚它到底包含哪些成員), -
對于不完全型別:可以定義指向這種型別的指標或參考,也可以宣告(但是不能定義)以不完全型別作為引數或者回傳型別的函式,
-
對于一個類來說,在我們創建它的物件之前該類必須被定義過,而不能僅僅被宣告,否則編譯器就無法了解這樣的物件需要多少存盤空間,類似的,類也必須首先被定義,然后才能用參考或指標訪問其成員,畢竟,如果類尚未定義,編譯器也就不清楚該類到底有哪些成員,
-
因為只有當類全部完成后類才算被定義,所以一個類的成員型別不能是該類自己,然而,一旦一個類的名字出現后,它就被認為是宣告過了(但尚未定義),因此類允許包括指向它自身型別的參考或指標:
class Link_screen { Screen window; Link_screen *next; Link_screen *prev; }; -
在類中,可以把普通的非成員函式是定義成友元,也可以把其他類定義成友元,也可以把其他類(之前已定義過的)的成員函式定義成友元,此外,友元函式能定義在類的內部,這樣的函式是隱式行內的,
class Screen { // Window_mgr::clear必須在Screen類之前被宣告 friend void Window_mgr::clear(ScreenIndex); // Screen類的剩余部分 }; -
如果一個類指定了友元類,則友元類的成員函式可以訪問此類包括非公有成員在內的所有成員,
class Screen { // Window_mgr的成員可以訪問Screen類的私有部分 friend class Window_mgr; // Screen類的剩余部分 }; -
友元關系不存在傳遞性,每個類負責控制自己的友元或友元函式,
-
要想令某個成員函式作為友元,我們必須仔細組織程式的結構以滿足宣告和定義的彼此依賴關系,
-
如果一個型別想把一組多載函式宣告成它的友元,它需要對這組函式中的每一個分別宣告,
-
類和非成員函式的宣告不是必須在它們的友元宣告之前,當一個名字第一次出現在一個友元宣告中時,我們隱式地假定該名字在當前作用域中是可見的,然而,友元本身不一定真的宣告在當前作用域中,甚至就算在類的內部定義該函式,我們也必須在類的外部提供相應的宣告從而使得函式可見,換句話說,即使我們僅僅是用宣告友元的類的成員呼叫該友元函式,它也必須是被宣告過的,
struct X { friend void f() { /*友元函式可以定義在類的內部*/ } X() { f(); } // 錯誤:f還沒有被宣告 void g(); void h(); }; void X::g() { return f(); } // 錯誤:f還沒有被宣告 void f(); // 宣告那個定義在X中的函式 void X::h() { return f(); } // 正確:現在f的宣告在作用域中了 -
友元宣告的作用是影響訪問權限,它本身并非普通意義上的宣告,
-
每個類都會定義它自己的作用域,在類的作用域之外,普通的資料和函式成員只能由物件、參考或者指標使用成員訪問運算子來訪問,對于型別別成員則使用作用域運算子訪問,不論哪種情況,跟在運算子之后的名字都必須是對應類的成員,
-
一旦遇到了類名,定義的剩余部分就在類的作用域之內了,這里的剩余部分包括引數串列和函式體,
void Window_mgr::clear(ScreenIndex i) { Screen &s = screens[i]; s.contents = string(s.height * s.width, ' '); } -
函式的回傳型別通常出現在函式名之前,因此當成員函式定義在類的外部時,回傳型別中使用的名字都位于類的作用域之外,這時,回傳型別必須指明它是哪個類的成員(尾置回傳型別在當前類的定義域中),
-
一般的名字查找(尋找與所用名字最匹配的宣告的程序):
- 首先,在名字所在的塊中尋找其宣告陳述句,只考慮在名字的使用之前出現的宣告,
- 如果沒找到,繼續查找外層作用域,
- 如果最終沒有找到匹配的宣告,則程式報錯,
-
對于定義在類內部的成員函式來說,決議其中名字的方式與上述的查找規則有所區別:
- 首先,編譯成員的宣告
- 直到類全部可見后才編譯函式體
-
編譯器處理完類中的全部宣告后才會處理成員函式的定義,這樣可以簡化類代碼的組織方式,這種兩段的處理方式只適用于成員函式體中使用的名字,宣告中使用的名字,包括回傳型別或者引數串列中使用的名字,都必須在使用前確保可見,如果某個成員的宣告使用了類中尚未出現的名字,則編譯器將會在定義該類的作用域中繼續查找(進行一般的名字查找程序),
// 理解下面這段程式代碼 typedef double Money; string bal; class Account { public: Money balance() { return bal; } // 回傳的是成員bal,而非外層作用域的string物件 private: Money bal; // balance函式體在整個類可見后才被處理 // ... }; -
一般來說,內層作用域可以重新定義外層作用域中的名字,即使該名字已經在內層作用域中使用過,然而在類中,如果成員使用了外層作用域中的某個名字,而該名字代表一種型別,則類不能在之后重新定義該名字(因為兩段式的名字查找),
typedef double Money; string bal; class Account { public: Money balance() { return bal; } // 使用外層作用域的Money private: typedef double Money; // 錯誤:不能重新定義Money,即使與外層作用域中的定義完全一致 Money bal; // ... }; -
型別名的定義通常出現在類的開始處,這樣就能確保所有使用該型別的成員都出現在類名的定義之后,
-
成員函式中使用的名字按照如下方式決議:
- 首先,在成員函式內查找該名字的宣告,和前面一樣,只有在函式使用之前出現的宣告才被考慮,(成員函式作用域)
- 如果在成員函式內沒有找到,則在類內繼續查找,這時類的所有成員都可以被考慮,(類作用域)
- 如果類內也沒找到該名字的宣告,在成員函式定義之前(成員函式可以定義在類外)的作用域內繼續查找,(類的外層作用域)
-
盡管類的成員被隱藏了,但我們仍然可以通過加上類的名字或顯式地使用this指標來強制訪問成員,
void Screen::dummy_fcn(pos height) // 不建議隱藏類中同名的成員 { cursor = width * this->height; // 另一種表示該成員的方式 cursor = width * Screen::height; } -
盡管外層的物件被隱藏掉了,但我們仍然可以用作用域運算子訪問它,
void Screen::dummy_fcn(pos height) // 不建議隱藏外層作用域中可能被用到的名字 { cursor = width * ::height; // 全域的那個height } -
當成員定義在類的外部時,名字查找的第三步不僅要考慮類定義之前的全域作用域中的宣告,還需要考慮在成員函式定義之前的全域作用域中的宣告,
// 代碼可以被正常使用 int height; // 定義了一個名字,稍后將在Screen中使用 class Screen { public: typedef std::string::size_type pos; void setHeight(pos); pos height = 0; // 隱藏了外層作用域中的height }; Screen::pos verify(Screen::pos); void Screen::setHeight(pos var) { // var:引數 // height:類的成員 // verify:全域函式 height = verify(var); } -
如果沒有在建構式的初始值串列中顯式地初始化成員,則該成員將在建構式體之前執行默認初始化,
-
建構式的初始值有時必不可少:如果成員是const或者是參考的話,必須將其初始化,類似的,當成員屬于某種型別別且該類沒有定義默認建構式時,也必須將這個成員初始化,
-
隨著建構式體一開始執行,初始化就完成了,我們初始化const或者參考型別的資料成員的唯一機會就是通過建構式初始化,
class ConstRef { public: ConstRef(int ii); private: int i; const int ci; int &ri; }; // 錯誤:ci和ri必須被初始化 ConstRef::ConstRef(int ii) { // 賦值 i = ii; // 正確 ci = ii; // 錯誤:不能給const賦值 ri = i; // 錯誤:不能給const賦值 } // 正確形式:顯式地初始化參考和const成員 ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) { } -
如果成員是const、參考,或者屬于某種未提供默認建構式的型別別,我們必須通過建構式初始值串列為這些成員提供初值,
-
成員的初始化順序與它們在類定義中的出現順序一致,建構式初始值串列中初始值的前后位置關系不會影響實際的初始化順序,
class X { int i; int j; public: // 未定義的:i在j之前被初始化 X(int val): j(val), i(j) { } // 錯誤:試圖使用未定義的值j初始化i }; -
最好令建構式初始值的順序與成員宣告的順序保持一致,而且如果可能的話,盡量避免使用某些成員初始化其他成員,
-
如果可能的話,最好用建構式的引數作為成員的初始值,而盡量避免使用同一個物件的其他成員,這樣的好處是我們可以不必考慮成員的初始化順序,
-
如果一個建構式為所有引數都提供了默認實參,則它實際上也定義了默認建構式,
-
一個委托建構式使用它所屬類的其他建構式執行它自己的初始化程序,或者說它把它自己的一些(或者全部)職責委托給了其他建構式,
class Sales_data { public: // 非委托建構式使用對應的實參初始化成員 Sales_data(std::string s, unsigned cnt, double price): bookNo(s), units_sold(cnt), revenue(cnt*price) { } // 其余建構式全都委托給另一個建構式 Sales_data(): Sales_data("", 0, 0) { } Sales_data(std::string s): Sales_data(s, 0, 0,) { } Sales_data(std::istream &is): Sales_data() { read(is, *this); } // 其他成員與之前的版本一致 } -
當一個建構式委托給另一個建構式時,受委托的建構式的初始值串列和函式體被依次執行(然后才會執行委托者的函式體),
-
當物件被默認初始化或值初始化時自動執行默認建構式,類必須包含一個默認建構式以便在下述情況下使用:
-
默認初始化在以下情況下發生:
- 當我們在塊作用域內不使用任何初始值定義一個非靜態變數或者陣列時,
- 當一個類本身含有型別別的成員且使用合成的默認建構式時(那么這個成員執行默認建構式),
- 當型別別的成員沒有在建構式初始值串列中顯式地初始化時(那么這個成員執行默認建構式),
-
值初始化在以下情況下發生:
- 在陣列初始化的程序中如果我們提供的初始值數量少于陣列的大小時,
- 當我們不使用初始值定義一個區域靜態變數時,
- 當我們通過書寫形如
T()的運算式顯式地請求值初始化時,其中T是型別名(例如vector),
-
不那么明顯的一種情況是類的某些資料成員缺少默認建構式,代碼如下,在實際中,如果定義了其他建構式,那么最好也提供一個默認建構式,
class NoDefault { public: NoDefault(const std::string&); // 還有其他成員,但是沒有其他建構式了 }; struct A { // 默認情況下my_mem是public的 NoDefault my_mem; }; A a; // 錯誤:不能為A合成建構式 struct B { B() { } // 錯誤:b_member沒有初始值 NoDefault b_member; } -
使用默認建構式:
Sales_data obj(); // 錯誤:宣告了一個函式而非物件 Sales_data obj2; // 正確:obj2是一個物件而非函式 -
如果建構式只接受一個實參,則它實際上定義了轉換為此型別別的隱式轉換機制,有時我們把這種建構式稱作轉換建構式,能通過一個實參呼叫的建構式定義了一條從建構式的引數型別向型別別隱式轉換的規則,
-
編譯器只會自動地執行一步型別轉換,例如,因為下面的代碼隱式地使用了兩種轉換規則,所以它是錯誤的:
// 錯誤:需要用戶定義的兩種轉換: // (1)把"9-999-99999-9"轉換成string // (2)再把這個(臨時的)string轉換成Sales_data item.combine("9-999-99999-9"); // 正確:顯式地轉換成string,隱式地轉換成Sales_data item.combine(string("9-999-99999-9")); // 正確:隱式地轉換成string,顯式地轉換成Sales_data item.combine(Sales_data("9-999-99999-9")); // 通過讀取標準輸入創建了一個(臨時的)Sales_data物件,隨后將得到的物件傳遞給combine, item.combine(cin); // Sales_data物件是個臨時量,一旦combine完成我們就不能再訪問它了,實際上,我們構建了一個物件,先將它的值加到item中,隨后將其丟棄, -
在要求隱式轉換的程式背景關系中,我們可以通過將建構式宣告為
explicit加以阻止,此時,沒有任何建構式能用于隱式地創建Sales_data物件,之前的兩種用法都無法通過編譯:item.combine(null_book); // 錯誤:string建構式是explicit的 item.combine(cin); // 錯誤:istream建構式時explicit的 -
關鍵字explicit只對一個實參的建構式有效,需要多個實參的建構式不能用于執行隱式轉換,所以無需將這些建構式指定為explicit的,只能在類內宣告建構式時使用explicit關鍵字,在類外部定義時不應重復,
- inline是用于實作的關鍵字(放在定義處)
- static是用于宣告的關鍵字(放在宣告處)
- explicit是用于宣告的關鍵字(放在宣告處)
- friend是用于宣告的關鍵字(放在宣告處)
-
發生隱式轉換的一種情況是當我們執行拷貝形式的初始化時(使用=),此時,我們只能使用直接初始化而不能使用explicit建構式,
Sales_data item1(null_book); // 正確:直接初始化 // 錯誤:不能將explicit建構式用于拷貝形式的初始化程序 Sales_data item2 = null_book; -
當我們用explicit關鍵字宣告建構式時,它將只能以直接初始化的形式使用,而且,編譯器將不會在自動轉換程序中使用該建構式,
-
盡管編譯器不會將explicit的建構式用于隱式轉換程序,但是我們可以使用這樣的建構式顯式地強制進行轉換,
// 正確:實參是一個顯式構造的Sales_data物件 item.combine(Sales_data(null_book)); // 正確:static_cast可以使用explicit的建構式 item.combine(static_cast<Sales_data>(cin)); -
我們用過的一些標準庫中的類含有單引數的建構式:
- 接受一個單引數的
const char*的string建構式不是explicit的, - 接受一個容量引數的vector建構式是explicit的,
- 接受一個單引數的
-
聚合類使得用戶可以直接訪問其成員,并且具有特殊的初始化語法形式,當一個類滿足如下條件時,我們說它是聚合的:
- 所有成員都是public的
- 沒有定義任何建構式
- 沒有類內初始值
- 沒有基類,也沒有virtual函式
struct Data { int ival; string s; }; -
我們可以提供一個花括號括起來的成員初始值串列,并用它初始化聚合類的資料成員:
Data val1 = {0, "Anna"};,初始值的順序必須與宣告的順序一致,Data val2 = {"Anna", 1024};錯誤,如果初始值串列中的元素個數少于類的成員數量,則靠后的成員被值初始化,初始值串列的元素個數絕對不能超過類的成員數量, -
顯式地初始化類的物件存在三個明顯的缺點:
- 要求類的所有成員都是public的
- 將正確初始化每個物件的每個成員的重任交給了類的用戶(而非類的作者),因為用戶很容易忘掉某個初始值,或者提供一個不恰當的初始值,所以這樣的初始化程序冗長乏味且容易出錯,
- 添加或洗掉一個成員之后,所有的初始化陳述句都需要更新,
-
constexpr函式的引數和回傳值必須是字面值型別,除了算術型別、參考和指標外,某些類也是字面值型別,和其它類不同,字面值型別的類可能含有constexpr函式成員,這樣的成員必須符合constexpr函式的所有要求,它們是隱式const的,
-
資料成員都是字面值型別的聚合類是字面值常量類,如果一個類不是聚合類,但它符合下述要求,則它也是一個字面值常量類:
- 資料成員都必須是字面值型別
- 類必須至少含有一個constexpr建構式
- 如果一個資料成員含有類內初始值,則內置型別成員的初始值必須是一條常量運算式;或者如果成員屬于某種型別別,則初始值必須使用成員自己的constexpr建構式,
- 類必須使用解構式的默認定義,該成員負責銷毀類的物件,
-
constexpr建構式必須既符合建構式的要求(意味著不能包含回傳陳述句),又符合constexpr函式的要求(意味著它能擁有的唯一可執行陳述句就是回傳陳述句),綜合這兩點可知,constexpr建構式體一般來說應該是空的,
class Debug { public: constexpr Debug(bool b = true): hw(b), io(b), other(b) { } constexpr Debug(bool h, bool i, bool o): hw(h), io(i), other(o) { } constexpr bool any() { return hw || io || other; } void set_io(bool b) { io = b; } void set_hw(bool b) { hw = b; } void set_other(bool b) { other = b; } private: bool hw; // 硬體錯誤,而非IO錯誤 bool io; // IO錯誤 bool other; // 其他錯誤 }; constexpr Debug io_sub(false, true, false); // 除錯IO if (io_sub.any()) // 等價于if(true) cerr << "print appropriate error messages" << endl; constexpr Debug prod(false); // 無除錯 if (prod.any()) // 等價于if(false) cerr << "print an error message" << endl; -
constexpr建構式必須初始化所有資料成員,初始值(或者)使用constexpr建構式或者是一條常量運算式,
-
constexpr建構式用于生成constexpr物件以及constexpr函式的引數或回傳型別,
-
我們通過在成員的宣告之前加上關鍵字static使得其與類關聯在一起,和其他成員一樣,靜態成員可以是public的或private的,靜態資料成員的型別可以是常量、參考、指標、型別別等,
-
類的靜態成員存在于任何物件之外,物件中不包含任何與靜態資料成員有關的資料,
-
類似的,靜態成員函式也不與任何物件系結在一起,它們不包含this指標,作為結果,靜態成員函式不能宣告成const的,而且我們也不能在static函式體內使用this指標,這一限制既適用于this的顯式使用,也對呼叫非靜態成員的隱式使用有效,
-
使用類的靜態成員:
- 使用作用域運算子直接訪問靜態成員:
r = Account::rate(); - 雖然靜態成員不屬于類的某個物件,但是我們仍然可以使用類的物件、參考或者指標來訪問靜態成員:
r = ac1.rate()、r = ac2->rate() - 成員函式不用通過作用域運算子就能直接使用靜態成員:
class Account { public: void calculate() { amount += amount * interestRate; } static double rate() { return interestRate; } static void rate(double); private: std::string owner; double amount; static double interestRate; static double initRate(); }; - 使用作用域運算子直接訪問靜態成員:
-
我們既可以在類的內部也可以在類的外部定義靜態成員函式,當在類的外部定義靜態成員時,不能重復static關鍵字,該關鍵字只出現在類內部的宣告陳述句:
void Account::rate(double newRate) { interestRate = newRate; } -
和類的所有成員一樣,當我們指向類外部的靜態成員時,必須指明成員所屬的類名,
-
因為靜態資料成員不屬于類的任何一個物件,所以它們并不是在創建類的物件時被定義的,這意味著它們不是由類的建構式初始化的,而且一般來說,我們不能在類的內部初始化靜態成員,相反的,必須在類的外部定義和初始化每個靜態成員,和其它物件一樣,一個靜態資料成員只能定義一次,要想確保物件只定義一次,最好的辦法是把靜態資料成員的定義與其他非行內函式的定義放在同一個檔案中,
-
類似于全域變數,靜態資料成員定義在任何函式之外,因此一旦它被定義,就將一直存在于程式的整個生命周期中,
-
從類名開始,這條定義陳述句的剩余部分就都位于類的作用于之內了,和其他成員的定義一樣,interestRate的定義也可以訪問類的私有成員,
// 定義并初始化一個靜態成員 double Account::interestRate = initRate(); // 可以直接使用initRate函式 -
通常情況下,類的靜態成員不應該在類的內部初始化,然而,我們可以為靜態成員提供const整數型別的類內初始值,不過要求靜態成員必須是字面值常量型別的constexpr,(除了靜態常量成員之外,其他靜態成員不能在類的內部初始化,)初始值必須是常量運算式,因為這些成員本身就是常量運算式,所以它們能用在所有適合于常量運算式的地方,例如:
class Account { public: static double rate() { return interestRate; } static void rate(double); private: static constexpr int period = 30; // period是常量運算式 double daily_tbl[period]; }; -
如果在類的內部提供了一個初始值,則成員的定義不能再指定一個初始值了,
// 一個不帶初始值的靜態成員的定義 constexpr int Account::period; // 初始值在類的定義內提供 -
即使一個常量靜態資料成員在類內部被初始化了,通常情況下也應該在類的外部定義一下該成員,
-
靜態成員的優點包括:作用域位于類的范圍之內,避免與其他類的成員或者全域作用域的名字沖突;可以是私有成員,而全域物件不可以;通過閱讀程式可以非常容易地看出靜態成員與特定類關聯,使得程式的含義清晰明了,
-
靜態資料成員可以是不完全型別,特別的,靜態資料成員的型別可以就是它所屬的型別別,非靜態資料成員只能宣告成它所屬類的指標或參考,
-
我們可以使用靜態成員作為默認實參,非靜態資料成員不能作為默認實參,因為它的值本身屬于物件的一部分,這么做的結果是無法真正提供一個物件以便從中獲取成員的值,最終將引發錯誤,(普通成員函式包含this形參,但因為函式引數決議順序是未定的,所以該默認值也是未定的,
int function(class_type *this, int n = this->a)) -
建構式初始值串列:未經初始值串列初始化的成員將被默認初始化,
-
成員函式:類的函式成員,普通的函式通過隱式的this指標與類的物件系結在一起;靜態成員函式不與物件系結在一起也沒有this指標,成員函式可以多載,此時隱式的this指標參與函式匹配的程序,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/251342.html
標籤:C++
上一篇:STL_vector容器
