首頁 > 軟體

C++ primer類的基礎精講

2022-07-01 14:04:03

定義抽象資料型別

初探this和

struct Sales_data
{
    string isbn(){return bookNo;}
    Sales_data & combine(const Sales_data&);
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0;
};
Sales_data total;

引入this

對於isbn成員函數的呼叫: total.isbn();

當我們呼叫成員函數時,實則上是在替某個物件呼叫它。在上面的呼叫中,當isbn返回bookNo時,實際上隱式地返回total.bookNo.

成員函數通過一個名為this的額外隱式引數來存取呼叫它的那個物件。當我們呼叫一個成員函數時,用請求該函數的物件地址初始化this。例如,如果呼叫total.isbn(),編譯器負責把total的地址傳遞給isbn的隱式形參this,可以等價認為編譯器將該呼叫重寫成Sales_data::isbn(&total),呼叫Sales_data時的isbn成員時傳入了total的地址。

在成員函數內部,我們可以直接使用呼叫該函數的物件的成員,而無須通過成員存取運運算元來做到這一點,因為this所指的正是這個物件。任何對類成員的直接存取都被看做this的隱式呼叫,例如,isbn在使用bookNo時,隱式地使用this指向的成員,就如同this->bookNo一樣。

建構函式

定義:類通過一個或幾個特殊的成員函數來控制其物件的初始化,這些函數叫做建構函式。

無論何時,只要類的物件被建立,就會執行建構函式。

建構函式的名字和類名一樣,建構函式沒有返回型別,一個類可以擁有多個建構函式,但每個建構函式之間必須在引數數量或引數型別上存在不同。且建構函式不能被宣告成const。

當一個類沒有定義任何建構函式時,編譯器會給類自動新增一個預設建構函式,該建構函式無須任何實參對物件進行初始化。

對前面的Sales_data類進行編寫建構函式

struct Sales_data
{
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

(1)=default的含義

如果需要預設建構函式起作用,那麼可以在參數列後面寫上=default來要求編譯器生成預設建構函式。

(2)建構函式初始值列表

Sales_data(const string &s):bookNo(s)()
Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}

上面出現了兩個新的建構函式的寫法,該部分稱為建構函式初始值列表。

負責為新建立的物件的一個或幾個資料成員賦初值。建構函式初始值是成員名字的一個列表,每個名字後面緊跟括號括起來的成員初始值。

當某個資料成員被建構函式初始值列表忽略時,他將以合成預設建構函式相同的方式隱式初始化。所以,第一個建構函式等價於:

Sales_data(const string &s):bookNo(s),units_sold(0),revenue(0)();

存取控制和封裝

存取控制符public和private

定義在public說明符之後的成員在整個程式可被存取。

定義在private說明符之後的成員可以被類的成員函數存取,但是不能被使用該類的程式碼存取。

Sales_data類的新形式

