💢歡迎來到張胤塵的技術站
💥技術如江河,匯聚眾志成。代碼似星辰,照亮行征程。開源精神長,傳承永不忘。攜手共前行,未來更輝煌💥
文章目錄
- C/C++ | 每日一練 (2)
- 題目
- 參考答案
- 封裝
- 繼承
- 多態
- 虛函數
- 底層實現
- 單繼承
- 多繼承
- 注意事項
C/C++ | 每日一練 (2)
題目
簡述 c++
面向對象的三大特性。
參考答案
面向對象三大特性:封裝、繼承、多態。
封裝
封裝指的是將對象的行為和屬性結合成為一個類,并隱藏對象的內部實現細節,僅通過對象的接口(即公開的方法)與外界交互。
- 隱藏內部實現細節:保護對象的內部狀態,防止外部直接訪問或修改對象的私有成員。
- 提供統一的接口:通過公開的方法(如構造函數、成員函數等)對外提供服務,使對象的使用更加安全和方便。
例如:
#include <iostream>class Person
{
private:// 私有成員,外部無法直接訪問std::string name;int age;public:// 提供接口訪問私有成員void setName(const std::string &newName){name = newName;}void setAge(int newAge){age = newAge;}void display() const{std::cout << name << " is " << age << " years old." << std::endl;}
};
繼承
繼承是指一個類(派生類或子類)可以繼承另一個類(基類或父類)的屬性和方法。子類可以擴展或修改父類的功能,而無需重新編寫相同的代碼,支持代碼重用和擴展。繼承可以是單繼承或多繼承(在 c++
中支持)
繼承的主要作用是:
- 代碼復用:減少重復代碼,提高開發效率。
- 拓展功能:派生類可以在繼承基類的基礎上,添加新的功能或修改現有功能。
#include <iostream>class Animal
{
public:void eat(){std::cout << "animal eat" << std::endl;}
};class Dog : public Animal
{
public:void bark(){std::cout << "dog eat" << std::endl;}
};
多態
多態是指相同的接口在不同的類實例上具有不同的表現形式。多態分為:
-
編譯時多態(函數重載和運算符重載)
-
運行時多態(通過虛函數實現)。
運行時多態是面向對象編程中最重要的多態形式,它通過虛函數和繼承實現。
例如:
#include <iostream>class Shape
{
public:// 定義為虛函數virtual void draw() const{std::cout << "drawing a shape" << std::endl;}
};class Circle : public Shape
{
public:// 重寫基類的虛函數void draw() const override{std::cout << "drawing a circle" << std::endl;}
};class Square : public Shape
{
public:// 重寫基類的虛函數void draw() const override{std::cout << "drawing a square" << std::endl;}
};int main()
{Shape *s1 = new Circle();Shape *s2 = new Square();s1->draw(); // drawing a circles2->draw(); // drawing a squaredelete s1;delete s2;
}
虛函數
在 c++
中,虛函數是實現運行時多態的關鍵機制。它允許派生類重寫繼承自基類的成員函數,從而在運行時根據對象的實際類型調用相應的函數實現。
虛函數是在基類中通過關鍵字 virtual
聲明的成員函數。它的作用是讓派生類可以覆蓋該函數,從而實現多態行為。
#include <iostream>class Base {
public:virtual void display() { std::cout << "Base::display()" << std::endl;}
}
虛函數的主要作用是實現 動態綁定或運行時多態。具體來說:
- 當通過基類指針或引用調用虛函數時,程序會根據對象的實際類型(派生類類型)來調用對應的函數實現。
- 如果沒有虛函數,調用的將是基類的成員函數,而不是派生類的實現,這種行為稱為靜態綁定。
#include <iostream>class Base {
public:virtual void display() {std::cout << "Base::display()" << std::endl;}
};class Derived : public Base {
public:// 重寫基類的虛函數void display() override {std::cout << "Derived::display()" << std::endl;}
};int main() {Base* ptr = new Derived();delete ptr;return 0;
}
在上述代碼中,ptr
是基類指針指向子類對象,由于 display()
是虛函數,程序會調用派生類的 display()
的實現。
底層實現
虛函數的實現依賴于虛表(簡稱 vtable
)和虛表指針(vptr
):
- 每個包含虛函數的類都有一個虛表(
vtable
),虛表中存儲了該類中所有虛函數的地址。 - 每個對象在對象頭中會隱式地包含一個虛表指針(
vptr
),指向其所屬類的虛表。 - 當通過基類指針或引用調用虛函數時,程序會通過
vptr
查找虛表,然后在虛表中根據函數索引找到正確的函數地址。 - 執行函數調用。
單繼承
單繼承的動態多態結構圖如下所示:
多繼承
多繼承是 c++
中的一種繼承方式,它允許一個子類從多個基類繼承屬性和行為。這種繼承方式可以提供更大的靈活性,使得派生類能夠組合多個基類的特性。但是,多繼承也引入了復雜性,尤其是在內存布局、虛函數表、構造和析構順序等方面。多繼承的動態多態結構圖如下所示:
#include <iostream>class Base1
{
public:virtual void display(){std::cout << "Base1::display()" << std::endl;}virtual void show(){std::cout << "Base1::show()" << std::endl;}virtual ~Base1(){std::cout << "Base1::~Base1()" << std::endl;}private:int a;int b;
};class Base2
{
public:virtual void cat(){std::cout << "Base2::cat()" << std::endl;}virtual ~Base2(){std::cout << "Base2::~Base2()" << std::endl;}private:int c;
};class Derived : public Base1, public Base2
{
public:// 重寫基類的虛函數void display() override{std::cout << "Derived::display()" << std::endl;}void cat() override{std::cout << "Derived::cat()" << std::endl;}~Derived() override {std::cout << "Derived::~Derived" << std::endl;}
private:int d;
};int main() {Base1* ptr1 = new Derived();Base2* ptr2 = new Derived();delete ptr1;delete ptr2;return 0;
}
注意事項
- 虛函數必須是成員函數:全局函數或靜態成員函數不能聲明為虛函數。
- 派生類的覆蓋函數必須與基類的虛函數具有相同的簽名(函數名、參數類型和數量)。如果派生類的函數與基類虛函數簽名不一致(函數名相同,參數類型和數量不相同),則不會覆蓋而是隱藏。
- 純虛函數:在基類中,可以將虛函數聲明為純虛函數,即在聲明時賦值為
= 0
。包含純虛函數的類稱為抽象類,不能實例化對象。
class AbstractClass {
public:virtual void func() = 0; // 純虛函數
};
- 析構函數的虛化:如果基類有虛函數,通常需要將析構函數聲明為虛函數,以確保通過基類指針刪除派生類對象時,能夠正確調用派生類的析構函數。
class Base {
public:virtual ~Base() { cout << "Base destructor" << endl; }
};class Derived : public Base {
public:~Derived() { cout << "Derived destructor" << endl; }
};
- 虛函數的實現依賴于虛表和虛表指針,因此會帶來一定的性能開銷;每個對象需要存儲一個虛表指針(通常為 4 字節或 8 字節)。
- 動態多態在調用虛函數時需要通過虛表查找函數地址,這比直接調用非虛函數稍慢。但是這種開銷通常是可以接受的,特別是在需要多態的場景中。
🌺🌺🌺撒花!
如果本文對你有幫助,就點關注或者留個👍
如果您有任何技術問題或者需要更多其他的內容,請隨時向我提問。