類--定義和友元
一、類的定義
1、成員函式
1)定義成員函式
成員函式的宣告必須在類的內部,它的定義既可以在類的內部也可以在類的外部。定義在類內部的成員函式是隱式的inline函式。
成員函式通過一個名為this的額外的隱式引數來訪問呼叫它的那個物件。當我們呼叫一個成員函式時,用請求該函式的物件地址初始化this。因為this的目的總是指向“這個”物件,所以this是一個常量指標,我們不允許改變this中儲存的地址。當一個成員呼叫另外一個成員時,this指標在其中隱式地傳遞。
2)const成員函式
儘管this是隱式的,但它仍然需要遵循初始化規則,意味著(在預設情況下)我們不能把this繫結到一個常量物件上。這一情況也就使得我們不能在一個常量物件上呼叫普通的成員函式。
如果this不是隱式的,我們直接把this宣告成指向常量的常量指標就可以了。然而,this是隱式的並且不會出現在引數列表中,所以在哪兒將this宣告成指向常量的指標就成為我們必須面對的問題。C++語言的做法是允許把const關鍵字放在成員函式的引數列表之後不,此時,緊跟在引數列表後面的const表示this是一個指向常量的指標。像這樣使用const的成員函式被稱作常量成員函式。常量成員函式不能改變呼叫它的物件的內容。
1 std::string isbn() const;
常量物件,以及常量物件的引用或指標都只能呼叫常量成員函式。
3)類作用域和成員函式
類本身就是一個作用域。類的成員函式的定義巢狀在類的作用域之內。
編譯器分兩步處理類:首先編譯成員的宣告,然後才輪到成員函式。因此,成員函式體,可以隨意使用類中的其他成員而無須在意這些成員出現的次序。
4)在類的外部定義成員函式
當我們在類的外部定義成員函式時,成員函式的定義必須與它的宣告匹配。也就是說,返回型別、引數列表和函式名都得與類內部的宣告保持一致。如果成員被宣告為常量成員函式,那麼它的定義也必須在引數列表後明確指定const屬性。
1 std::string SalesData::isbn() const 2 { 3 return this->m_book_no; 4 }
5)返回this物件的函式
我們無須使用隱式的this指標訪問函式呼叫者的某個具體成員,而是需要把呼叫函式的物件當成一個整體來訪問:
1 SalesData &SalesData::combine(const SalesData &rhs) 2 { 3 m_units_sold += rhs.m_units_sold; 4 m_revenue += rhs.m_revenue; 5 return *this; // 返回呼叫該函式的物件 6 }
2、定義類相關的非成員函式
類的作者常常需要定義一些輔助函式,比如add、read、print等。儘管這些函式定義的操作從概念上來說屬於類的介面組成部分,但它們實際上並不屬於類本身。
一般來說,如果非成員函式是類介面的組成部分,則這些函式的宣告應該與類宣告在同一個標頭檔案內。在這種方式下,使用者使用介面的任何部分都只需要引入一個檔案。
3、建構函式
建構函式的任務是初始化類物件的資料成員,無論何時只要類的物件被建立,就會執行建構函式。
建構函式不能被宣告成const的。當我們建立一個類的const物件時,直到建構函式完成初始化過程,物件才能真正取得其“常量”屬性。因此,建構函式在const物件的構造過程中可以向其寫值。
1)合成的預設建構函式
如果我們沒有為類的物件提供初始值,那麼類就會執行預設初始化。類通過一個特殊的建構函式來控制預設初始化過程,這個函式叫做預設建構函式。預設建構函式無須任何實參。
如果類沒有顯示地定義建構函式,那麼編譯器就會為我們隱式地定義一個預設建構函式。編譯器建立的建構函式又被稱為合成的預設建構函式。對於大多數類來說,這個合成的預設建構函式將按照如下規則初始化類的資料成員:如果存在類內初始值,用它來初始化成員;否則,預設初始化該成員。
2)某些類不能依賴於合成的預設建構函式
a、編譯器只有與在發現類不包含任何建構函式的情況下才會替我們生成一個預設的建構函式。
b、對於某些類來說,合成的預設建構函式可能執行錯誤的操作。如果定義在塊中的內建型別或複合型別(比如陣列和指標)的物件被預設初始化,則它們的值將是未定義的。該準則同樣適用於預設初始化的內建型別成員。因此,含有內建型別或複合型別成員的類應該在類的內部初始化這些成員,或者定義一個自己的預設建構函式。否則,使用者在建立類的物件時就可能得到未定義的值。如果類包含有內建型別或複合型別的成員,則只有當這些成員全都被賦予了類內的初始值時,這個類才適合於使用合成的預設建構函式。
c、有的時候編譯器不能為某些類合成預設的建構函式。例如,如果類中包含一個其他類型別的成員且這個成員的型別沒有預設建構函式,那麼編譯器將無法初始化該成員。對於這樣的類來說,我們必須自定義預設建構函式,否則該類將沒有可用的預設建構函式。
3)=default的含義
在C++11標準中,如果我們需要預設的行為,那麼可以通過在引數列表後面寫上 = default 來要求編譯器生成建構函式。其中,= default既可以和宣告一起出現在類的內部,也可以作為定義出現在類的外部。和其他函式一樣,如果= default在類的內部,則預設建構函式是內聯的;如果它在類的外部,則該成員預設情況下不是內聯的。
如果你的編譯器不支援類內初始值,那麼你的預設建構函式應該使用建構函式初始值列表來初始化類的每個成員。
4)建構函式初始值列表
1 SalesData(const std::string &s) :m_book_no(s){} 2 SalesData(const std::string &s, unsigned n, double p):
冒號與花括號之間的部分就是建構函式初始值列表,它負責為新建立的物件的一個或幾個資料成員賦初始值。建構函式初始值是成員名字的一個列表,每個名字後面緊跟括號括起來的成員初始值,不同成員的初始化通過逗號分開來。沒有出現在建構函式初始值列表中的成員將通過相應的類內初始值(如果存在的話)初始話,或者執行預設初始化。
如果你的編譯器不支援類內初始值,則所有建構函式都應該顯示地初始化每個內建型別的成員。
4、拷貝、賦值和析構
如果我們不主動定義這些操作,則編譯器將替我們合成它們。一般來說,編譯器生成的版本將對物件的每個成員執行拷貝、賦值和銷燬操作。
儘管編譯器能替我們合成拷貝、賦值和銷燬的操作,但是必須要清楚的一點是,對於某些類來說合成的版本無法正常工作。特別是,當類需要分配類物件之外的資源時,合成的版本常常會失效。
二、訪問控制與封裝
1、訪問說明符
定義在public說明符之後的成員在整個程式內可被訪問,public成員定義類的介面。
定義在private說明符之後的成員可以被類的成員函式訪問,但是不能被使用該類的程式碼訪問,private部分封裝了類的實現細節。
一個類可以包含0個或多個訪問說明符,而且對於某個訪問說明符能出現多少次也沒有嚴格限定。每個訪問說明符指定了接下來的成員的訪問級別,其有效範圍直到出現下一個訪問說明符或者到達類的結尾處為止。
2、使用class或struct關鍵字
使用class或struct定義類的唯一區別就是預設的訪問許可權。
類可以在它的第一個訪問說明符之前定義成員,對這種成員的訪問許可權依賴於類定義的方式。如果我們使用struct關鍵字,則定義在第一個訪問說明符之前的成員是public的;相反,如果我們使用class關鍵字,則這些成員是private的。
3、友元
類可以允許其他類或者函式訪問它的非公有成員,方法是令其他類或者函式成為它的友元。如果類想把一個函式作為它的友元,只需要增加一條以friend關鍵字開始的函式宣告語句即可。
1 class SalesData 2 { 3 // 友元宣告 4 friend SalesData add(const SalesData &, const SalesData &); 5 friend std::ostream &print(std::ostream &, const SalesData &); 6 friend std::istream &read(std::istream &, SalesData &); 7 public: 8 SalesData() = default; 9 SalesData(const std::string &s) :m_book_no(s){} 10 SalesData(const std::string &s, unsigned n, double p): 11 m_book_no(s), m_units_sold(n), m_revenue(p * n){} 12 13 std::string isbn() const; 14 SalesData &combine(const SalesData &); 15 private: 16 double avgPrice() const; 17 18 std::string m_book_no; // 書名 19 unsigned m_units_sold = 0; // 數量 20 double m_revenue = 0.0; // 總價 21 }; 22 23 // SalesData的非成員介面函式的宣告 24 SalesData add(const SalesData &, const SalesData &); 25 std::ostream &print(std::ostream &, const SalesData &); 26 std::istream &read(std::istream &, SalesData &);View Code
友元宣告只能出現在類的內部,但是在類內出現的具體位置不限。友元不是類的成員也不受它所在區域的訪問控制級別的約束。
友元的宣告僅僅指定了訪問的許可權,而非一個通常意義上的函式宣告。如果我們希望類的使用者能夠呼叫某個友元函式,那麼我們就必須在友元宣告之外再專門對函式進行一次宣告。
為了使友元對類的使用者可見,我們通常把友元的宣告與類本身放置在同一個標頭檔案中(類的外部)。
一個完整的列子:
SalesData.h
1 #ifndef SALES_DATA 2 #define SALES_DATA 3 4 #include <iostream> 5 #include <string> 6 class SalesData 7 { 8 // 友元宣告 9 friend SalesData add(const SalesData &, const SalesData &); 10 friend std::ostream &print(std::ostream &, const SalesData &); 11 friend std::istream &read(std::istream &, SalesData &); 12 public: 13 SalesData() = default; 14 SalesData(const std::string &s) :m_book_no(s){} 15 SalesData(const std::string &s, unsigned n, double p): 16 m_book_no(s), m_units_sold(n), m_revenue(p * n){} 17 18 std::string isbn() const; 19 SalesData &combine(const SalesData &); 20 private: 21 double avgPrice() const; 22 23 std::string m_book_no; // 書名 24 unsigned m_units_sold = 0; // 數量 25 double m_revenue = 0.0; // 總價 26 }; 27 28 // SalesData的非成員介面函式的宣告 29 SalesData add(const SalesData &, const SalesData &); 30 std::ostream &print(std::ostream &, const SalesData &); 31 std::istream &read(std::istream &, SalesData &); 32 33 #endifView Code
SalesData.cpp
1 #include "SalesData.h" 2 #include <string> 3 4 std::string SalesData::isbn() const 5 { 6 return this->m_book_no; 7 } 8 9 SalesData &SalesData::combine(const SalesData &rhs) 10 { 11 m_units_sold += rhs.m_units_sold; 12 m_revenue += rhs.m_revenue; 13 return *this; // 返回呼叫該函式的物件 14 } 15 16 double SalesData::avgPrice() const 17 { 18 if (m_units_sold) 19 return m_revenue / m_units_sold; 20 else 21 return 0; 22 } 23 24 SalesData add(const SalesData &lhs, const SalesData &rhs) 25 { 26 SalesData sum = lhs; 27 sum.combine(rhs); 28 return sum; 29 } 30 31 std::ostream &print(std::ostream &os, const SalesData &item) 32 { 33 os << item.isbn() << " " << item.m_units_sold << " " 34 << item.m_revenue << " " << item.avgPrice(); 35 return os; 36 } 37 38 std::istream &read(std::istream &is, SalesData &item) 39 { 40 double price = 0; 41 is >> item.m_book_no >> item.m_units_sold >> price; 42 item.m_revenue = price * item.m_units_sold; 43 return is; 44 }View Code
main.cpp
1 #include <iostream> 2 #include <string> 3 #include "../header/SalesData.h" 4 5 int main() 6 { 7 SalesData s1("book", 10,5.5), s2("book",10,5.5), sum; 8 sum = add(s1, s2); 9 print(std::cout, sum); 10 std::cout << std::endl; 11 return 0; 12 }View Code