C++ Primer 筆記-第7章 類(lèi)

類(lèi)的基本思想是:數(shù)據(jù)抽象 data abstraction?和?封裝 encapsulation。
數(shù)據(jù)抽象?是一種依賴(lài)于?接口 interface?和?實(shí)現(xiàn) implementation?分離的編程(以及設(shè)計(jì))技術(shù)。
類(lèi)的?接口?包括用戶(hù)所能執(zhí)行的操作;類(lèi)的?實(shí)現(xiàn)?則包括類(lèi)的數(shù)據(jù)成員、負(fù)責(zé)接口實(shí)現(xiàn)的函數(shù)體以及定義類(lèi)所需的各種私有函數(shù)。
封裝?實(shí)現(xiàn)了類(lèi)的接口和實(shí)現(xiàn)的分離。封裝后的類(lèi)隱藏了它的實(shí)現(xiàn)細(xì)節(jié),類(lèi)的用戶(hù)只能使用接口而無(wú)法訪(fǎng)問(wèn)實(shí)現(xiàn)部分。

7.1 定義抽象數(shù)據(jù)類(lèi)型
7.1.2 定義改進(jìn)的?Sales_data
?類(lèi)
成員函數(shù)通過(guò)一個(gè)名為?
this
?的額外隱式參數(shù)來(lái)訪(fǎng)問(wèn)調(diào)用它的那個(gè)對(duì)象。當(dāng)調(diào)用一個(gè)成員函數(shù)時(shí),用請(qǐng)求該函數(shù)的對(duì)象地址初始化?this
。

struct Sales_data {
? std::string isbn() const {return bookNo;} ? // 隱式inline
? std::string bookNo; }
Sales_data total;
total.isbn();
// 編譯器負(fù)責(zé)把 total 的地址傳遞給 isbn() 的隱式形參 this
// 可以等價(jià)地認(rèn)為編譯器將該調(diào)用重寫(xiě)成了:
Sales_data::isbn(&total); ? // 偽代碼

任何自定義名為?
this
?的參數(shù)或變量的行為都是非法的。this
?是個(gè)常量指針,初始化該指針之后,不再允許改變?this
?中保存的地址。例如在?Sales_data
?成員函數(shù)中,this
?的類(lèi)型是?Sales_data *const
(頂層?const
)。this
?是隱式的而且不會(huì)出現(xiàn)在參數(shù)列表中,所以在哪里將?this
?聲明成指向常量的指針就是我們需要面對(duì)的問(wèn)題。C++語(yǔ)言的做法是允許把?const
?關(guān)鍵字放在成員函數(shù)的參數(shù)列表之后,此時(shí)?this
?是一個(gè)指向常量對(duì)象的常量指針(const Sales_data *const this
),向這樣使用?const
?的成員函數(shù)被稱(chēng)作?常量成員函數(shù)。

// 偽代碼:
std::string Sales_data::isbn(const Sales_data *const this) {
? return this->bookNo;
}

7.1.3 定義類(lèi)相關(guān)的非成員函數(shù)
IO類(lèi)屬于不能被拷貝的類(lèi)型,因此我們只能通過(guò)引用來(lái)傳遞它們。

7.2 訪(fǎng)問(wèn)控制與封裝
使用?
class
?和?struct
?定義類(lèi)的唯一的區(qū)別就是默認(rèn)的訪(fǎng)問(wèn)權(quán)限。
7.2.1 友元
類(lèi)可以允許其他類(lèi)或者函數(shù)訪(fǎng)問(wèn)它的非公有成員,方式是令其他類(lèi)或者函數(shù)成為它的?友元 friend
封裝有兩個(gè)重要的優(yōu)點(diǎn):
· 確保用戶(hù)代碼不會(huì)無(wú)意間破壞封裝對(duì)象的狀態(tài)。
· 被封裝的類(lèi)的具體實(shí)現(xiàn)細(xì)節(jié)可以隨時(shí)改變,而無(wú)須調(diào)整用戶(hù)級(jí)別的代碼

7.3 類(lèi)的其他特性
7.3.1 類(lèi)成員再探
一個(gè)?可變數(shù)據(jù)成員 mutable data member?(通過(guò)在變量的聲明中加入?
mutable
?關(guān)鍵字)永遠(yuǎn)不會(huì)是?const
, 即使它是?const
?對(duì)象的成員。因此,一個(gè)?const
?成員函數(shù)可以改變一個(gè)可變成員的值。當(dāng)提供一個(gè)類(lèi)內(nèi)初始值時(shí),必須以符號(hào)?
=
?或者?{}
?表示。
7.3.3 類(lèi)類(lèi)型
因?yàn)橹挥挟?dāng)類(lèi)全部完成后類(lèi)才算被定義,所以一個(gè)類(lèi)的成員類(lèi)型不能是該類(lèi)自己。然而,一旦一個(gè)類(lèi)的名字出現(xiàn)后,它就被認(rèn)為是聲明過(guò)了(但尚未定義),因此類(lèi)允許包含指向它自身類(lèi)型的引用或指針。
7.3.4 友元再探
友元關(guān)系不具有傳遞性。
如果一個(gè)類(lèi)想把一組重載函數(shù)聲明成它的友元,它需要對(duì)這組函數(shù)中的每一個(gè)函數(shù)分別聲明。

