C++--繼承

文章目錄

  • 繼承
    • 1. 繼承的概念及定義
      • 1.1 繼承的概念
      • 1.2 繼承的定義
        • 1.2.1 定義格式
        • 1.2.2 繼承方式和訪問限定符
        • 1.2.3 繼承基類成員訪問方式的變化
          • 1.2.3.1 基類成員訪問方式的變化規則
          • 1.2.3.2 默認繼承方式
      • 1.3 繼承類模版
    • 2. 基類和派生類的轉化
    • 3. 繼承中的作用域
      • 3.1 隱藏
      • 3.2 經典面試題
    • 4. 派生的默認成員函數
      • 4.1 普通類的默認成員函數
      • 4.2 派生類的默認成員函數
        • 4.2.1 派生類的構造函數
        • 4.2.2 派生類的拷貝構造函數
        • 4.2.3 派生類的賦值重載函數
        • 4.2.4 派生類的析構函數
        • 4.2.5 派生類的4個成員函數的總結
      • 4.3 面試題:實現一個不能被繼承的類
    • 5. 繼承和友元
    • 6. 繼承和靜態成員
    • 7. 多繼承和菱形繼承問題
      • 7.1 繼承模型
      • 7.2 虛繼承
      • 7.3 虛擬繼承的原理
      • 7.4 多繼承面試題
    • 8. 繼承和組合

繼承

1. 繼承的概念及定義

1.1 繼承的概念

繼承(inheritance)機制是面向對象程序設計使代碼可以復用的重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能,這樣產生新的類,稱為派生類。

繼承呈現了面向對象程序設計的層次結構,體現了由簡單到復雜的認知過程。以前接觸的復用都是函數復用,而繼承便是類設計層次的復用。

具體示例:

下面在沒有學習到繼承之前設計了兩個類 StudentTeacherStudentTeacher 都有姓名、年齡等成員變量,都有 Print 這個成員函數,然而這些內容在這兩個類中是重復出現的,設計到兩個類里面就是冗余的。

當讓這兩個類中也有一些不同的成員變量,比如 Teacher 中獨有的成員變量是工號,Student 中獨有的成員變量是學號。

//Student類
class Student
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "張三";   //姓名int _age = 18;  		//年齡int _stuid;   			//學號
};//Teacher類
class Teacher
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "張三";   //姓名int _age = 18;  		//年齡int _jobid;   			//工號
};

在這里插入圖片描述

下面是使用了繼承,將公有的成員都放到了 Person 中,StudentTeacher 都繼承Person,就可以復?這些成員,就 不需要重復定義了,省去了很多麻煩。

繼承后,父類 Person 的成員,包括成員函數和成員變量,都會變成子類的一部分,也就是說,子類 StudentTeacher 復用了父類 Person 的成員。

//父類
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "張三";   //姓名int _age = 18;     		//年齡
};//子類
class Student : public Person
{
protected:int _stuid;   //學號
};//子類
class Teacher : public Person
{
protected:int _jobid;   //工號
};

在這里插入圖片描述

1.2 繼承的定義

1.2.1 定義格式

下面看到 Person基類,也稱作父類Student派生類,也稱作子類。(因為翻譯的原因,所以既叫基類/派生類,也叫父類/子類)

在這里插入圖片描述

1.2.2 繼承方式和訪問限定符

訪問限定符有以下三種:

  1. public訪問
  2. protected訪問
  3. private訪問

繼承的方式也有類似的三種:

  1. public繼承
  2. protected繼承
  3. private繼承

在這里插入圖片描述

1.2.3 繼承基類成員訪問方式的變化

基類當中被不同訪問限定符修飾的成員,以不同的繼承方式繼承到派生類當中后,該成員最終在派生類當中的訪問方式將會發生變化。

類成員/繼承方式public繼承protected繼承private繼承
基類的public成員派生類的public成員派生類的protected成員派生類的private成員
基類的protected成員派生類的protected成員派生類的protected成員派生類的private成員
基類的private成員在派生類中不可見在派生類中不可見在派生類中不可見

**總結:**可以認為三種訪問限定符的權限大小為:public > protected > private,可以以這個為基準去理解表中的結果。

1.2.3.1 基類成員訪問方式的變化規則
  1. 在基類當中的訪問方式為 publicprotected 的成員,在派生類當中的訪問方式變為:Min(成員在基類的訪問方式,繼承方式)
  2. 在基類當中的訪問方式為 private 的成員,在派生類當中都是不可見的。

