重讀C++Primer學(xué)習(xí)筆記 類篇

類的基本思想是數(shù)據(jù)抽象data abstraction封裝 encapsulation。
數(shù)據(jù)抽象是一種依賴于接口 interface實(shí)現(xiàn) implementation分離的編程和設(shè)計(jì)技術(shù)
封裝實(shí)現(xiàn)了類的接口和實(shí)現(xiàn)的分離

c++ Primer 第五版 第230頁

成員函數(shù)的聲明必須在類的內(nèi)部,他的定義既可以在類的內(nèi)部也可以在外部。作為接口組成部分的非成員函數(shù),定義和聲明都在類的外部

struct Sales_data{
  std::string isbn() const {return bookNo;}
  Sales_data& combine(const Sales_data&);
  double avg_price() const;
  std::string bookNo;
  unsigned units_sold =0;
  double revenue = 0.0;
  
};
Sales_data add(const Sales_data&,, const Sales_data&);
std::ostream &print(std::ostream&,, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);

我們再觀察一次對(duì)isbn成員函數(shù)的調(diào)用
total.isbn()
當(dāng)我們調(diào)用成員函數(shù)時(shí),實(shí)際上是在替某個(gè)對(duì)象調(diào)用它。如果isbn指向Sales_data的成員(例如bookNo),它隱式的指向調(diào)用該函數(shù)對(duì)象成員。在上面的調(diào)用中,isbn隱式的返回total.bookNo
成員函數(shù)通過this的額外隱式參數(shù)來訪問調(diào)用他的對(duì)象,也就是說編譯器負(fù)責(zé)將total的地址傳遞給isbn的隱式形參this

//偽代碼 說明調(diào)用成員函數(shù)的實(shí)際執(zhí)行過程
Sales_data::isbn(&total);

在成員函數(shù)內(nèi)部 我們可以直接調(diào)用該函數(shù)的對(duì)象成員
,而無需通過成員訪問運(yùn)算符做到這一點(diǎn),因?yàn)閠his所指的正是這個(gè)對(duì)象。也就是說isbn使用bookNo時(shí),就像我們書寫了this->bookNo一樣
isbn的另一個(gè)關(guān)鍵之處是緊隨參數(shù)列表后面的const,他的作用是隱式修改this指針的類型
默認(rèn)情況下,this的類型是指向類類型非常量版本的常量指針
在成員函數(shù)中,this的類型是Sales_data * const,隱式參數(shù)仍然要遵循初始化規(guī)則,意味著(在默認(rèn)情況下),我們不能把this綁定在 一個(gè)常量對(duì)象上(因?yàn)榈讓邮且粋€(gè)非const),這種情況使得我們不能在一個(gè)常量對(duì)象上調(diào)用普通的成員函數(shù)
如果isbn是一個(gè)普通函數(shù)且this是一個(gè)普通指針參數(shù)u,那么可以聲明成const Sales_dataconst,但由于this是隱式的,所以如何做出類似的聲明就成了一個(gè)問題了。
C++的做法是允許把const放在成員函數(shù)的參數(shù)列表之后,
緊隨在參數(shù)列表之后的const代表this是一個(gè)指向常量的指針。這樣使用const的成員函數(shù)稱為常量成員函數(shù)*
可以將其想象成

//偽代碼 說明其是如何使用的
//非法的,不能顯示定義this
std::string Sales_data::isbn(const Sales_data * const this)
{return this->isbn;}

常量對(duì)象以及常量對(duì)象的引用或指針執(zhí)泥調(diào)用常量對(duì)象函數(shù)

c++ Primer第五版 第234頁

類的作者常常定義一些輔助函數(shù),雖然操作從概念上屬于類的接口的組成部分,當(dāng)并不將其定義為類的成員函數(shù)
一般來說,如果非成員函數(shù)是類接口的組成部分,這些類的聲明應(yīng)該與類放在一個(gè)頭文件里面

istream &read(istream &is,Sales_data &item)
{
        double price = 0;
        is >> item.bookNo >> item.units_sold >> price;
        item.revenue = price * item.units;
        return is;
}
ostream &print(ostream &os, const Sales_data &item)
{
        os<< item.isbn()....;
        return os;
}