class Sales_data
{
public:
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
private:
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

友元

類允許其他類或者函數存取他的非公有成員,方法是令其他類或者函數成為他的友元。如果一個類想把一個函數作為他的友元,只需要增加一個friend關鍵字開始的函數宣告語句即可

class Sales_data
{
    friend Sales_data add(const Sales_data &,const Sales_data&);
    friend istream &read(istream&,Sales_data&);
    friend ostream &print(ostream&,const Sales_data&)
public:
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
private:
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

友元的宣告只能出現在類定義的內部。友元不是類的成員,也不受他所在區域存取控制級別的約束。

類的其他特性

可變資料成員

在一個const成員函數中,若希望修改類的某個資料成員,可以通過在變數的宣告中加入mutable關鍵字實現

class screen{
public:
        void some_menmber() const;
private:
        mutable size_t access_ctr
};
void screen::some_member() const
{
    ++access_ctr
}

返回*this的成員函數

inline Screen &Screen::set(char c)
{
    contents[cursor]=c;
    return *this;
}
inline Screen &Screen::set(pos r,pos col,char ch)
{
    contents[r*width+col]=ch;
    return *this;
}
inline Screen &Screen::move(pos r,pos c)
{
    pos row=r*width;
    cursor=row+c;
    return *this;
}

move和set一樣,返回的值是對物件的參照。

myScreen.move(4,0).set('#');

等同於

myScreen.move(4.0);

myScreen.set('#');

假如我們定義的返回型別不是參照,則move的返回值將是*this的副本,因此呼叫set只能改變臨時副本,不能改變myScreen的值

友元類

例如,window_mgr類的某些成員需要存取screen類的內部資料,例如window_mgr的clear函數將一個指定的screen類的內容設定為空白。

class Screen{
//window_mgr的成員可以存取Screen類的私有部分
friend class Window_mgr
//Screen類剩餘部分
}
class Window_mgr{
public:
    using ScreenIndex=std::vector<Screen>::size_type
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens{Screen(24,80,' ')}
};
void Window_mgr::clear(ScreenIndex)
{
    Screen &s=screen[i];
    s.contens=string(s.height*swidth,' ')
}

建構函式再探

建構函式初始值列表

(1)建構函式的初始值有時必不可少

如果成員是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),ri(i){};

如果成員是const,參照,或者屬於某種未提供預設建構函式的類型別,我們必須通過建構函式初始值列表為這些成員提供初值。

(2)成員初始化順序

成員初始化的順序與他們在類定義中的出現順序一致。一般來說初始化的順序沒什麼特別要求,不過如果一個成員是用另一個成員來初始化的,那麼著兩個成員的初始化順序就關鍵了。

例如

class X
{
    int i;
    int j;
public:
    X(int val):j(val),i(j){}
};

而編譯器實際上是先初始化i,在初始化j,而初始化i的時候發現j沒有值,所以上述建構函式會發生錯誤。

預設建構函式的作用

當物件被預設初始化或值初始化時自動執行預設建構函式。

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

(1)當我們在塊作用域內不適用任何初始值定義一個非靜態變數或陣列時。

(2)當一個類本身含有類型別的成員且使用合成的預設建構函式時

(3)當類型別的成員沒有在建構函式初始值列表中顯示地初始化時

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

(1)在陣列初始化的過程中如果我們提供的初始值數量少於陣列的大小時

(2)當我們不適用初始值定義一個區域性靜態變數

使用預設建構函式

下面的obj的宣告可以正常通過

Sales_data obj();//正確,定義了一個函數而非物件
if(obj.isbn()==primer_5th_ed.isbn())//錯誤:obj是一個函數

但當我們試圖使用obj時,編譯器將報錯,提示我們不能使對函數使用成員存取運運算元。因為obj的實際含義是一個不接受任何引數的函數並且其返回值是Sales_data型別的物件。

如果想定義一個使用預設建構函式進行初始化的物件,正確方法是去掉物件名之後的空括號對。

Sales_data obj;

聚合類

滿足一下條件的類是聚合類:

(1)所有成員都是public的

(2)沒有定義任何建構函式

(3)沒有類內初始值

(4)沒有基礎類別,也沒有虛擬函式

例:

struct Data
{
    int val;
    string s;
};

類的靜態成員

class Account
{
public:
    void calculate(){amount+=amount*interestRate;}
    static double rate(){return interestRate;}
    static void rate(double);
private:
    string owner;
    double amount;
    static double interestRate;
    static double initRate();
}

類的靜態成員存在於任何物件之外,物件中不包含任何與靜態資料成員有關的資料,因此,每個Account物件將包含兩個資料成員:owner和amout。只存在一個interestRate物件而且他被所有account物件共用。

靜態成員函數也不與任何物件繫結在一個,他們不包含this指標。作為結果,靜態成員函數不能宣告成const的,而且我們也不能在static函數體內使用this指標。

到此這篇關於C++ primer類的基礎精講的文章就介紹到這了,更多相關C++類別內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com