如何去理解基類的private成員在派生類當中不可見

這句話的意思是,無法在派生類當中訪問基類的 private 成員。

例如,雖然 Student 類繼承了 Person 類,但是無法在 Student 類當中訪問 Person 類當中的 private 成員 _name。

//基類
class Person
{
private:string _name = "張三"; //姓名
};
//派生類
class Student : public Person
{
public:void Print(){//在派生類當中訪問基類的private成員,報錯!cout << _name << endl; }
protected:int _stuid;   //學號
};

也就是說,基類的 private 成員無論以什么方式繼承,在派生類中都是不可見的,這里的不可見是指基類的私有成員雖然被繼承到了派生類對象中,但是語法上限制派生類對象不管在類里面還是類外面都不能去訪問它

  1. 因為規則2中規定基類的private成員在派生類中是不能被訪問的,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就需要定義為 protected ,由此可以看出,protected 限定符是因繼承才出現的。

**注意:**在實際運用中一般使用的都是 public 繼承,幾乎很少使用 protectedprivate 繼承,也不提倡使用 protectedprivate 繼承,因為使用 protectedprivate 繼承下來的成員都只能在派生類的類里面使用,實際中擴展維護性不強。

1.2.3.2 默認繼承方式

在使用繼承的時候也可以不指定繼承方式,使用關鍵字 class 時默認的繼承方式是 private ,使用 struct 時默認的繼承方式是 public

在關鍵字為class的派生類當中,所繼承的基類成員_name的訪問方式變為private。

//基類
class Person
{
public:string _name = "張三"; //姓名
};//派生類
class Student : Person //默認為private繼承
{
protected:int _stuid;   //學號
};

在關鍵字為struct的派生類當中,所繼承的基類成員_name的訪問方式仍為public。

//基類
class Person
{
public:string _name = "張三"; //姓名
};//派生類
struct Student : Person //默認為public繼承
{
protected:int _stuid;   //學號
};

注意: 雖然繼承時可以不指定繼承方式而采用默認的繼承方式,但還是最好顯示的寫出繼承方式。

1.3 繼承類模版

下面是利用繼承,通過 vector<int> 作為基類繼承給了 stack 從而達到快速開發的目的。

namespace bit
{//template<class T>//class vector//{};// stack和vector的關系,既符合is-a,也符合has-a template<class T>class stack : public std::vector<T>{public:void push(const T& x){// 基類是類模板時,需要指定?下類域, // 否則編譯報錯:error C3861: “push_back”: 找不到標識符 // 因為stack<int>實例化時,也實例化vector<int>了 // 但是模版是按需實例化,push_back等成員函數未實例化,所以找不到 vector<T>::push_back(x);//push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}};
}int main()
{bit::stack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();}return 0;
}

**補充:**第18行,使用了類模版中還未實例化的成員函數一定要指定類域才可以調用。

2. 基類和派生類的轉化

在繼承關系中,派生類對象可以直接賦值給基類的對象/基類的指針/基類的引用,而不產生類型轉換。這個賦值的過程也被形象的叫做切片或者切割,寓意把派生類中父類那部分切來賦值過去。

在這里插入圖片描述

如圖所示:派生類對象賦值給基類對象時是直接將派生類中屬于基類那一部分切割給基類。引用和指針也是一樣,基類的引用是派生類中屬于基類那一部分成員的別名,基類的指針指向派生類中屬于基類的那一部分

具體示例:

//基類
class Person
{
protected:string _name; //姓名string _sex;  //性別int _age;     //年齡
};//派生類
class Student : public Person
{
protected:int _stuid;   //學號
};int main()
{Student s;Person p = s;     //派生類對象賦值給基類對象,可以Person* ptr = &s; //派生類對象賦值給基類指針,可以Person& ref = s;  //派生類對象賦值給基類引用,可以s = p;	//基類對象不可以賦值給派生類,這里會編譯錯誤return 0;
}

派生類對象賦值給基類指針圖示:

在這里插入圖片描述

派生類對象賦值給基類引用圖示:

在這里插入圖片描述

**注意:**基類對象不能賦值給派生類對象,基類的指針可以通過強制類型轉換賦值給派生類的指針,但是此時基類的指針必須是指向派生類的對象才是安全的。

3. 繼承中的作用域

3.1 隱藏