注意

  • 由于IO類不能被拷貝,所以只能通過引用來傳遞他們,
  • print不負(fù)責(zé)換行,盡量減少對(duì)格式的控制,可以確保由用戶代碼來決定是否換行

c++ Primer 第五版 第235頁 構(gòu)造函數(shù)

構(gòu)造函數(shù)的名字和類名相同,和其他函數(shù)不同,構(gòu)造函數(shù)沒有返回類型
構(gòu)造函數(shù)不能被聲明為const的
編譯器創(chuàng)造的構(gòu)造函數(shù)被稱為合成的默認(rèn)構(gòu)造函數(shù)
對(duì)大多數(shù)類來說,它的操作是

  • 存在類內(nèi)初始值的,用于初始化成員
  • 否則,默認(rèn)初始化
    只有類沒有聲明任何構(gòu)造函數(shù)時(shí),編譯器才會(huì)自動(dòng)生成默認(rèn)構(gòu)造函數(shù)
    如果類包含有內(nèi)置類型或者復(fù)合類型的成員,只有這些成員都被賦予了類內(nèi)初始值時(shí),這個(gè)類才適合合成的默認(rèn)構(gòu)造函數(shù)
struct Sales_data{
    Sales_data() = default;
    Sales_data(const std::string &s) : bookNo(s){}
    Sales_data(const std::string &s, unsigned n, double p):
                 bookNo(s),units_sold(n), revenue(n*p){}
     Sales_data(std::istream &);
//其他已有的
    unsigned units_sold = 0;
    double revenue = 0.0;
};

Sales_data() = default;在C++11標(biāo)準(zhǔn)中,如果我們需要默認(rèn)行為,可以用=default來要求編譯器生成構(gòu)造函數(shù)
對(duì)于第二個(gè)和第三個(gè)構(gòu)造函數(shù),在冒號(hào)和花括號(hào)之間新出現(xiàn)的部分,,稱為構(gòu)造函數(shù)初始化列表
當(dāng)某個(gè)成員變量被初始化列表忽略時(shí),他將以與合成默認(rèn)構(gòu)造函數(shù)相同的方式隱式初始化

c++Primer 第五版 第241頁

類可以允許其他類或者函數(shù)訪問它的非公有成員,方法是將其聲明為類的友元(friend)
友元聲明只能出現(xiàn)在類的內(nèi)部,位置不限。不過一般來說,最好在類定義開始或者結(jié)束前的位置集中聲明友元。

c++Primer 第五版 第242頁 封裝的益處

封裝有兩個(gè)重要的優(yōu)點(diǎn):

  • 確保用戶代碼不會(huì)無意間破壞封裝對(duì)象的狀態(tài)
  • 被封裝的類的具體實(shí)現(xiàn)細(xì)節(jié)可以隨時(shí)改變,而無需調(diào)整用戶級(jí)別代碼

C++ Primer第五版 第242頁 友元的聲明

友元的聲明僅僅指定了訪問的權(quán)限,而非一個(gè)通常意義上的函數(shù)聲明,因此在友元聲明之外需要再專門對(duì)函數(shù)進(jìn)行一次聲明
由于我們前面提到友元聲明通常放在類同一個(gè)頭文件中,因此我們的Sales_data頭文件應(yīng)該為友元函數(shù)在類外部提供聲明(除了類內(nèi)部以外)

c++ Primer 第五版 第245頁 類內(nèi)聯(lián)函數(shù)

定義在類內(nèi)部的成員函數(shù)是自動(dòng)inline的
我們可以在類的內(nèi)部將inline作為聲明的一部分顯式支出,也可以在類的外部用inline修飾其定義
雖然我們無需在聲明和定義同時(shí)說明inline,但這樣做是合法的。一般最好在類外部定義的地方說明inline,這使得類更容易理解
當(dāng)然inline成員函數(shù)應(yīng)該與相應(yīng)的類定義在同一個(gè)頭文件中

c++Primer 第五版第245頁 可變數(shù)據(jù)成員