7.4 類(lèi)的作用域
當(dāng)我們?cè)陬?lèi)的外部定義成員函數(shù)時(shí),必須同時(shí)提供類(lèi)名和函數(shù)名。一旦遇到了類(lèi)名,定義的剩余部分就在類(lèi)的作用域之內(nèi)了,這里的剩余部分包括參數(shù)列表和函數(shù)體,此時(shí)我們可以直接使用類(lèi)的其他成員而無(wú)需再次授權(quán)。但是函數(shù)的返回類(lèi)型通常出現(xiàn)在函數(shù)名的前面,此時(shí)返回類(lèi)型中使用的名字都位于類(lèi)的作用域之外,所以必須聲明它是哪個(gè)類(lèi)的成員。

class Screen{}
class Window_mgr {
public:
? using ScreenIndex = std::vector<Screen>::size_type;
? ScreenIndex addScreen(const Screen&); ? ?
}
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen& s){}

7.4.1 名字查找與類(lèi)的作用域
編譯器處理完類(lèi)中的全部聲明后才會(huì)處理成員函數(shù)的定義。
一般來(lái)說(shuō),內(nèi)層作用域可以重新定義外層作用域的名字,即使該名字已經(jīng)在內(nèi)層作用域中使用過(guò)。然而在類(lèi)中,如果成員使用了外層作用域中的某個(gè)名字,而該名字代表一種類(lèi),則類(lèi)不能在之后重新定義該名字:

typedef double Money;
class Account {
public:
? ?Money balance(){ return bal; } ?// 使用外層作用域的Money
private:
? typedef double Money; ? ? ? ? ? // 錯(cuò)誤:不能重新定義 Money
? Money bal;
}

即使?
Account
?中定義的?Money
?類(lèi)型與外層作用域一致,上述代碼仍然是錯(cuò)誤的。但是編譯器并不為此負(fù)責(zé),一些編譯器仍將順利通過(guò)這樣的代碼,而忽略代碼錯(cuò)誤的事實(shí)。類(lèi)型名的定義通常出現(xiàn)在類(lèi)的開(kāi)始處,這樣就能確保所有使用該類(lèi)型的成員都出現(xiàn)在類(lèi)名的定義之后。

7.5 構(gòu)造函數(shù)再探
7.5.1 構(gòu)造函數(shù)初始值列表
成員的初始化順序與它們?cè)陬?lèi)定義中出現(xiàn)順序一致。盡量避免使用某些成員初始化其他成員。
7.5.2 委托構(gòu)造函數(shù)
C++11 新標(biāo)準(zhǔn)擴(kuò)展了構(gòu)造函數(shù)初始值的功能,使得我們可以定義所謂的?委托構(gòu)造函數(shù) delegating constructor。一個(gè)委托構(gòu)造函數(shù)使用它所屬類(lèi)的其他構(gòu)造函數(shù)執(zhí)行它自己的初始化過(guò)程,或者說(shuō)它把它自己的一些(或者全部)職責(zé)委托給了其他構(gòu)造函數(shù)。

class Sales_data {
public:
?
// 非委托構(gòu)造函數(shù)
? Sales_data(int a) {
? ? ? // 實(shí)現(xiàn)a
?
}
? // 委托構(gòu)造函數(shù),該函數(shù)會(huì)先調(diào)用實(shí)現(xiàn)a,然后再調(diào)用實(shí)現(xiàn)b
? Sales_data(): Sales_data(0) {
? ? ? ?// 實(shí)現(xiàn)b
? }
}

7.5.4 隱式的類(lèi)類(lèi)型轉(zhuǎn)換
如果構(gòu)造函數(shù)只接受一個(gè)實(shí)參,則它實(shí)際上定義了轉(zhuǎn)換為此類(lèi)類(lèi)型的隱式轉(zhuǎn)換機(jī)制,稱(chēng)為?轉(zhuǎn)換構(gòu)造函數(shù)converting constructor。
編譯器值會(huì)自動(dòng)地執(zhí)行一步類(lèi)型轉(zhuǎn)換。如該調(diào)用需類(lèi)型轉(zhuǎn)換多次,則應(yīng)使用顯示類(lèi)型轉(zhuǎn)換。
使用?
explicit
?關(guān)鍵字聲明構(gòu)造函數(shù)時(shí),該構(gòu)造函數(shù)只能以直接初始化的形式使用,且編譯器將不會(huì)在自動(dòng)轉(zhuǎn)換過(guò)程中使用該構(gòu)造函數(shù)。

std::string str;
Sales_data item1(str); ?// 正確:直接初始化
Sales_data item2 = str; // 錯(cuò)誤,不能用于拷貝形式的初始化過(guò)程。