在繼承體系中的基類和派生類都有獨立的作用域。若派生類和基類中有同名成員(成員變量、成員函數),派生類成員將屏蔽基類對自己作用域中同名成員的直接訪問,這種情況叫隱藏

具體示例:

#include <iostream>
#include <string>
using namespace std;//父類
class Person
{
protected:int _num = 111;
};//子類
class Student : public Person
{
public:void fun(){cout << _num << endl;}
protected:int _num = 999;
};int main()
{Student s;s.fun(); return 0;
}
//運行結果:999

對于以上代碼,訪問成員函數 fun 會打印子類中的成員變量 _num ,但是父類和子類中均有成員變量 _num ,但是這里會訪問子類中的 _num,也就是打印 999

補充:

  • 若此時就是要訪問父類當中的 _num 成員,可以使用作用域限定符進行指定訪問

    void fun()
    {cout << Person::_num << endl; //指定訪問父類當中的_num成員
    }
    

3.2 經典面試題

題目描述:

**問題1:**下面兩個 func 是什么關系?A. 重載 B. 重寫 C.沒關系

**問題2:**下面這段程序編譯運行的結果是什么?A. 編譯報錯 B. 運行報錯 C.正常運行

class A
{
public:void func(){cout << "func()" << endl;}
};class B : public A
{
public:void func(int i){cout << "func(int i)" <<i<<endl;}
};int main()
{B b;b.fun();return 0;
}

問題1:

雖然 A 類中的 func 函數和 B 類中的 func 函數同名且參數不同,但是它們不構成重載,因為它們的作用域不同,重載函數一定是在同一個作用域中的。并且根據隱藏的規則,成員函數的隱藏,只需要函數名相同就構成隱藏,可知,這兩個函數構成隱藏,選擇A。

問題2:

所以兩個 func 的關系是隱藏,因為 B 繼承自 A,這里通過實例化 B 的對象 b 來調用 fun 函數,但是 B 中的 fun 函數需要參數,所以這里的語法出現問題,會編譯報錯。選擇A。

如果這里想調用父類中的 fun 函數,需要在指定父類作用域。

總結:

  • 針對成員變量,派生類和基類中有同名成員,派生類成員將屏蔽基類對同名成員的直接訪問,這種情況叫隱藏。(在派生類成員函數中,可以使用基類 :: 基類成員顯示訪問)
  • 如果是成員函數的隱藏,只需要函數名相同就構成隱藏
  • 注意在實際中在繼承體系里面最好不要定義同名的成員。

4. 派生的默認成員函數

4.1 普通類的默認成員函數

在學習派生類的默認成員函數之前,先來回顧一下普通類的默認成員函數:C++中成員變量的類型一共可以分為兩類:內置類型和自定義類型,各個默認成員函數對它們的處理可以用下面兩個圖片概括:

在這里插入圖片描述

在這里插入圖片描述

**注意:**由于取地址重載和 const 取地址重載這兩個默認成員函數一般使用編譯器自動生成的即可,所以在這里不考慮它們。

4.2 派生類的默認成員函數

和普通類的默認成員函數一樣,這里只討論構造函數、析構函數、拷貝構造函數和賦值重載函數這四個成員函數。

4.2.1 派生類的構造函數

在均使用默認構造的前提下對于派生類的成員變量中的內置類型(有缺省值就用,沒有就由編譯器初始化)、自定義類型(使用默認構造)和父類成員(調用父類默認構造)。

若要自行實現構造函數,如果基類有默認的構造函數,派生類的構造函數無須調用基類的構造函數直接初始化子類的成員變量即可。如果基類沒有默認的構造函數,則必須在派生類構造函數的初始化列表階段顯示調用基類的構造函數,再對剩余派生類中的成員進行構造。

//父類
class Person 
{
public://父類構造函數Person(const char* name)//非默認構造: _name(name){cout << "Person()" << endl;}
protected:string _name; // 姓名
}//子類
class Student : public Person 
{
public://自行實現的子類構造函數Student(const char* name, int num): Person(name)  //顯示調用父類構造, _num(num){cout << "Student()" << endl;}protected:int _num; //學號
}

補充:上述代碼中的父類中沒有構造函數,所以其子類 Student 的構造函數就必須先顯示調用父類 Person 的構造函數再初始化子類中的成員變量。如果父類 Person 中的構造函數有默認構造那么子類的構造函數在默認情況下就無需顯式調用父類的構造函數,只需要對子類的成員函數進行初始化即可。

4.2.2 派生類的拷貝構造函數

