天天看點

C++ Primer 5th學習筆記6 類

  類的基本思想是資料抽象和封裝,類的接口包括使用者所能執行的操作;類的實作包括類的資料成員、負責接口實作的函數體以及定義類所需的各種私有函數。

1 定義抽象資料類型

 從改進的Sales_data開始,其結構如下圖所示:

struct Sales_data
{
    std::string  isbn() const {return bookNo; }
    Sales_data& conbine(const Sales_data&);
    double avg_price() const;
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
//Sales_data的非成員接口函數
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
           

1.1 定義成員函數

  成員函數可以通過一個名為this的額外隐式參數來通路調用它的那個對象。如下面的isbn函數,可以改為另外一種方式:

//原函數
std::string  isbn() const {return bookNo; }
           
//采用this定義
std::string  isbn() const {return this->bookNo; }
           

isbn函數中參數清單之後的const關鍵字使用是:修改隐式this指針的類型

類的作用域和成員函數

  編譯器在處理類時,有兩步:首先編譯成員的聲明,若有成員函數體的話,才去編譯成員函數體,是以函數體可以随意使用類中的其他成員而無須在意這些成員出現的次序

在類的外部定義成員函數

  當在類的外部定義成員函數時,成員函數的定義必須與它的聲明比對。如果成員函數被聲明成常量成員函數,則其定義也必須在參數清單後明确指定const屬性,同時,類外部定義的成員的名字必須包含它所屬的類名:

double Sales_data::avg_price() const
{
    if (units_sold)
        return revenue/units_sold;
    else
        return 0;
    
}
           

定義一個傳回this對象的函數

  函數combine的設計類似于複合指派運算符±,調用該函數的對象代表的是指派運算符左側的運算對象,右側運算對象則通過顯式的實參被傳入函數:

Sales_data& Sales_data:;combine(const Sales_data &rhs)
{
    units_sold += rhs.units_sold;       //把rhs的成員加到this對象的成員上
    revenue += rhs.revenus;
    return *this;       //傳回調用該函數的對象
}
           

return語句解引用this指針以獲得執行該函數的對象,即函數調用傳回tatal的引用

1.2 定義類相關的非成員函數

定義read和print函數

  其定義程式如下:

//輸入的交易資訊包括ISBN、售出總數和售出價格
istream &read(istream &is, Sales_data &item)
{
    double price = 0;
    is >> item.bookNo >> item.units_sold >> price;
    item.revenue = price * item.units_sold;
    return is;
}
ostream &print(ostream &os, const Sales_data &item)
{
    os << item.isbn() << " " << item.units_sold << " "
       << item.revenue << " " << item.avg_price();
    return os;
}
           

read函數從給定流中将資料讀到給定的對象,print函數則負責将給定内容的對象列印到給定的流中。

Tip:read和print分别手一個各自IO類型的引用作為其參數,這是因為IO類屬于不能被拷貝的類型,是以我們隻能通過引用來傳遞;由于讀取和寫入的操作會改變流的内容,是以兩個函數接受的都是普通引用。

1.3 構造函數

  構造函數:控制類的對象初始化的函數,作用是初始化類對象的資料成員。構造函數的名字和類名相同;構造函數不能被聲明成const,構造函數在const對象的構造過程中可以向其寫值。

定義Sales_data的構造函數,此時最初的類的程式變為:

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(p *  n){}
              
    Sales_data(std::istream &);
    //之前已有的其他成員
    std::string  isbn() const {return bookNo; }
    Sales_data& conbine(const Sales_data&);
    double avg_price() const;
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
    
}
           

在類的外部定義構造函數

  在類的外部定義構造函數時,必須指明該構造函數是哪個類的成員,示例如下

Sales_data::Sales_data(std::istream &is)
{
     read(is, *this);
}
           

預設構造函數的作用

 預設初始化在以下情況下發生:

  • 當我們在塊作用域内不使用任何初始值定義一個非靜态變量或數組時;
  • 當一個類本身含有類類型的成員且使用合成的預設構造函數是;
  • 當類類型的成員沒有在構造函數初始值清單中顯示地初始化時;

 值初始化在以下情況下發生:

  • 在數組初始化的過程中若提供的初始值數量少于數組的大小時
  • 當不使用初始值定義一個局部靜态變量時
  • 當通過書寫形如

    T()

    的表達式顯式地請求值初始化時,其中T是類型名

2 通路控制與封裝

  使用通路說明符加強類的封裝性:

  • 定義在public說明符之後的成員在整個程式内可被通路,public成員定義類的接口
  • 定義在private說明符之後後的成員可以被類的成員函數通路,但是不能被使用該類的代碼通路,private封裝了類的實作細節

    再次定義Sales_data類,其程式如下:

class Sales_data
{
    public:        //添加通路說明符
        Sales_data() = default;        //聲明預設構造函數
        //構造函數初始值清單
        Sales_data(const std::string &s, unsigned n, double p):
              bookNo(s), units_sold(n), revenue(p *  n){}
        Sales_data(const std::string &s): bookNo(s) {}
        Sales_data(std::istream &);
        std::string  isbn() const { return bookNo; }
        Sales_data& conbine(const Sales_data&);
    private:        //添加通路說明符
        double avg_price() const
            { return units_sold ? revenue/units_sold : 0;}
        std::string bookNo;
        unsigned units_sold = 0;
        double revenue = 0.0;
};
           

使用

class

struct

關鍵字

  

struct

與class的差別在于預設的通路權限不同。如果使用的是

struct

關鍵字,則定義在第一個通路說明符之間的成員是

pubilc

的,相反,如果我們使用

class

關鍵字,則這些成員是

private

的。

2.1 友元

  類可以允許其他類或者函數通路它的非公有成員,方法是令其類或函數成為它的友元,在函數聲明語句的開始之前加上friend關鍵字即可。示例如下:

class Sales_data
{
    //為Sales_data的非成員函數所做的友元聲明
    friend Sales_data add(const Sales_data&, const Sales_data&);
    friend std::ostream &print(std::ostream&, const Sales_data&);
    friend std::istream &read(std::istream&, Sales_data&);
    public:        //添加通路說明符
        Sales_data() = default;        //聲明預設構造函數
        //構造函數初始值清單
        Sales_data(const std::string &s, unsigned n, double p):
              bookNo(s), units_sold(n), revenue(p *  n){}
        Sales_data(const std::string &s): bookNo(s) {}
        Sales_data(std::istream &);
        std::string  isbn() const { return bookNo; }
        Sales_data& conbine(const Sales_data&);
    private:        //添加通路說明符
        double avg_price() const
            { return units_sold ? revenue/units_sold : 0;}
        std::string bookNo;
        unsigned units_sold = 0;
        double revenue = 0.0;
};
//為Sales_data的非成員函數所做的友元聲明
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
           

友元聲明隻能出現在類定義的内部

封裝的益處

  封裝有兩個重要的特點:

  • 確定使用者代碼不會無意間破壞封裝對象的狀态
  • 被封裝的類的具體實作細節可以随時改變,而無須調整使用者級别的代碼。

繼續閱讀