關(guān)鍵字?
explicit
?只對(duì)一個(gè)實(shí)參的構(gòu)造函數(shù)有效。只能在類(lèi)內(nèi)聲明構(gòu)造函數(shù)時(shí)使用,在類(lèi)外部定義時(shí)不應(yīng)重復(fù)。盡管編譯器不會(huì)將?
explicit
?的構(gòu)造函數(shù)用于隱式轉(zhuǎn)換過(guò)程,但是我們可以使用?Sales_data(str)
?或者?static_cast<Sales_data>(str)
?這兩種方式顯示地強(qiáng)制進(jìn)行轉(zhuǎn)換。
7.5.5 聚合類(lèi)
聚合類(lèi) aggregate class?只含有公有成員的類(lèi),并且沒(méi)有類(lèi)內(nèi)初始值或者構(gòu)造函數(shù)。聚合類(lèi)的成員可以用花括號(hào)括起來(lái)的初始值列表進(jìn)行初始化。
7.5.7 字面值常量類(lèi)
數(shù)據(jù)成員都是字面值類(lèi)型的聚合類(lèi)是字面值常量類(lèi)。
如果一個(gè)類(lèi)不是聚合類(lèi),但是它符合下述要求,它也是一個(gè)字面值常量類(lèi):
· 數(shù)據(jù)成員都必須是字面值類(lèi)型。 · 類(lèi)必須至少含有一個(gè)?
constexpr
?構(gòu)造函數(shù)。 · 如果一個(gè)數(shù)據(jù)成員含有類(lèi)內(nèi)初始值,則內(nèi)置類(lèi)型成員的初始值必須是一條常量表達(dá)式;或者如果成員屬于某種類(lèi)類(lèi)型,則初始值必須使用成員自己的?constexpr
?構(gòu)造函數(shù)。 · 類(lèi)必須使用析構(gòu)函數(shù)的默認(rèn)定義,該成員負(fù)責(zé)銷(xiāo)毀類(lèi)的對(duì)象。constexpr
?構(gòu)造函數(shù)體一般來(lái)說(shuō)應(yīng)該是空。且必須初始化所有數(shù)據(jù)成員。

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; }
private:
? bool hw;
? bool io;
? bool other;
}
constexpr Debug io_sub(false, true, false);
constexpr Debug prod(false);
io_sub.any();
prod.any();


7.6 類(lèi)的靜態(tài)成員
有的時(shí)候類(lèi)需要它的一些成員與類(lèi)本身直接相關(guān),而不是與類(lèi)的各個(gè)對(duì)象保持關(guān)聯(lián)。
類(lèi)的靜態(tài)成員存在于任何對(duì)象之外,對(duì)象中不包含任何于靜態(tài)數(shù)據(jù)成員有關(guān)的數(shù)據(jù)。類(lèi)的靜態(tài)成員對(duì)象只有一個(gè),且被所有該類(lèi)對(duì)象共享。
靜態(tài)成員函數(shù)也不于任何對(duì)象綁定在一起,它們不包含?
this
?指針。不能聲明成?const
。這一限制既適用于?this
?的顯示使用,也對(duì)調(diào)用?非靜態(tài)成員的隱式使用?有效。因?yàn)殪o態(tài)數(shù)據(jù)成員不屬于類(lèi)的任何一個(gè)對(duì)象,所以它們并不是在創(chuàng)建類(lèi)的對(duì)象時(shí)被定義的。這意味著它們不是由類(lèi)的構(gòu)造函數(shù)初始化的,必須在類(lèi)的外部定義和初始化每個(gè)靜態(tài)成員,且一個(gè)靜態(tài)數(shù)據(jù)成員只能定義一次。一旦它被定義,就將一直存在于程序的整個(gè)生命周期中。
要想確保對(duì)象只定義一次,最好的辦法是把靜態(tài)數(shù)據(jù)成員的定義與其他非內(nèi)聯(lián)函數(shù)的定義放在同一個(gè)文件中。
通常類(lèi)的靜態(tài)成員不應(yīng)該在類(lèi)的內(nèi)部初始化。然而,我們可以為靜態(tài)成員提供?
const
?整數(shù)類(lèi)型的類(lèi)內(nèi)初始值,不過(guò)要求靜態(tài)成員必須是字面值常量類(lèi)型的?constexpr
:

class Account {
private:
? static constexpr int period = 30;
}
// 即使在類(lèi)內(nèi)部被初始化了,通常情況下也應(yīng)該在類(lèi)的外不定義一下該成員。
constexpr int Account::period;

靜態(tài)數(shù)據(jù)成員的類(lèi)型可以就是它所屬的類(lèi)類(lèi)型。而非靜態(tài)數(shù)據(jù)成員則受到限制,只能聲明成它所屬類(lèi)的指針或引用。
可以使用靜態(tài)數(shù)據(jù)成員作為成員函數(shù)的默認(rèn)實(shí)參。非靜態(tài)數(shù)據(jù)成員則不能,因?yàn)榉庆o態(tài)數(shù)據(jù)成員本身屬于對(duì)象的一部分,無(wú)法提供一個(gè)對(duì)象以便從中獲取成員的值,最終將引發(fā)錯(cuò)誤。

