C/C++ 學習筆記:類相關

構造和析構次數

A* p =new A[5];//  構造五次

delete[ ] p;//  析構五次

delete p;// 若不使用[ ],則析構一次。


假設 A 是一個類,像這樣 A a(); 並不是表示調用構造函數創建對象,只是定義了一個函數而已。


絕不重新定義繼承而來的缺省參數


若父類沒有無參的構造函數,子類需要在自己的構造函數中顯式調用父類的構造函數。


構造函數中可以調用虛函數

class Base {
public: 
    Base() { Function();}
    virtual void Function() { cout << "Base::Function" << endl;}
}; 
class A :public Base {
public: 
    A() { Function();} 
    virtual void Function() { cout << "A::Function" << endl;}
};

另外:這樣定義一個 A 的對象, A a;  會輸出

Base::Function

A::Function

 而不是

A::Function

A::Function

要知道在 Base 的構造函數中調用 Fuction 的時候,調用的是 Base 的 Function。因爲此時 A 還沒開始構造。



靜態成員變量可被該類的所有方法訪問。


子類型必須是子類繼承了父類的所有可繼承特性,也即公有繼承,才能說是子類型,否則就只是單純的子類



重載、重寫、隱藏
1. 重載:是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。(它們的地址在編譯期就綁定了(早綁定),因此,重載和多態無關!
2. 重寫(覆蓋):是指子類重新定義父類虛函數的方法。其函數名、參數列表、返回值類型,所有都必須同基類中被重寫的函數一致,只有函數體不同(花括號內)。(和多態真正相關
3. 隱藏:隱藏是指派生類的函數屏蔽了與其同名的基類函數。注意只要同名函數,不管參數列表是否相同,基類函數都會被隱藏。(和多態無關
重載和重寫的區別

1). 範圍區別:重寫和被重寫的函數在不同的類中,重載和被重載的函數在同一類中。

2). 參數區別:重寫與被重寫的函數參數列表、返回類型一定相同;重載和被重載的函數參數列表一定不同,不關心函數返回類型。

class A {
    int function(int i);
    int function(int i) const; // 第一個函數的重載
    int function(const int i);// 編譯錯誤,這裏不是重載第一個函數
};

分析

首先 int function(inti)const; 這個函數的形參表和第一個函數是不一樣的,因爲在類中隱含this 形參的存在這裏加上 const 修飾的是 this 指針,則 this 指針的類型就變爲指向const 對象的指針。因此,使用指針傳參時,指向const  對象的指針和指向非const  對象的指針做形參的函數是不同的。

然後 int function(constint i); 這個函數無法重載第一個函數,因爲這個函數和第一個函數對於重載來說是等價的。對於非引用或指針傳參,形參是否const  是等價的,但對於引用或者指針形參來說,有無const  是不同的。

3).virtual的區別:重寫的基類必須要有virtual 修飾,重載函數和被重載函數可以被virtual 修飾,也可沒有。

隱藏和重寫,重載的區別

1). 與重載範圍不同:隱藏函數和被隱藏函數在不同類中。

2). 參數的區別:隱藏函數和被隱藏函數參數列表可以相同,也可以不同,但函數名一定同;當參數不同時,無論基類中的函數是否被virtual 修飾,基類函數都是被隱藏,而不是被重寫。

總的來說:隱藏和重寫,要在不同的類中,如果不滿足重寫,那就是隱藏

 
友元函數最後面不能加 const ,在函數後面加 const  只適用於成員函數
 
虛函數
虛函數的底層是通過虛函數表實現的。
虛函數必須是其所在類的成員函數,而不能是友元函數,也不能是靜態成員函數(static

1. 直接繼承

1)每一個具有虛函數的類都有 1 個虛函數表 VTABLE(如果子類有自己的虛函數,那麼子類的虛表裏面存放繼承來的虛函數地址和自己虛函數的地址),裏面按在類中聲明的虛函數的順序存放着虛函數的地址,這個虛函數表 VTABLE 是這個類的所有對象所共有的;

2)在每個具有虛函數的類的對象裏面都有一個 VPTR 虛函數指針,這個指針指向它自己類的 VTABLE  的首地址,每個類的對象都有這麼一種指針。

2. 虛繼承

對於虛繼承,若派生類有自己的虛函數,則它本身需要有一個虛指針,指向自己的虛表(虛繼承的時候,子類自己的虛函數單獨有個虛表,然後各個父類的的虛函數分別對應一個虛表)。另外,派生類虛繼承父類時,首先要通過加入一個虛指針來指向父類,因此有可能會有兩個虛指針。

類對象的大小 = 各非靜態數據成員(包括父類的非靜態數據成員)的總和+ vfptr指針(多繼承下可能不止一個)+ vbptr指針(多繼承下可能不止一個)+ 編譯器額外增加的字節(對齊)。


C++開發的時候,用來做基類的類的析構函數一般都是虛函數。可是,爲什麼要這樣做呢?
虛析構函數的作用:當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數會被調用。

 

多態需要通過父類的指針或者引用來實現

如果父類裏的虛函數是 private 的,子類依然可以實現多態。雖然父類這個虛函數是 private,但,父類指針或引用可以通過自己的public 方法調用這個私有的虛函數。當然,子類重寫的這個虛函數並不是繼承來自這個父類的,但是可以實現多態。

 
多繼承問題

class A {
public:
    int x;
    A(int X) : x(X) {}
    virtual void print() {
        cout << "A" <<endl;
    }
};
class B {
public:
    int x;
    B(int X) : x(X) {}
    virtual void print() {
        cout << "B" <<endl;
    }
};
class C : public A, public B {
public:
    int x;
    C() : A(1), B(2), x(3) {}
};
 
int main() {
    C c;
    cout << c.A::x << endl;// 輸出 1
    cout << c.B::x << endl;// 輸出 2
    cout << c.x <<endl;// 輸出 3
 
    c.print();// 這裏會編譯錯誤,歧義。因爲繼承了 A 和 B 的 print,無法確定調用哪個。此時,若在 C 中定義了一個 print,就可以這樣使用
 
    // 雖然 C 從 A 和 B 繼承了 print 函數,但是由於 A 和 B 中都有 print,所以 C 的對象訪問的時候要加上 A 或者 B 的域名
    c.A::print();// 輸出 A
    c.B::print();// 輸出 B
    A* pA = &c;
    B* pB = &c;
    pA->print();// 輸出 A
    pB->print();// 輸出 B
}


在重載 “+” 運算符中參數要加const,否則,連續相加會編譯出錯。同理 “- * / ” 也是。
class A {
    int val;
public:
    A(int v):val(v) {}
    friend const A operator+(const A& L, const A& R) {
        A temp(L.val + R.val);
        return temp;
    }
};
A a(1), b(2), c(3);
A d = a+b+c;
因爲 operator+(返回值類型是A,所以 a b 求值後返回的是一個臨時變量,接着這個臨時變量再與 c 相加調用operator+(),臨時變量不能修改,所以需要在形參裏用const 引用的形式捕獲。
返回值最好加上 const,否則,(b)= c這樣變態的代碼也不會編譯出錯。同理 “- * / ” 也是。

不能被重載的操作符有

.”   “::”   “?:”   “->”   “sizeof”   “#”(預處理符號)(小竅門:帶點的操作符肯定不可以重載)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章