有時(shí)(但并不頻繁)會(huì)有一種情況,我們希望修改類的某個(gè)數(shù)據(jù)成員,即使在const成員函數(shù)中,這時(shí)可以通過mutable關(guān)鍵字來實(shí)現(xiàn)

class Screen{
private:
    mutable size_t access_ctr;
public:
    void some_member() const;
};
void Screen::some_member() const{
    ++access_ctr;
}//const成員函數(shù)仍然可以修改mutablemember

c++Primer 第五版 第247頁 從const成員函數(shù)返回*this

一個(gè)const成員函數(shù)如果以引用的形式返回*this,那他的返回類型將是常量引用
如果display是一個(gè)const成員,返回類型為const Screen&,這樣其返回值無法被修改

Screen myScreen;
myScreen.display(cout).set('*');
//如果display返回常量引用,set將引發(fā)錯(cuò)誤

通過區(qū)分成員函數(shù)是否是const的,我們可以對(duì)其進(jìn)行重載。由于非常量版本的函數(shù)對(duì)常量對(duì)象不可用,所以我們只能在常量對(duì)象上調(diào)用const成員對(duì)象
在下面這個(gè)例子中,定義一個(gè)do_display進(jìn)行實(shí)際工作,

class Screen{
public:
    Screen &display(std::ostream &os)
                {do_display(os);return *this;}
    const Screen &display(std::ostream &os) const
               {do_display(os); return *this;}
private:
     void do_display(std::ostream &os) const{os<<contents;}
};

和我們之前學(xué)的一樣,一個(gè)成員調(diào)用另一個(gè)成員時(shí),this被隱式的傳遞
小建議
為什么要費(fèi)力定義一個(gè)單獨(dú)的do_display函數(shù)呢?

  • 一個(gè)基本的原因是避免多處使用同一代碼
  • 隨著類的規(guī)模發(fā)展,display可能更復(fù)雜,相應(yīng)操作只寫一處作用較明顯
  • 開發(fā)過程中可能給do_display加調(diào)試信息,最終版本被去掉,因此只定義一處更容易
  • 額外的調(diào)用不會(huì)增加任何開銷,因?yàn)樗陬悆?nèi)定義被隱式的聲明為內(nèi)聯(lián)函數(shù)
    實(shí)踐中,設(shè)計(jì)良好的c++代碼常常包含大量類似于do_display的小函數(shù),完成一組其他函數(shù)的“實(shí)際”工作。

c++Primer第五版第250頁 類的聲明

類似于函數(shù),類也可以先聲明,而暫時(shí)不定義

class Screen;//Screen類的聲明

對(duì)于一個(gè)類來說,我們創(chuàng)建他的對(duì)象之前該類必須被定義,而不是僅僅被聲明,但也有一種例外情況,當(dāng)一個(gè)類的名字出現(xiàn)后,他就被認(rèn)為被聲明過(不是定義),因此類允許包含指向他自身類型的引用或指針:

class Link_screen{
    Screen window;
    Link_screen *next;
    Link_screen *prev;
};

c++Primer 第五版 第250頁 友元

類可以把其他類定義為友元,也可以把其他類(之前定義過的)的成員函數(shù)定義成友元
如果一個(gè)類指定為友元類,那友元類的成員函數(shù)可以訪問此類包括非公有成員在內(nèi)的所有成員

class Screen{friend class Window_mgr;};//友元類
class Screen{friend void Window_mgr::clear(ScreenIndex);};//友元 類成員函數(shù)

注意友元關(guān)系不存在傳遞性
盡管重載函數(shù)名字相同,但仍然是不同的函數(shù),因此一組重載函數(shù)的友元聲明必須為每一個(gè)分別聲明

c++ Primer 第五版 第253頁 類的作用域

void Window_mgr::clear(ScreenIndex i){}

由于編譯器在處理參數(shù)列表之前已明確了我們處于Window_mgr作用域中,所以不必專門說明ScreenIndex是Window_mgr類定義的
另一方面,函數(shù)返回類型通常在函數(shù)名之前。因此當(dāng)類成員函數(shù)定義在類外部時(shí),返回類型中使用的名字都位于類的作用域之外,這時(shí)需要指定他屬于哪個(gè)類

