C++模組2:物件導向程式設計

噠宰的自我修養發表於2020-11-19

C++模組2:物件導向程式設計

1 .String類的實現:

class MyString  
{  
public:
    MyString();
    MyString(const MyString &);
    MyString(const char *);  
    MyString(const size_t,const char);  
    ~MyString();
  
    size_t length();// 字串長度  
    bool isEmpty();// 返回字串是否為空  
    const char* c_str();// 返回c風格的trr的指標 
    friend ostream& operator<< (ostream&, const MyString&);  
    friend istream& operator>> (istream&, MyString&);  
    //add operation  
    friend MyString operator+(const MyString&,const MyString&);  
    // compare operations  
    friend bool operator==(const MyString&,const MyString&);  
    friend bool operator!=(const MyString&,const MyString&); 
    friend bool operator<=(const MyString&,const MyString&); 
    friend bool operator>=(const MyString&,const MyString&);  
    // 成員函式實現運算子過載,其實一般需要返回自身物件的,成員函式運算子過載會好一些
    char& operator[](const size_t);  
    const char& operator[](const size_t)const; 
    MyString& operator=(const MyString&); 
    MyString& operator+=(const MyString&); 
    // 成員操作函式  
    MyString substr(size_t pos,const size_t n);  
    MyString& append(const MyString&);  
    MyString& insert(size_t,const MyString&);  
    MyString& erase(size_t,size_t);  
    int find(const char* str,size_t index=0);  

private:  
    char *p_str;  
    size_t strLength;  
};

2 .派生類中建構函式與解構函式,呼叫順序:

建構函式的呼叫順序總是如下:

A:基類建構函式。如果有多個基類,則建構函式的呼叫順序是某類在類派生表中出現的順序,而不是它們在成員初始化表中的順序。
B:成員類物件建構函式。如果有多個成員類物件則建構函式的呼叫順序是物件在類中被宣告的順序,而不是它們出現在成員初始化表中的順序。如果有的成員不是類物件,而是基本型別,則初始化順序按照宣告的順序來確定,而不是在初始化列表中的順序。
C:派生類建構函式。

解構函式正好和建構函式相反。

3. 虛擬函式的實現原理:

虛擬函式表:
編譯器會為每個有虛擬函式的類建立一個虛擬函式表,該虛擬函式表將被該類的所有物件共享。類的虛擬函式表是一塊連續的記憶體,每個記憶體單元中記錄一個JMP指令的地址。類的每個虛擬函式佔據虛擬函式表中的一塊,如果類中有N個虛擬函式,那麼其虛擬函式表將有4N位元組的大小。
編譯器在有虛擬函式的類的例項中建立了一個指向這個表的指標,該指標通常存在於物件例項中最前面的位置(這是為了保證取到虛擬函式表的有最高的效能)。這意味著可以通過物件例項的地址得到這張虛擬函式表,然後就可以遍歷其中函式指標,並呼叫相應的函式。
有虛擬函式或虛繼承的類例項化後的物件大小至少為4位元組(確切的說是一個指標的位元組數;說至少是因為還要加上其他非靜態資料成員,還要考慮對齊問題);沒有虛擬函式和虛繼承的類例項化後的物件大小至少為1位元組(沒有非靜態資料成員的情況下也要有1個位元組來記錄它的地址)。

哪些函式適合宣告為虛擬函式,哪些不能?

當存在類繼承並且解構函式中有必須要進行的操作時(如需要釋放某些資源,或執行特定的函式)解構函式需要是虛擬函式,否則若使用父類指標指向子類物件,在delete時只會呼叫父類的解構函式,而不能呼叫子類的解構函式,從而造成記憶體洩露或達不到預期結果;

行內函數不能為虛擬函式:行內函數需要在編譯階段展開,而虛擬函式是執行時動態繫結的,編譯時無法展開;

建構函式不能為虛擬函式:建構函式在進行呼叫時還不存在父類和子類的概念,父類只會呼叫父類的建構函式,子類呼叫子類的,因此不存在動態繫結的概念;但是建構函式中可以呼叫虛擬函式,不過並沒有動態效果,只會呼叫本類中的對應函式;

靜態成員函式不能為虛擬函式:靜態成員函式是以類為單位的函式,與具體物件無關,虛擬函式是與物件動態繫結的。

4. 虛繼承的實現原理:

為了解決從不同途徑繼承來的同名的資料成員在記憶體中有不同的拷貝造成資料不一致問題,將共同基類設定為虛基類。這時從不同的路徑繼承過來的同名資料成員在記憶體中就只有一個拷貝,同一個函式名也只有一個對映。這樣不僅就解決了二義性問題,也節省了記憶體,避免了資料不一致的問題。

建構函式和解構函式的順序:虛基類總是先於非虛基類構造,與它們在整合體系中的次序和位置無關。如果有多個虛基類,則按它們在派生列表中出現的順序從左到右依次構造。

#include <iostream>
using namespace std;

class zooAnimal
{
public: zooAnimal(){cout<<"zooAnimal construct"<<endl;}
};
class bear : virtual public zooAnimal
{
public: bear(){cout<<"bear construct"<<endl;}
};
class toyAnimal
{
public: toyAnimal(){cout<<"toyAnimal construct"<<endl;}
};
class character
{
public: character(){cout<<"character construct"<<endl;}
};
class bookCharacter : public character
{
public: bookCharacter(){cout<<"bookCharacter construct"<<endl;}
};
class teddyBear : public bookCharacter, public bear, virtual public toyAnimal
{
public: teddyBear(){cout<<"teddyBear construct"<<endl;}
};

int main()
{
    teddyBear();
}

編譯器按照直接基類的宣告順序依次檢查,以確定其中是否含有虛基類。如果有,則先構造虛基類,然後按照宣告順序依次構造其他非虛基類。建構函式的順序是:zooAnimal, toyAnimal, character, bookCharacter, bear, teddyBear。析構過程與構造過程正好相反。

相關文章