在均使用默認拷貝構造的前提下對于派生類的成員變量中的內置類型(淺拷貝)、自定義類型(此類型的拷貝構造)和父類成員(調用父類的拷貝構造)。

如果要自行實現拷貝構造函數,派生類的拷貝構造函數必須先調用基類的拷貝構造完成基類的拷貝初始化。再對子類中的成員變量進行拷貝構造。

//父類
class Person 
{
public://父類拷貝構造函數Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}
protected:string _name; // 姓名
}//子類
class Student : public Person 
{
public://自行實現的子類拷貝構造函數Student(const Student& s): Person(s)  //顯示調用父類拷貝構造, _num(s._num){cout << "Student(const Student& s)" << endl;}protected:int _num; //學號
}

**補充:**針對拷貝構造無論父類如何,子類若要自行實現拷貝構造函數必須在初始化列表中顯示調用父類的拷貝構造函數。

并且這里調用父類的拷貝構造時傳遞的參數直接傳遞子類的變量名即可,因為以指針的形式接收,父類的拷貝構造函數接受后會對其進行切割,這里涉及基類和派生類對象的賦值中的知識點。

4.2.3 派生類的賦值重載函數

在均使用默認賦值重載構造的前提下對于派生類的成員變量中的內置類型(淺拷貝)、自定義類型(此類型的拷貝構造)和父類成員(調用父類的拷貝構造)。與拷貝構造相同。

如果要自行實現賦值重載函數,其要求也與自行實現拷貝構造函數相同,必須要調用基類的operator=完成基類的復制。

//父類
class Person 
{
public://父類賦值重載函數Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}
protected:string _name; // 姓名
}//子類
class Student : public Person 
{
public://自行實現的子類賦值重載函數Student& operator = (const Student& s) {cout << "Student& operator= (const Student& s)" << endl;if (this != &s){Person::operator =(s);  //父類賦值重載_num = s._num;}return *this;}
protected:int _num; //學號
}

**補充:**針對賦值重載函數無論父類如何,子類若要自行實現賦值重載函數必須在初始化列表中顯示調用父類的賦值重載函數。

同樣這里的賦值重載函數也需要使用傳子類的變量名作為參數給父類的賦值重載函數,因為因為以指針的形式接收,父類的賦值重載函數接受后會對其進行切割,使其變成子類對象中父類那部分的別名。

并且這里調用 operator = 時需要指定父類類域,因為如果不指定這里的 operator = 由于子類和父類中的函數名相同構成隱藏,就會一直調用子類的 operator = 最后造成棧溢出

4.2.4 派生類的析構函數
//父類
class Person 
{
public://父類析構函數~Person(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
}//子類
class Student : public Person 
{
public://自行實現的子類賦值重載函數~Student() {cout << "~Student()" << endl;}
protected:int _num; //學號
}

補充:派生類的析構函數會在被調用完成后自動調用基類的析構函數清理基類成員。所以在自行實現子類析構函時不需要顯示調用父類的析構函數。

如果在平時代碼中需要在子類中調用父類的析構函數需要使用類域指定析構函數,Person : ~Person()。這是因為派生類的析構和基類的析構構成隱藏關系。(由于多態關系需求,所有的析構函數的函數名都會被編譯器處理為 destructor,因為函數名相同所以構成隱藏。)

4.2.5 派生類的4個成員函數的總結
  1. 派生類的成員變量分為三類:內置類型、自定義類型以及父類成員變量。其中派生類成員函數對內置類型和自定義類型的處理和普通類的成員函數一樣,但是父類成員變量必須由父類成員函數來處理

  2. 派生類的析構函數非常特殊,它不需要我們顯式調用父類的析構函數,而是會在子類析構函數調用完畢后自動調用父類的析構函數,這樣做是為了保證子類成員先被析構,父類成員后被析構 (如果我們顯式調用父類析構,那么父類成員變量一定先于子類成員變量析構)。同時,子類析構和父類析構構成隱藏在這里插入圖片描述

  3. 派生類對象初始化先調用基類構造再調派生類構造,派生類對象析構清理先調用派生類析構再調基類的析構。

    并且派生類對象的析構函數在被調用完之后會自動調用基類的析構函數清理基類成員。因為只有這樣才能保證派生類對象先清理派生類成員在清理基類成員的順序。在這里插入圖片描述

4.3 面試題:實現一個不能被繼承的類