class Window_mgr{
public:
    ScreenIndex addScreen(const Screen&);};
//首先處理返回類型
Window_mgr::ScreenIndex
Window_mgr::addScreen(const Screen &s){}

c++Primer 第五版 第254頁 名字查找和類的作用域

一般來說內(nèi)層作用域可以重新定義外層作用域中的名字,即使該名字在內(nèi)層作用域中使用過
然而,在類中,如果成員使用了外層作用域中的某個(gè)名字,而這個(gè)名字代表一種類型,則類不能在之后重新定義該名字。

c++Primer 第五版第259頁 構(gòu)造函數(shù)初始化

構(gòu)造函數(shù)初始化列表只說明用于初始化成員的值,但并不規(guī)定他們具體執(zhí)行順序
成員的初始化順序和他們在類定義中出現(xiàn)的順序一致
最好令構(gòu)造函數(shù)初始值的順序與成員聲明的順序一致,如果可能的話,盡量避免使用某些成員初始化其他成員

c++Primer第五版 第261頁委托構(gòu)造函數(shù)

c++11允許委托構(gòu)造函數(shù)使用所屬類的其他構(gòu)造函數(shù)協(xié)助其進(jìn)行構(gòu)造

class Sales_data{
public:
    Sales_data(std::string s, unsigned cnt):bookNo(s),units_sold(cnt){}
    Sales_data():Sales_data("",0){}
    Sales_data(std::istream &is): Sales_data()
          {read(is,*this);}
};

c++ Primer 第五版第263頁 隱式類類型轉(zhuǎn)換

如果類的構(gòu)造函數(shù)只接受一個(gè)實(shí)參,實(shí)際上定義了轉(zhuǎn)換為該類型的隱式轉(zhuǎn)換機(jī)制,有時(shí)我們稱之為轉(zhuǎn)換構(gòu)造函數(shù)
而由于編譯器只能自動(dòng)地執(zhí)行一步類型轉(zhuǎn)換,因此

string null_book  = "9999";
item.combine(null_book);//會(huì)臨時(shí)構(gòu)造Sales_data轉(zhuǎn)換null_book
//下面的代碼隱式使用了兩種轉(zhuǎn)換,因此報(bào)錯(cuò)
item.combine("9999");
//把"9999"轉(zhuǎn)換為string,再將string轉(zhuǎn)為Sales_data
//如果想完成上述代碼 可以
item.combine(string("9999"));//正確 顯式string,隱式Salesdata
item.combine(Sales_data("9999"));//正確 隱式string,顯式Salesdata

而在要求隱式轉(zhuǎn)換的上下文中,我們可以通過explicit關(guān)鍵字進(jìn)行阻止

class Sales_data{
    explicit Sales_dadta(const std::string &s):bookNo(s){}
    explicit Sales_data(std::istream&);
};
item.combine(null_book);//報(bào)錯(cuò)
item.combine(cin);//報(bào)錯(cuò)

關(guān)鍵字explicit支隊(duì)一個(gè)實(shí)參的構(gòu)造函數(shù)有效
盡管編譯器不會(huì)將explicit構(gòu)造函數(shù)用于隱式轉(zhuǎn)換,但我們可以用于顯式強(qiáng)制轉(zhuǎn)換

item.combine(Sales_data(null_book));//正確
item.combine(static_cast<Sales_data>(cin));//正確

C++ Primer 第五版 第267頁 字面值常量類

