成員函數的聲明必須在類的內部,它的定義則既能夠在類的內部也能夠在類的外部。做爲接口組成部分的非成員函數,它們的定義和聲明都在類的外部。數組
定義在類內部的函數是隱式的inline函數。app
成員函數經過一個名爲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是隱式的,但它仍然須要遵循初始化規則,意味着(在默認狀況下)咱們不能把this綁定到一個常量對象上。這一狀況也就使得咱們不能在一個常量對象上調用普通的成員函數。調試
緊跟在參數列表後面的const表示this是一個指向常量的指針。像這樣使用const的成員函數被稱做常量成員函數。rest
在常量成員函數中,由於this是指向常量的指針,因此常量成員函數不能改變調用它的對象的內容。code
常量對象,以及常量對象的引用或指針都只能調用常量成員函數。對象
編譯器分兩步處理類:首先編譯成員的聲明,而後才輪到成員函數體(若是有的話)。所以,成員函數體能夠隨意使用類中的其餘成員而無須在乎這些成員出現的次序。接口
當咱們在類的外部定義成員函數時,成員函數的定義必須與它的聲明匹配。例: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關鍵字,在類外部定義時不該重複。
發生隱式轉換的一種狀況是當咱們執行拷貝形式的初始化時(使用=)。此時,咱們只能使用直接初始化而不能使用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的。聚合類使得用戶能夠直接訪問其成員,而且具備特殊的初始化語法形式。當一個類知足以下條件時,咱們說它是聚合的:
struct Data { int ival; string s; };
咱們能夠提供一個花括號括起來的成員初始值列表,並用它初始化聚合類的數據成員:Data val1 = {0, "Anna"};
。初始值的順序必須與聲明的順序一致。Data val2 = {"Anna", 1024};
錯誤。若是初始值列表中的元素個數少於類的成員數量,則靠後的成員被值初始化。初始值列表的元素個數絕對不能超過類的成員數量。
顯式地初始化類的對象存在三個明顯的缺點:
constexpr函數的參數和返回值必須是字面值類型。除了算術類型、引用和指針外,某些類也是字面值類型。和其它類不一樣,字面值類型的類可能含有constexpr函數成員。這樣的成員必須符合constexpr函數的全部要求,它們是隱式const的。
數據成員都是字面值類型的聚合類是字面值常量類。若是一個類不是聚合類,但它符合下述要求,則它也是一個字面值常量類:
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指針參與函數匹配的過程。