**方法1:**基類的構造函數私有,派生類的構成函數必須調用基類的構造函數,但是基類的構成函數私有化以后,派生類看不見就不能調用了,那么派生類就無法實例化出對象。

class Base 
{
public:void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private:// C++98的?法 Base(){}
};class Derive :public Base 
{void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};int main()
{Base b;Derive d;return 0;
}

上面這種是 C++98 給出的做法,它雖然阻止了子類創建對象,但是構造私有化也使得它本身也不能創建對象,因為創建對象需要調用構造函數。所以 C++11 提供了另外一種方式。

**方法2:**C++11新增了一個 final 關鍵字,final 修改基類,派生類就不能繼承了。

//C++11的方法
class Base final
{
public:void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private:
};class Derive :public Base 
{void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};int main()
{Base b;Derive d;return 0;
}

5. 繼承和友元

友元關系不能被繼承,基類友元不能訪問派生類私有和保護成員。

**簡記:**你父親的朋友并不是你的朋友。

class Student;
class Person 
{
public:friend void Display(const Person& p, const Student& s);  //友元函數
protected:string _name; // 姓名
};class Student : public Person 
{friend void Display(const Person& p, const Student& s);  //友元函數
protected:int _stuNum; // 學號
};void Display(const Person& p, const Student& s) 
{cout << p._name << endl;cout << s._stuNum << endl;
}

補充:這里的 Dispaly 函數分別調用了基類和派生類中的成員變量,如果需要訪問的話需要再基類和派生類中都加上友元聲明。

6. 繼承和靜態成員

在 類和對象介紹了類的靜態成員變量具有如下特性:

  • 靜態成員為所有類對象所共享,不屬于某個具體的對象,存放在靜態區;
  • 靜態成員變量必須在類外定義,定義時不添加 static 關鍵字,類中只是聲明;
  • 靜態成員變量的訪問受類域與訪問限定符的約束。

在繼承中,如果父類定義了 static 靜態成員,則該靜態成員也屬于所有派生類及其對象,即整個繼承體系里面只有一個這樣的成員,并且無論派生出多少個子類,都只有一個 static 成員實例。繼承下來的靜態成員變量都是指向同一塊空間的。

class Person
{
public:string _name;static int _count;	//靜態成員類內聲明
};
int Person::_count = 0;	//靜態成員類外定義class Student : public Person
{
protected:int _stuNum;
};

在這里插入圖片描述

7. 多繼承和菱形繼承問題

7.1 繼承模型

**單繼承:**一個子類只有一個直接父類時稱這個繼承關系為單繼承。

在這里插入圖片描述

**多繼承:**一個子類有兩個或兩個以上直接父類時稱這個繼承關系為多繼承。

在這里插入圖片描述

**菱形繼承:**菱形繼承是多繼承的一種特殊情況。

在這里插入圖片描述

從菱形繼承的模型構造就可以看出,菱形繼承的繼承方式存在數據冗余二義性的問題。

例如,對于以上菱形繼承的模型,當實例化出一個 Assistant 對象后,訪問成員時就會出現二義性問題。

class Person
{
public:string _name; //姓名
};class Student : public Person
{
protected:int _num; //學號
};class Teacher : public Person
{
protected:int _id; //職工編號
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; //主修課程
};int main()
{Assistant a;a._name = "peter"; //二義性:無法明確知道要訪問哪一個_name,產生報錯return 0;
}

補充: Assistant 對象是多繼承的 StudentTeacher ,而 StudentTeacher 當中都繼承了 Person ,因此 StudentTeacher 當中都有 _name 成員,若是直接訪問 Assistant 對象的 _name 成員會出現訪問不明確的報錯。

如果想要訪問 _name 中的數據,可以具體指定是哪個類域的 _name

//顯示指定訪問哪個父類的成員
a.Student::_name = "張同學";
a.Teacher::_name = "張老師";

雖然該方法可以解決二義性的問題,但仍然不能解決數據冗余的問題。因為在 Assistant的對象在 Person 成員始終會存在兩份。

在這里插入圖片描述

7.2 虛繼承

為了解決菱形繼承的二義性和數據冗余問題,出現了虛擬繼承。如前面說到的菱形繼承關系,在Student和Teacher繼承Person是使用虛擬繼承,即可解決問題

虛擬繼承代碼如下:

class Person
{
public:string _name; //姓名
};class Student : virtual public Person //虛擬繼承
{
protected:int _num; //學號
};class Teacher : virtual public Person //虛擬繼承
{
protected:int _id; //職工編號
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse; //主修課程
};int main()
{Assistant a;a._name = "peter"; //無二義性return 0;
}

此時就可以直接訪問 Assistant 對象的 _name 成員了,并且之后就算指定訪問 AssistantStudent 父類和 Teacher 父類的 _name 成員,訪問到的都是同一個結果,解決了二義性的問題。而打印 AssistantStudent 父類和 Teacher 父類的 _name 成員的地址時,顯示的也是同一個地址,解決了數據冗余的問題。

cout << a.Student::_name << endl; //運行結果:peter
cout << a.Teacher::_name << endl; //運行結果:petercout << &a.Student::_name << endl; //運行結果:0136F74C
cout << &a.Teacher::_name << endl; //運行結果:0136F74C

7.3 虛擬繼承的原理

7.4 多繼承面試題

多繼承中指針偏移問題?下面說法正確的是()

A: p1 == p2 == p3
B: p1 < p2 < p3
C: p1 == p3 != p2
D: p1 != p2 != p3

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };int main()
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

解答:

在這里插入圖片描述

首先創建一個 Derive 類的對象 d ,因為C++中規定在內存中先繼承的存儲在前面,所以這里的 Base1Base2 呈上圖存儲方式排列。

所以 p3 理所當然指向這塊空間的起始地址,然而對于 p1p2 因為其類型為 Derive 的父類,所以在賦值的時候,需要進行切片,指向子類中父類那一塊所屬的空間。

p1 指向 Base1 的起始地址也就是和 p3 指向的空間一樣,但是需要注意 p1p3 的含義并不一樣,如果對 p3 解引用其空間包含 d 的整塊空間,如果對 p1 解引用其空間則只包含 Base1 那一塊。p2與以上類似,指向 Base2 的起始地址。

最后根據圖示的地址大小可以得出答案為C。

8. 繼承和組合

public 繼承是一種 is-a 的關系。也就是說每個派生類對象都是一個基類對象,本質是子類對象是一種特殊的父類對象

組合是一種 has-a 的關系,假設 B 組合了 A,則每個 B 對象中都有一個 A 對象。

繼承允許你根據基類的實現來定義派生類的實現,這種通過生成派生類的復用通常被稱為白箱復用 (white-box reuse)。術語 “白箱” 是相對可視性而言:在繼承方式中,基類的內部細節對子類可見,即派生類可以訪問基類的 protected 成員 。所以繼承一定程度破壞了基類的封裝,基類的改變,對派生類有很大的影響,派生類和基類間的依賴關系很強,耦合度高

對象組合是類繼承之外的另一種復用選擇,新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接口,這種復用風格被稱為黑箱復用 (black-box reuse),因為對象的內部細節是不可見的,即組合只能訪問對象的共有成員,對象只以 “黑箱” 的形式出現。組合類之間沒有很強的依賴關系,耦合度低,優先使用對象組合有助于保持每個類被封裝

所以如果既能用繼承,也能用組合,優先使用組合,因為組合耦合度低,代碼維護性好。對于繼承來說,父類的任何一個非私有成員修改都可能會影響子類,而對于組合,只有公有成員修改才可能會影響;但在實際開發中基本上不會出現全部都是公有成員的類,所以優先使用組合。

不過繼承也有用武之地的,有些關系就適合繼承那就用繼承,另外要實現多態也必須要繼承,只是說當類之間的關系即可以用繼承,可以用組合時,優先使用組合。

**eg1:**車類和寶馬類就是is-a的關系,它們之間適合使用繼承。

class Car
{
protected:string _colour; //顏色string _num; //車牌號
};class BMW : public Car	//BWM是車,繼承關系
{
public:void Drive(){cout << "this is BMW" << endl;}
};

**eg2:**車和輪胎之間就是has-a的關系,它們之間則適合使用組合。