某些類可以是字面值常量類,它可能含有constexpr函數(shù)成員,這樣的成員必須符合constexpr函數(shù)的所有要求,它們都是隱式const的
數(shù)據(jù)成員都是字面值類型的聚合類是一個(gè)字面值常量類
符合下列要求的費(fèi)聚合類也是字面值常量類:

  • 數(shù)據(jù)成員都是字面值
  • 至少含有一個(gè)constexpr構(gòu)造函數(shù)
  • 如果一個(gè)數(shù)據(jù)成員有類內(nèi)初始值,則內(nèi)置類型成員的初始值必須是常量表達(dá)式;或者成員屬于某種類類型,初始值必須使用成員自己的constexpr構(gòu)造函數(shù)
  • 類必須使用析構(gòu)函數(shù)的默認(rèn)定義,該成員負(fù)責(zé)銷毀類的對(duì)象
    盡管構(gòu)造函數(shù)不能是const的,但字面值常量類的構(gòu)造函數(shù)可以是constexpr的
    constexpr構(gòu)造函數(shù)可以聲明成=default,或者刪除函數(shù)的形式。構(gòu)造函數(shù)必須既符合構(gòu)造函數(shù)的要求(不能包含返回語句),又必須符合constexpr的要求(它能擁有的唯一可執(zhí)行語句就是返回語句),這兩點(diǎn)可知其函數(shù)體一般來說是空的
class Debug{
public:
    constexpr Debug(bool b=true):hw(b),io(b),other(b){}
/**/
private:
    bool hw,io,other;
};

constexpr構(gòu)造函數(shù)補(bǔ)習(xí)初始化所有數(shù)據(jù)成員,初始值或使用constexpr構(gòu)造函數(shù)或者一挑常量表達(dá)式

c++Primer 第五版 第269頁 類的靜態(tài)成員

static關(guān)鍵字使其成為類的靜態(tài)成員,靜態(tài)成員可以是public或者private的
類似的靜態(tài)成員函數(shù)不與任何對(duì)象綁定在一起,不包含this指針,不能聲明為const,且函數(shù)體內(nèi)不能使用this
和類的所有成員一樣,當(dāng)我們只想類外部的靜態(tài)成員時(shí),必須指明所屬的類。static關(guān)鍵字只出現(xiàn)在類內(nèi)部的聲明語句中。
靜態(tài)數(shù)據(jù)成員不由類的構(gòu)造函數(shù)初始化。而且一般來說,我們不在類內(nèi)部初始化靜態(tài)成員。相反的,必須在類的外部定義和初始化每個(gè)靜態(tài)成員。和其他對(duì)象一樣,一個(gè)靜態(tài)數(shù)據(jù)成員只能定義一次
類似于全局變量,一單定義,靜態(tài)數(shù)據(jù)成員就一直存在于程序的整個(gè)聲明周期中。

double Account::interestRate = initRate();
//定義并初始化一個(gè)靜態(tài)成員

通常情況下類的靜態(tài)成員不應(yīng)該在類內(nèi)初始化,但是我們可以為靜態(tài)成員提供const整數(shù)類型的類內(nèi)初始值,不過要求靜態(tài)成員必須是字面值常量類型的constexpr

class Account{
public:
    static double rate(){return interestRate;}
    static void rate(double);
private:
    static constexpr int period  = 30;//
};

c++Primer 第五版 第271頁

靜態(tài)成員獨(dú)立于任何對(duì)象。因此在某些非靜態(tài)成員非法的場合,靜態(tài)成員卻可以正常使用。例如,靜態(tài)數(shù)據(jù)成員可以是不完全類型(249頁),特別地,靜態(tài)數(shù)據(jù)成員的可以就是他所屬的類類型。而非靜態(tài)成員受到限制,只能聲明它所屬的類的指針或引用:

class Bar{
 public:
//
private:
    static Bar mem1;//正確,靜態(tài)成員可以  是不完全類型
    Bar *mem2;//正確 指針成員可以是不完全類型
    Bar mem3;// 錯(cuò)誤,數(shù)據(jù)成員必須是完全類型
};
//另外一個(gè)區(qū)別是我們可以使用靜態(tài)成員作為**默認(rèn)實(shí)參**
class Screen{
    public:
        Screen& clear(char=bkground);
private:
        static const char  bkground;
};

非靜態(tài)數(shù)據(jù)成員不能作為默認(rèn)實(shí)參,因?yàn)樗闹当旧韺儆趯?duì)象的一部分,這樣做無法真正提供一個(gè)對(duì)象以便從中獲取成員的值,從而引發(fā)錯(cuò)誤

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容