class Tire
{
protected:string _brand; //品牌size_t _size; //尺寸
};class Car	//車有輪胎,組合關系
{
protected:string _colour; //顏色string _num; //車牌號Tire _t; //輪胎
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/87218.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87218.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87218.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

無REPOSITORY、TAG的docker懸空鏡像究竟是什么?是否可刪除?

有時候&#xff0c;使用docker images指令我們可以發現大量的無REPOSITORY、TAG的docker鏡像&#xff0c;這些鏡像究竟是什么&#xff1f; 它們沒有REPOSITORY、TAG名稱&#xff0c;沒有辦法引用&#xff0c;那么它們還有什么用&#xff1f; [rootcdh-100 data]# docker image…

創建一個基于YOLOv8+PyQt界面的駕駛員疲勞駕駛檢測系統 實現對駕駛員疲勞狀態的打哈欠檢測,頭部下垂 疲勞眼睛檢測識別

如何使用Yolov8創建一個基于YOLOv8的駕駛員疲勞駕駛檢測系統 文章目錄 1. 數據集準備2. 安裝依賴3. 創建PyQt界面4. 模型訓練1. 數據集準備2. 模型訓練數據集配置文件 (data.yaml)訓練腳本 (train.py) 3. PyQt界面開發主程序 (MainProgram.py) 4. 運行項目5. 關鍵代碼解釋數據集…

使用FFmpeg將YUV編碼為H.264并封裝為MP4,通過api接口實現

YUV數據來源 攝像頭直接采集的原始視頻流通常為YUV格式&#xff08;如YUV420&#xff09;&#xff0c;尤其是安防攝像頭和網絡攝像頭智能手機、平板電腦的攝像頭通過硬件接口視頻會議軟件&#xff08;如Zoom、騰訊會議&#xff09;從攝像頭捕獲YUV幀&#xff0c;進行預處理&am…

tcpdump工具交叉編譯

本文默認系統已經安裝了交叉工具鏈環境。 下載相關版本源碼 涉及tcpdump源碼&#xff0c;以及tcpdump編譯過程依賴的pcap庫源碼。 網站&#xff1a;http://www.tcpdump.org/release wget http://www.tcpdump.org/release/libpcap-1.8.1.tar.gz wget http://www.tcpdump.org/r…

神經網絡中torch.nn的使用

卷積層 通過卷積核&#xff08;濾波器&#xff09;在輸入數據上滑動&#xff0c;卷積層能夠自動檢測和提取局部特征&#xff0c;如邊緣、紋理、顏色等。不同的卷積核可以捕捉不同類型的特征。 nn.conv2d() in_channels:輸入的通道數&#xff0c;彩色圖片一般為3通道 out_c…

在MATLAB中使用GPU加速計算及多GPU配置

文章目錄 在MATLAB中使用GPU加速計算及多GPU配置一、基本GPU加速使用1. 檢查GPU可用性2. 將數據傳輸到GPU3. 執行GPU計算 二、多GPU配置與使用1. 選擇特定GPU設備2. 并行計算工具箱中的多GPU支持3. 數據并行處理&#xff08;適用于深度學習&#xff09; 三、高級技巧1. 異步計算…

【unitrix】 4.12 通用2D仿射變換矩陣(matrix/types.rs)

一、源碼 這段代碼定義了一個通用的2D仿射變換矩陣結構&#xff0c;可用于表示二維空間中的各種線性變換。 /// 通用2D仿射變換矩陣&#xff08;元素僅需實現Copy trait&#xff09; /// /// 該矩陣可用于表示二維空間中的任意仿射變換&#xff0c;支持以下應用場景&#xff…

android RecyclerView隱藏整個Item后,該Item還占位留白問題

前言 android RecyclerView隱藏整個Item后,該Item還占位留白問題 思考了利用隱藏和現實來控制item 結果實現不了方案 解決方案 要依據 model 的第三個參數&#xff08;布爾值&#xff09;決定是否保留數據&#xff0c;可以通過 ?filter 高階函數結合 ?空安全操作符? 實…

地圖瓦片介紹與地圖瓦片編程下載

前沿 地圖瓦片指將一定范圍內的地圖按照一定的尺寸和格式&#xff0c;按縮放級別或者比例尺&#xff0c;切成若干行和列的正方形柵格圖片&#xff0c;對切片后的正方形柵格圖片被形象的稱為瓦片[。瓦片通常應用于B/S軟件架構下&#xff0c;瀏覽器從服務器獲取地圖數據&#xf…

手機屏亮點缺陷修復及相關液晶線路激光修復原理

摘要 手機屏亮點缺陷嚴重影響顯示品質&#xff0c;液晶線路短路、電壓異常是導致亮點的關鍵因素。激光修復技術憑借高能量密度與精準操控性&#xff0c;可有效修復液晶線路故障&#xff0c;消除亮點缺陷。本文分析亮點缺陷成因&#xff0c;深入探究液晶線路激光修復原理、工藝…

MySQL數據一鍵同步至ClickHouse數據庫

隨著數據量的爆炸式增長和業務場景的多樣化&#xff0c;傳統數據庫系統如MySQL雖然穩定可靠&#xff0c;但在海量數據分析場景下逐漸顯露出性能瓶頸。這時&#xff0c;ClickHouse憑借其列式存儲架構和卓越的OLAP&#xff08;在線分析處理&#xff09;能力脫穎而出&#xff0c;成…

Android中Compose常用組件以及布局使用方法

一、基礎控件詳解 1. Text - 文本控件 Text(text "Hello Compose", // 必填&#xff0c;顯示文本color Color.Blue, // 文字顏色fontSize 24.sp, // 字體大小&#xff08;注意使用.sp單位&#xff09;fontStyle FontStyle.Italic, // 字體樣式&…

SCI一區黑翅鳶優化算法+三模型光伏功率預測對比!BKA-CNN-GRU、CNN-GRU、GRU三模型多變量時間序列預測

SCI一區黑翅鳶優化算法三模型光伏功率預測對比&#xff01;BKA-CNN-GRU、CNN-GRU、GRU三模型多變量時間序列預測 目錄 SCI一區黑翅鳶優化算法三模型光伏功率預測對比&#xff01;BKA-CNN-GRU、CNN-GRU、GRU三模型多變量時間序列預測效果一覽基本介紹程序設計參考資料 效果一覽 …

創客匠人視角:創始人 IP 打造為何成為知識變現的核心競爭力

在互聯網流量成本高企的當下&#xff0c;知識變現行業正經歷從 “產品競爭” 到 “IP 競爭” 的范式遷移。創客匠人 CEO 老蔣指出&#xff0c;創始人 IP 已成為企業突破增長瓶頸的關鍵支點 —— 美特斯邦威創始人周成建首次直播即創下 1500 萬元成交額&#xff0c;印證了創始人…

類圖+案例+代碼詳解:軟件設計模式----生成器模式(建造者模式)

生成器模式&#xff08;建造者模式&#xff09; 把復雜對象的建造過程和表示分離&#xff0c;讓同樣的建造過程可以創建不同的表示。 假設你去快餐店買漢堡&#xff0c;漢堡由面包、肉餅、蔬菜、醬料等部分組成。 建造者模式的角色類比&#xff1a; 產品&#xff08;Product…

UI前端與數字孿生融合探索:為智慧物流提供可視化解決方案

hello寶子們...我們是艾斯視覺擅長ui設計、前端開發、數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩! 在全球供應鏈數字化轉型的浪潮中&#xff0c;智慧物流正從概念走向落地 —— 據 MarketsandMa…

遠程辦公與協作新趨勢:從遠程桌面、VDI到邊緣計算,打造高效、安全的混合辦公環境

一、引言 隨著數字化轉型的加速&#xff0c;越來越多的企業開始采用遠程辦公和混合辦公模式&#xff0c;以提升員工的靈活性和企業的敏捷性。然而&#xff0c;異地辦公也帶來了諸如桌面環境不一致、安全風險增加、溝通協作效率降低等諸多挑戰。因此&#xff0c;如何打造一致、…

算法總結篇:二叉樹

二叉樹解題整體框架&#xff1a; 1、確定當前題型是做高度還是深度還是搜索樹還是其他 高度&#xff08;從下往上&#xff0c;求根深度、高度等&#xff09;&#xff1a; 使用后序遍歷會更加簡單&#xff0c;遞歸方法一般需要返回值返回上級&#xff0c;讓上級對返回值進行判斷…

【Elasticsearch】most_fields、best_fields、cross_fields 的區別與用法

most_fields、best_fields、cross_fields 的區別與用法 1.核心區別概述2.詳細解析與用法2.1 best_fields&#xff08;最佳字段匹配&#xff09;2.2 most_fields&#xff08;多字段匹配&#xff09;2.3 cross_fields&#xff08;跨字段匹配&#xff09; 3.對比案例3.1 使用 best…

力扣網C語言編程題:在數組中查找目標值位置之暴力解法

一. 簡介 本文記錄一下力扣網上涉及數組的問題&#xff1a;排序數組中查找目標值的位置。主要以C語言實現。 二. 力扣網C語言編程題&#xff1a;在數組中查找目標值位置 題目&#xff1a;在排序數組中查找元素的第一個和最后一個位置 給你一個按照非遞減順序排列的整數數組 …