1. 繼承
1.1概念
繼承(Inheritance)是面向?qū)ο缶幊讨械囊粋€(gè)重要概念,它允許一個(gè)類(lèi)(稱(chēng)為派生類(lèi)或子類(lèi))從另一個(gè)類(lèi)(稱(chēng)為基類(lèi)或父類(lèi))繼承屬性和方法。繼承是實(shí)現(xiàn)類(lèi)之間的關(guān)系,通過(guò)繼承,子類(lèi)可以重用父類(lèi)的代碼,并且可以在此基礎(chǔ)上添加新的功能或修改已有的功能。
在C++中,繼承可以通過(guò)關(guān)鍵字 class
或 struct
后面的冒號(hào)來(lái)聲明和定義。語(yǔ)法格式如下:
class DerivedClass : accessSpecifier BaseClass {
// DerivedClass 的成員和方法
};
其中,DerivedClass
是派生類(lèi)的名稱(chēng),BaseClass
是基類(lèi)的名稱(chēng),accessSpecifier
是訪問(wèn)控制符,可以是 public
、protected
或 private
,用于控制派生類(lèi)對(duì)基類(lèi)成員的訪問(wèn)權(quán)限。
C++ 支持三種繼承方式:
-
公有繼承(Public Inheritance):通過(guò)
public
關(guān)鍵字聲明,派生類(lèi)可以訪問(wèn)基類(lèi)的公有成員和受保護(hù)成員,基類(lèi)的私有成員對(duì)派生類(lèi)不可見(jiàn)。 -
保護(hù)繼承(Protected Inheritance):通過(guò)
protected
關(guān)鍵字聲明,派生類(lèi)可以訪問(wèn)基類(lèi)的公有成員和受保護(hù)成員,基類(lèi)的私有成員對(duì)派生類(lèi)不可見(jiàn)。 -
私有繼承(Private Inheritance):通過(guò)
private
關(guān)鍵字聲明,派生類(lèi)無(wú)法訪問(wèn)基類(lèi)的成員,除了通過(guò)友元關(guān)系。
派生類(lèi)可以繼承基類(lèi)的成員變量和成員函數(shù),包括公有成員、受保護(hù)成員和私有成員。繼承的成員可以通過(guò)派生類(lèi)對(duì)象進(jìn)行訪問(wèn)。
繼承還可以形成類(lèi)的層次結(jié)構(gòu),派生類(lèi)可以再次被其他類(lèi)繼承,形成多層次繼承關(guān)系。繼承鏈上的每個(gè)派生類(lèi)都可以添加新的成員和方法,形成更豐富的功能。
需要注意的是,C++ 支持多繼承,即一個(gè)派生類(lèi)可以同時(shí)繼承多個(gè)基類(lèi)。多繼承的語(yǔ)法類(lèi)似于單繼承,通過(guò)逗號(hào)分隔多個(gè)基類(lèi)。
繼承在面向?qū)ο缶幊讨惺且环N強(qiáng)大的機(jī)制,它提供了代碼重用和擴(kuò)展的能力。通過(guò)繼承,可以建立類(lèi)之間的關(guān)系,并且實(shí)現(xiàn)了多態(tài)性和抽象性等重要概念。
例
已有的類(lèi)稱(chēng)為父類(lèi)或者基類(lèi)
新的類(lèi)稱(chēng)為子類(lèi)或者派生類(lèi)
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
void work(){
cout<<"我是名廚師"<<endl;
}
void show(){
cout<<"姓氏:"<<first_name<<endl;
}
};
class Son:public Father{
};
int main()
{
Son s;
s.show();
s.work();
}
在父類(lèi)基礎(chǔ)上做更改
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
void work(){
cout<<"我是名廚師"<<endl;
}
void show(){
cout<<"姓氏:"<<first_name<<endl;
}
};
class Son:public Father{
public:
int age=20;
void work(){
cout<<"我是名貨車(chē)司機(jī)"<<endl;
}
void show(){
//調(diào)用基類(lèi)的show()方法
Father::show();
//cout<<"年齡"<<age<<"姓氏:"<<first_name<<endl;
cout<<"年齡"<<age<<endl;
}
};
int main()
{
Son s;
s.show();
s.work();
}
1.2 構(gòu)造函數(shù)
構(gòu)造函數(shù)(Constructor)是一種特殊的成員函數(shù),用于在創(chuàng)建對(duì)象時(shí)初始化對(duì)象的狀態(tài)。構(gòu)造函數(shù)的名稱(chēng)與類(lèi)的名稱(chēng)相同,沒(méi)有返回類(lèi)型(包括 void),并且可以帶有參數(shù)。
在C++中,每個(gè)類(lèi)可以定義一個(gè)或多個(gè)構(gòu)造函數(shù)。當(dāng)創(chuàng)建類(lèi)的對(duì)象時(shí),會(huì)自動(dòng)調(diào)用相應(yīng)的構(gòu)造函數(shù)來(lái)初始化對(duì)象的數(shù)據(jù)成員。構(gòu)造函數(shù)可以執(zhí)行必要的初始化操作,如分配內(nèi)存、初始化成員變量、設(shè)置默認(rèn)值等。
構(gòu)造函數(shù)的特點(diǎn)如下:
-
與類(lèi)同名:構(gòu)造函數(shù)的名稱(chēng)必須與類(lèi)的名稱(chēng)完全相同。
-
沒(méi)有返回類(lèi)型:構(gòu)造函數(shù)沒(méi)有返回類(lèi)型,包括 void。在函數(shù)聲明和定義時(shí)不需要指定返回類(lèi)型。
-
可以帶有參數(shù):構(gòu)造函數(shù)可以帶有參數(shù),用于初始化對(duì)象的數(shù)據(jù)成員。參數(shù)可以是任意類(lèi)型,包括基本類(lèi)型、類(lèi)類(lèi)型和用戶(hù)自定義類(lèi)型。
-
可以重載:同一個(gè)類(lèi)可以定義多個(gè)構(gòu)造函數(shù),通過(guò)參數(shù)的類(lèi)型和數(shù)量的不同進(jìn)行重載。根據(jù)實(shí)際需要,可以使用不同的構(gòu)造函數(shù)來(lái)創(chuàng)建對(duì)象。
以下是一個(gè)示例,展示了如何定義和使用構(gòu)造函數(shù):
class MyClass {
public:
// 默認(rèn)構(gòu)造函數(shù)
MyClass() {
// 初始化數(shù)據(jù)成員
value = 0;
}
// 帶參數(shù)的構(gòu)造函數(shù)
MyClass(int num) {
// 使用參數(shù)初始化數(shù)據(jù)成員
value = num;
}
// 成員函數(shù)
void printValue() {
std::cout << "Value: " << value << std::endl;
}
private:
int value;
};
int main() {
// 使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建對(duì)象
MyClass obj1;
obj1.printValue(); // Output: Value: 0
// 使用帶參數(shù)的構(gòu)造函數(shù)創(chuàng)建對(duì)象
MyClass obj2(42);
obj2.printValue(); // Output: Value: 42
return 0;
}
在上面的示例中,MyClass
類(lèi)定義了兩個(gè)構(gòu)造函數(shù):一個(gè)默認(rèn)構(gòu)造函數(shù)和一個(gè)帶參數(shù)的構(gòu)造函數(shù)。默認(rèn)構(gòu)造函數(shù)沒(méi)有參數(shù),用于初始化對(duì)象的數(shù)據(jù)成員為默認(rèn)值。帶參數(shù)的構(gòu)造函數(shù)接收一個(gè)整數(shù)參數(shù),并用該參數(shù)初始化對(duì)象的數(shù)據(jù)成員。
在 main()
函數(shù)中,我們使用不同的構(gòu)造函數(shù)創(chuàng)建了兩個(gè) MyClass
對(duì)象 obj1
和 obj2
,并調(diào)用 printValue()
成員函數(shù)打印對(duì)象的值。
構(gòu)造函數(shù)在對(duì)象創(chuàng)建過(guò)程中自動(dòng)調(diào)用,可以確保對(duì)象在使用之前處于有效的狀態(tài)。構(gòu)造函數(shù)的重載和參數(shù)化使得可以根據(jù)需要進(jìn)行靈活的對(duì)象初始化。
1.2.1 透?jìng)鳂?gòu)造
透?jìng)鳂?gòu)造(Forwarding Constructors)是C++11引入的一種構(gòu)造函數(shù)技術(shù),它允許一個(gè)類(lèi)的構(gòu)造函數(shù)將參數(shù)透?jìng)鹘o另一個(gè)構(gòu)造函數(shù),從而避免了重復(fù)的代碼編寫(xiě)。
透?jìng)鳂?gòu)造的主要思想是,一個(gè)構(gòu)造函數(shù)可以接受相同的參數(shù)并將它們傳遞給另一個(gè)構(gòu)造函數(shù)來(lái)完成對(duì)象的構(gòu)造。這樣可以減少代碼冗余,提高代碼的可維護(hù)性和重用性。
在C++中,可以使用以下語(yǔ)法來(lái)實(shí)現(xiàn)透?jìng)鳂?gòu)造:
class MyClass {
public:
// 主構(gòu)造函數(shù)
MyClass(int num, double val, const std::string& str) {
// 對(duì)參數(shù)進(jìn)行初始化
// ...
}
// 透?jìng)鳂?gòu)造函數(shù)
MyClass(int num) : MyClass(num, 0.0, "") {
// 將參數(shù)透?jìng)鹘o主構(gòu)造函數(shù)
}
// 其他成員函數(shù)
// ...
};
在上面的示例中,MyClass
類(lèi)定義了一個(gè)主構(gòu)造函數(shù)和一個(gè)透?jìng)鳂?gòu)造函數(shù)。主構(gòu)造函數(shù)接受三個(gè)參數(shù),并進(jìn)行相應(yīng)的初始化操作。透?jìng)鳂?gòu)造函數(shù)只接受一個(gè)整數(shù)參數(shù),并將該參數(shù)透?jìng)鹘o主構(gòu)造函數(shù),同時(shí)使用默認(rèn)值來(lái)初始化其他參數(shù)。
通過(guò)透?jìng)鳂?gòu)造函數(shù),我們可以使用不同的參數(shù)列表來(lái)創(chuàng)建對(duì)象,而實(shí)際的初始化工作由主構(gòu)造函數(shù)完成。這樣可以避免在透?jìng)鳂?gòu)造函數(shù)中重復(fù)編寫(xiě)相同的初始化代碼,提高了代碼的可讀性和維護(hù)性。
需要注意的是,透?jìng)鳂?gòu)造函數(shù)只能將參數(shù)透?jìng)鹘o同一個(gè)類(lèi)中的另一個(gè)構(gòu)造函數(shù),不能透?jìng)鹘o父類(lèi)的構(gòu)造函數(shù)或其他類(lèi)的構(gòu)造函數(shù)。
透?jìng)鳂?gòu)造在處理多個(gè)構(gòu)造函數(shù)的情況下非常有用,它簡(jiǎn)化了構(gòu)造函數(shù)的實(shí)現(xiàn),減少了代碼的冗余,并提供了更靈活的對(duì)象初始化方式。
例
子類(lèi)直接調(diào)用父類(lèi)的構(gòu)造方法
#include <iostream>
using namespace std;
class Father{
private:
string first_name;
string sex;
public:
Father(string first_name){ //父類(lèi)中給出構(gòu)造函數(shù) 默認(rèn)無(wú)參的就不存在
this->first_name=first_name;
}
Father(string first_name,string sex){
this->first_name=first_name;
this->sex=sex;
}
void show(){
cout<<"姓氏:"<<first_name<<endl;
}
};
class Son:public Father{
private:
int age;
public:
Son(string n,int a):Father(n),age(a){}
Son(string first_name,string sex,int age):Father(first_name,sex),age(age){}
void show(){
Father::show();
//cout<<"年齡"<<age<<"姓氏:"<<first_name<<endl;
cout<<"年齡"<<age<<endl;
}
};
int main()
{
Son s("李",20);
s.show();
s.work();
Son s2("王","男",30);
s2.show();
}
1.2.2 委托構(gòu)造
委托構(gòu)造(Delegating Constructors)是C++11引入的一種構(gòu)造函數(shù)技術(shù),它允許一個(gè)構(gòu)造函數(shù)委托給同一個(gè)類(lèi)的另一個(gè)構(gòu)造函數(shù)完成對(duì)象的初始化。
委托構(gòu)造的主要思想是,一個(gè)構(gòu)造函數(shù)可以調(diào)用同一個(gè)類(lèi)中的其他構(gòu)造函數(shù)來(lái)完成對(duì)象的初始化。這樣可以避免在不同的構(gòu)造函數(shù)中重復(fù)編寫(xiě)相同的初始化代碼。
在C++中,可以使用以下語(yǔ)法來(lái)實(shí)現(xiàn)委托構(gòu)造:
class MyClass {
public:
// 構(gòu)造函數(shù)1
MyClass() : MyClass(0, 0.0, "") {
// 委托給構(gòu)造函數(shù)2
}
// 構(gòu)造函數(shù)2
MyClass(int num, double val, const std::string& str) {
// 對(duì)參數(shù)進(jìn)行初始化
// ...
}
// 其他成員函數(shù)
// ...
};
在上面的示例中,MyClass
類(lèi)定義了兩個(gè)構(gòu)造函數(shù):構(gòu)造函數(shù)1和構(gòu)造函數(shù)2。構(gòu)造函數(shù)1通過(guò)使用冒號(hào)初始化列表調(diào)用構(gòu)造函數(shù)2來(lái)完成對(duì)象的初始化。這樣,構(gòu)造函數(shù)1不再需要顯式初始化對(duì)象的數(shù)據(jù)成員,而是將工作委托給構(gòu)造函數(shù)2。
通過(guò)委托構(gòu)造,我們可以避免在多個(gè)構(gòu)造函數(shù)中重復(fù)編寫(xiě)相同的初始化代碼,提高了代碼的可維護(hù)性和重用性。同時(shí),委托構(gòu)造還可以實(shí)現(xiàn)更清晰和簡(jiǎn)潔的構(gòu)造函數(shù)邏輯。
需要注意的是,C++11中引入的委托構(gòu)造要求委托的構(gòu)造函數(shù)在初始化列表中出現(xiàn),而不是在函數(shù)體內(nèi)部。
委托構(gòu)造在處理多個(gè)構(gòu)造函數(shù)的情況下非常有用,它簡(jiǎn)化了構(gòu)造函數(shù)的實(shí)現(xiàn),減少了代碼的冗余,并提供了更靈活的對(duì)象初始化方式。
例
一個(gè)類(lèi)中構(gòu)造函數(shù)訪問(wèn)自己另一個(gè)構(gòu)造函數(shù)。相當(dāng)于間接的訪問(wèn)父類(lèi)的構(gòu)造函數(shù)。
Son(int a):Son(“王”,a){} —>Son(string n,int a):Father(n),age(a){} -->Father(string first_name)
#include <iostream>
using namespace std;
class Father{
private:
string first_name;
string sex;
public:
Father(string first_name){ //父類(lèi)中給出構(gòu)造函數(shù) 默認(rèn)無(wú)參的就不存在
this->first_name=first_name;
}
Father(string first_name,string sex){
this->first_name=first_name;
this->sex=sex;
}
void show(){
cout<<"姓氏:"<<first_name<<endl;
}
};
class Son:public Father{
private:
int age;
public:
Son(int a):Son("王",a){}
Son(string n,int a):Father(n),age(a){}
Son(string first_name,string sex,int age):Father(first_name,sex),age(age){}
void show(){
Father::show();
//cout<<"年齡"<<age<<"姓氏:"<<first_name<<endl;
cout<<"年齡"<<age<<endl;
}
};
int main()
{
Son s(20);
s.show();
}
1.2.3 繼承練習(xí)題
封裝Person 類(lèi)
name,age,sex
給出show方法要輸出上面三條信息
封裝Employee 職員 繼承自Person
salary, work_id
給出show方法要輸出除了Person類(lèi)中基本的情況之外 再輸出salary, work_id
封裝Manager 管理者 繼承自Employee
position 職位
給出show方法要輸出除了Employee類(lèi)中基本的情況之外 再輸出position
#include <iostream>
using namespace std;
class Person{
private:
string name;
int age;
string sex;
public:
Person(string name,int age,string sex){
this->name=name;
this->age=age;
this->sex=sex;
}
void show(){
cout<<name<<" "<<age<<" "<<sex<<endl;
}
};
class Employee:public Person{ //職員類(lèi)繼承自Person類(lèi)
private:
double salary;
string work_id;
public:
Employee(string name,int age,string sex,double salary,string work_id):Person(name,age,sex){
this->salary=salary;
this->work_id=work_id;
}
void show(){
Person::show();
cout<<salary<<" "<<work_id<<endl;
}
};
class Manager:public Employee{
private:
string position;
public:
//指明直接基類(lèi)的構(gòu)造函數(shù)函數(shù)如何調(diào)用
Manager(string name,int age,string sex,double salary,string work_id,string position)
:Employee(name,age,sex,salary,work_id)
{
this->position=position;
}
void show(){
Employee::show();
cout<<position<<endl;
}
};
int main()
{
Manager m("老吳",40,"男",8000,"007","經(jīng)理");
m.show();
}
1.3 對(duì)象的創(chuàng)建與銷(xiāo)毀
在C++中,對(duì)象的創(chuàng)建和銷(xiāo)毀是通過(guò)構(gòu)造函數(shù)和析構(gòu)函數(shù)來(lái)完成的。
-
對(duì)象的創(chuàng)建:
- 靜態(tài)對(duì)象的創(chuàng)建:靜態(tài)對(duì)象是在程序運(yùn)行之前就被創(chuàng)建的,它們存在于程序的整個(gè)生命周期中。靜態(tài)對(duì)象的創(chuàng)建由靜態(tài)存儲(chǔ)區(qū)域負(fù)責(zé)管理,例如全局變量或靜態(tài)成員變量。
- 棧上對(duì)象的創(chuàng)建:棧上對(duì)象是在函數(shù)內(nèi)部通過(guò)聲明局部變量來(lái)創(chuàng)建的,它們的生命周期與所在的作用域相對(duì)應(yīng)。當(dāng)函數(shù)退出作用域時(shí),棧上對(duì)象會(huì)自動(dòng)被銷(xiāo)毀。
- 堆上對(duì)象的創(chuàng)建:堆上對(duì)象通過(guò)使用
new
運(yùn)算符在堆上動(dòng)態(tài)分配內(nèi)存來(lái)創(chuàng)建。堆上對(duì)象的生命周期由程序員手動(dòng)管理,需要通過(guò)delete
運(yùn)算符來(lái)顯式釋放對(duì)象所占用的內(nèi)存。
-
對(duì)象的銷(xiāo)毀:
- 析構(gòu)函數(shù):析構(gòu)函數(shù)是在對(duì)象被銷(xiāo)毀時(shí)自動(dòng)調(diào)用的特殊成員函數(shù)。它的主要作用是清理對(duì)象所占用的資源,例如釋放動(dòng)態(tài)分配的內(nèi)存、關(guān)閉文件等。析構(gòu)函數(shù)的名稱(chēng)與類(lèi)的名稱(chēng)相同,以波浪號(hào)
~
作為前綴,沒(méi)有返回類(lèi)型和參數(shù)。 - 對(duì)象銷(xiāo)毀的時(shí)機(jī):
- 靜態(tài)對(duì)象:在程序結(jié)束時(shí)自動(dòng)銷(xiāo)毀。
- 棧上對(duì)象:當(dāng)對(duì)象所在的作用域結(jié)束時(shí)自動(dòng)銷(xiāo)毀。
- 堆上對(duì)象:需要程序員手動(dòng)調(diào)用
delete
運(yùn)算符來(lái)銷(xiāo)毀對(duì)象,并釋放內(nèi)存。
- 析構(gòu)函數(shù):析構(gòu)函數(shù)是在對(duì)象被銷(xiāo)毀時(shí)自動(dòng)調(diào)用的特殊成員函數(shù)。它的主要作用是清理對(duì)象所占用的資源,例如釋放動(dòng)態(tài)分配的內(nèi)存、關(guān)閉文件等。析構(gòu)函數(shù)的名稱(chēng)與類(lèi)的名稱(chēng)相同,以波浪號(hào)
在對(duì)象銷(xiāo)毀的過(guò)程中,析構(gòu)函數(shù)會(huì)按照與構(gòu)造函數(shù)相反的順序被調(diào)用。這意味著先創(chuàng)建的對(duì)象的析構(gòu)函數(shù)會(huì)先被調(diào)用,后創(chuàng)建的對(duì)象的析構(gòu)函數(shù)會(huì)后被調(diào)用。
正確管理對(duì)象的創(chuàng)建和銷(xiāo)毀是編寫(xiě)高質(zhì)量C++代碼的重要部分。通過(guò)適當(dāng)?shù)厥褂脴?gòu)造函數(shù)和析構(gòu)函數(shù),可以確保對(duì)象的正確初始化和資源的及時(shí)釋放,避免內(nèi)存泄漏和資源泄漏的問(wèn)題。
例
#include <iostream>
using namespace std;
class Father{
private:
string name;
public:
Father(string n):name(n){
cout<<"父類(lèi)的構(gòu)造函數(shù)調(diào)用"<<endl;
}
~Father(){
cout<<"父類(lèi)的析構(gòu)函數(shù)調(diào)用"<<endl;
}
};
class Son:public Father{
public:
Son(string name):Father(name){
cout<<"子類(lèi)的構(gòu)造函數(shù)調(diào)用"<<endl;
}
~Son(){
cout<<"子類(lèi)的析構(gòu)函數(shù)調(diào)用"<<endl;
}
};
int main()
{
Son s("小明");
}
#include <iostream>
using namespace std;
class Value{
public:
string str;
Value(string str){
this->str=str;
cout<<this->str<<"創(chuàng)建了"<<endl;
}
~Value(){
cout<<str<<"銷(xiāo)毀了"<<endl;
}
};
class Father{
public:
string name;
Value v= Value("Father中的對(duì)象成員");
static Value s_v; //static對(duì)象成員
Father(string n):name(n){
cout<<"父類(lèi)的構(gòu)造函數(shù)調(diào)用"<<endl;
}
~Father(){
cout<<"父類(lèi)的析構(gòu)函數(shù)調(diào)用"<<endl;
}
};
Value Father::s_v=Value("Father中的static對(duì)象成員");
class Son:public Father{
public:
Value v=Value("Son中的對(duì)象成員");
static Value s_v;
Son(string name):Father(name){
cout<<"子類(lèi)的構(gòu)造函數(shù)調(diào)用"<<endl;
}
~Son(){
cout<<"子類(lèi)的析構(gòu)函數(shù)調(diào)用"<<endl;
}
};
Value Son::s_v=Value("Son中的static對(duì)象成員");
int main()
{
cout<<"——————————程序運(yùn)行之前————————"<<endl;
{
Son s("小明");
}
cout<<"------------"<<endl;
}
1.4 多繼承
1.4.1多繼承
多繼承(Multiple Inheritance)是面向?qū)ο缶幊讨械囊环N概念,指的是一個(gè)類(lèi)可以從多個(gè)父類(lèi)中繼承屬性和行為。
在C++中,多繼承允許一個(gè)類(lèi)派生自多個(gè)基類(lèi),通過(guò)使用逗號(hào)分隔的形式來(lái)指定多個(gè)基類(lèi)。例如:
class Derived : public Base1, public Base2 {
// 類(lèi)成員和函數(shù)
};
在上面的示例中,Derived
類(lèi)通過(guò) public
訪問(wèn)修飾符從 Base1
和 Base2
兩個(gè)類(lèi)中進(jìn)行多繼承。這意味著 Derived
類(lèi)將繼承 Base1
和 Base2
類(lèi)中的所有公有成員和函數(shù)。
多繼承可以帶來(lái)一些優(yōu)勢(shì),如代碼重用、靈活性和多態(tài)性。通過(guò)從多個(gè)基類(lèi)中繼承,一個(gè)派生類(lèi)可以擁有多個(gè)父類(lèi)的特性,并具備更強(qiáng)大的功能。
然而,多繼承也帶來(lái)了一些挑戰(zhàn)和潛在的問(wèn)題。其中一個(gè)主要問(wèn)題是命名沖突,當(dāng)多個(gè)父類(lèi)具有同名的成員時(shí),派生類(lèi)需要明確指定使用哪個(gè)父類(lèi)的成員。此外,多繼承也增加了類(lèi)的復(fù)雜性和理解難度。
為了避免多繼承帶來(lái)的問(wèn)題,需要謹(jǐn)慎設(shè)計(jì)和使用多繼承,確保派生類(lèi)在繼承多個(gè)基類(lèi)時(shí)能夠保持清晰、可維護(hù)和易于理解的結(jié)構(gòu)。
需要注意的是,多繼承在實(shí)際開(kāi)發(fā)中并不常見(jiàn),更常見(jiàn)的是單繼承或使用接口(純虛函數(shù))實(shí)現(xiàn)的多態(tài)性。在設(shè)計(jì)類(lèi)的繼承關(guān)系時(shí),應(yīng)根據(jù)具體情況權(quán)衡利弊,并選擇合適的繼承方式。
例
一個(gè)類(lèi)有兩個(gè)個(gè)或者多個(gè)基類(lèi)叫多繼承,會(huì)獲得多個(gè)基類(lèi)的屬性和方法
#include <iostream>
using namespace std;
class Bed{
public:
void sleep(){
cout<<"可以躺著"<<endl;
}
};
class Sofa{
public:
void sit(){
cout<<"可以坐著"<<endl;
}
};
class SofaBed:public Bed,public Sofa{
};
int main()
{
SofaBed sf;
sf.sit();
sf.sleep();
}
1.4.2 多繼承問(wèn)題
多繼承在面向?qū)ο缶幊讨惺且环N強(qiáng)大的工具,但也帶來(lái)了一些潛在的問(wèn)題和挑戰(zhàn)。以下是一些常見(jiàn)的多繼承問(wèn)題:
-
命名沖突:當(dāng)多個(gè)父類(lèi)擁有同名的成員(變量、函數(shù)等)時(shí),派生類(lèi)在訪問(wèn)這些成員時(shí)會(huì)出現(xiàn)沖突。需要通過(guò)作用域解析符(
::
)明確指定使用哪個(gè)父類(lèi)的成員,或者使用虛擬繼承來(lái)解決沖突。 -
菱形繼承(Diamond Inheritance):當(dāng)一個(gè)派生類(lèi)繼承自?xún)蓚€(gè)間接基類(lèi),而這兩個(gè)間接基類(lèi)又共同繼承自同一個(gè)基類(lèi)時(shí),就形成了菱形繼承結(jié)構(gòu)。這種結(jié)構(gòu)可能導(dǎo)致二義性問(wèn)題和內(nèi)存資源的浪費(fèi)。C++通過(guò)虛擬繼承(virtual inheritance)來(lái)解決菱形繼承問(wèn)題,確保只有一份共同基類(lèi)的實(shí)例。
-
難以理解和維護(hù):多繼承會(huì)增加類(lèi)的復(fù)雜性,使類(lèi)的層次結(jié)構(gòu)變得更加龐大和復(fù)雜。這可能使代碼更難理解、調(diào)試和維護(hù)。需要謹(jǐn)慎設(shè)計(jì)多繼承關(guān)系,使類(lèi)的層次結(jié)構(gòu)保持簡(jiǎn)潔、清晰和易于理解。
-
耦合性增加:多繼承可能導(dǎo)致類(lèi)之間的耦合性增加,因?yàn)榕缮?lèi)同時(shí)繼承了多個(gè)父類(lèi)的特性。修改一個(gè)父類(lèi)可能會(huì)對(duì)多個(gè)派生類(lèi)產(chǎn)生影響,增加了代碼的脆弱性和維護(hù)的困難度。
為了解決多繼承問(wèn)題,可以采取以下幾種策略:
- 使用虛擬繼承(virtual inheritance)解決菱形繼承問(wèn)題,確保只有一份共同基類(lèi)的實(shí)例。
- 使用命名空間(namespace)來(lái)解決命名沖突問(wèn)題,將同名的成員放置在不同的命名空間中。
- 通過(guò)合理的設(shè)計(jì),避免過(guò)度使用多繼承,盡量保持類(lèi)的層次結(jié)構(gòu)的簡(jiǎn)潔和清晰。
- 使用接口(純虛函數(shù))實(shí)現(xiàn)的多態(tài)性,而不是通過(guò)多繼承來(lái)實(shí)現(xiàn)。
總而言之,多繼承是一項(xiàng)強(qiáng)大的特性,但在使用時(shí)需要謹(jǐn)慎考慮,權(quán)衡利弊。合理設(shè)計(jì)和使用多繼承可以充分發(fā)揮其優(yōu)勢(shì),同時(shí)避免潛在的問(wèn)題和復(fù)雜性。
例
如果多繼承的兩個(gè)基類(lèi)中有重名的成員時(shí),會(huì)產(chǎn)生二義性.
解決方式:使用基類(lèi)::同名成員 進(jìn)行區(qū)分,明確成員來(lái)源
#include <iostream>
using namespace std;
class Bed{
public:
void sleep(){
cout<<"可以躺著"<<endl;
}
void positon(){
cout<<"放在臥室"<<endl;
}
};
class Sofa{
public:
void sit(){
cout<<"可以坐著"<<endl;
}
void positon(){
cout<<"放在客廳"<<endl;
}
};
class SofaBed:public Bed,public Sofa{
};
int main()
{
SofaBed sf;
sf.sit();
sf.sleep();
//sf.positon(); //二義性問(wèn)題
sf.Bed::positon();
sf.Sofa::positon();
}
1.4.3 菱形繼承
菱形繼承(Diamond Inheritance)是指在多繼承關(guān)系中存在一個(gè)菱形形狀的繼承結(jié)構(gòu)。這種結(jié)構(gòu)通常發(fā)生在一個(gè)派生類(lèi)繼承自?xún)蓚€(gè)不同的父類(lèi),而這兩個(gè)父類(lèi)又共同繼承自同一個(gè)基類(lèi)的情況。
下面是一個(gè)簡(jiǎn)單的示例來(lái)說(shuō)明菱形繼承的概念:
class Base {
public:
int value;
};
class Derived1 : public Base {
};
class Derived2 : public Base {
};
class Derived3 : public Derived1, public Derived2 {
};
在上面的示例中,Derived1
和 Derived2
類(lèi)分別直接繼承自 Base
類(lèi)。然后,Derived3
類(lèi)通過(guò)多繼承同時(shí)繼承自 Derived1
和 Derived2
類(lèi)。這樣就形成了一個(gè)菱形繼承結(jié)構(gòu)。
菱形繼承可能導(dǎo)致以下問(wèn)題:
-
冗余數(shù)據(jù):
Derived3
類(lèi)繼承了兩次Base
類(lèi),因此在Derived3
對(duì)象中會(huì)有兩份value
成員變量。這樣會(huì)導(dǎo)致冗余的數(shù)據(jù)存儲(chǔ)和內(nèi)存占用。 -
二義性:由于
Derived3
類(lèi)繼承自?xún)蓚€(gè)共同的基類(lèi),當(dāng)訪問(wèn)基類(lèi)的成員時(shí)可能發(fā)生二義性。例如,如果在Derived3
類(lèi)中訪問(wèn)value
成員變量,編譯器無(wú)法確定是從Derived1
還是Derived2
繼承的value
。
為了解決菱形繼承帶來(lái)的問(wèn)題,C++提供了虛擬繼承(virtual inheritance)機(jī)制。通過(guò)在派生類(lèi)對(duì)共同基類(lèi)的繼承關(guān)系前加上 virtual
關(guān)鍵字,可以確保在派生類(lèi)中只有一份共同基類(lèi)的實(shí)例。
修改示例代碼如下:
class Base {
public:
int value;
};
class Derived1 : virtual public Base {
};
class Derived2 : virtual public Base {
};
class Derived3 : public Derived1, public Derived2 {
};
通過(guò)在 Derived1
和 Derived2
類(lèi)的繼承聲明中加上 virtual
關(guān)鍵字,可以解決菱形繼承帶來(lái)的冗余數(shù)據(jù)和二義性問(wèn)題?,F(xiàn)在,在 Derived3
類(lèi)中只會(huì)有一份 value
成員變量,并且通過(guò) Derived3
類(lèi)可以訪問(wèn)到該成員變量。
虛擬繼承機(jī)制是通過(guò)在共同基類(lèi)的子對(duì)象中添加一個(gè)虛擬指針(vptr)來(lái)實(shí)現(xiàn)的。這個(gè)虛擬指針指向一個(gè)虛擬函數(shù)表(vtable),用于解決函數(shù)調(diào)用的二義性問(wèn)題。
例1
一個(gè)類(lèi)的兩個(gè)直接基類(lèi),也擁有一個(gè)共同基類(lèi),叫菱形繼承。這時(shí)也會(huì)產(chǎn)生二義性問(wèn)題
菱形解決二義性可以使用作用域限定符的方式區(qū)分
#include <iostream>
using namespace std;
class Furniture{
public:
int a=5;
void show(){
cout<<"家具類(lèi)的show方法"<<endl;
}
};
class Bed:public Furniture{
public:
//繼承一次Furniture成員
};
class Sofa:public Furniture{
public:
//繼承一次Furniture成員
};
class SofaBed:public Bed,public Sofa{
//這時(shí)SofaBed會(huì)繼承兩次Furniture成員
};
int main()
{
SofaBed sf;
sf.Bed::show();
sf.Sofa::show();
cout<< &(sf.Bed::a)<<endl; //0x61fe88
cout<<&(sf.Sofa::a)<<endl; //0x61fe8c
}
例2
菱形繼承也可以通過(guò)虛繼承的方式,解決二義性。虛繼承之后,最上層的基類(lèi)成員相當(dāng)于變成共享的。所有的派生類(lèi)都只獲得一份最上層基類(lèi)(Furniture)的成員。
#include <iostream>
using namespace std;
class Furniture{
public:
int a=5;
void show(){
cout<<"家具類(lèi)的show方法"<<endl;
}
};
class Bed:virtual public Furniture{
public:
};
class Sofa:virtual public Furniture{
public:
};
class SofaBed:public Bed,public Sofa{
};
int main()
{
SofaBed sf;
//sf.Bed::show();
//sf.Sofa::show();
sf.show(); //只有一份沒(méi)有二義性
cout<< &(sf.Bed::a)<<endl; //0x61fe8c
cout<<&(sf.Sofa::a)<<endl; //0x61fe8c
}
2. 權(quán)限
2.1 不同權(quán)限修飾的成員的訪問(wèn)情況
public 本類(lèi)中可以訪問(wèn) 子類(lèi)中可以訪問(wèn) 全局中可以訪問(wèn)
protected 本類(lèi)中可以訪問(wèn) 子類(lèi)中可以訪問(wèn) 全局不可以訪問(wèn)
private 本類(lèi)中可以訪問(wèn) 子類(lèi)不可以訪問(wèn) 全局不可以訪問(wèn)
public | 本類(lèi)中可以訪問(wèn) | 子類(lèi)中可以訪問(wèn) | 全局中可以訪問(wèn) |
protected | 本類(lèi)中可以訪問(wèn) | 子類(lèi)中可以訪問(wèn) | 全局不可以訪問(wèn) |
private | 本類(lèi)中可以訪問(wèn) | 子類(lèi)不可以訪問(wèn) | 全局不可以訪問(wèn) |
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
protected:
string car="勞斯萊斯";
private:
string password="123789";
public:
void show(){
cout<<first_name<<" "<<car<<" "<<password<<endl;
}
};
class Son:public Father{
public:
void show(){
cout<<first_name<<endl;
cout<<car<<endl;
//cout<<password<<endl; //父類(lèi)中私有的不能訪問(wèn)到
}
};
int main()
{
Father f;
f.show();
Son s;
s.show();
cout<<"-------------"<<endl;
cout<<f.first_name<<endl;//全局中可以訪問(wèn)類(lèi)中public權(quán)限成員
//cout<<f.car<<endl; //全局中不可以訪問(wèn)類(lèi)中protected權(quán)限成員
//cout<<f.password<<endl; //全局中不可以訪問(wèn)類(lèi)中private權(quán)限成員
}
2.2 不同權(quán)限的繼承
繼承中的權(quán)限有三種
- public權(quán)限繼承
- protected權(quán)限繼承
- private權(quán)限繼承
需要注意:
私有成員能夠繼承,但是不能直接訪問(wèn),需要通過(guò)public接口才可以訪問(wèn)。
各種權(quán)限的繼承都不會(huì)改變基類(lèi)中的私有內(nèi)容的權(quán)限類(lèi)型
繼承并不會(huì)改變基類(lèi)中的權(quán)限,只是繼承過(guò)來(lái)的內(nèi)容的權(quán)限在派生類(lèi)中發(fā)生了變化
2.2.1 public繼承
公有繼承情況下,繼承過(guò)來(lái)的內(nèi)容權(quán)限在子類(lèi)是不變的
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
protected:
string car="勞斯萊斯";
private:
string password="123789";
public:
void show(){
cout<<first_name<<" "<<car<<" "<<password<<endl;
}
};
class Son:public Father{
public:
void show(){
cout<<first_name<<endl;
cout<<car<<endl;
//cout<<password<<endl; //父類(lèi)中私有的不能訪問(wèn)到
}
};
class GrandSon:public Son{
public:
void show(){
cout<<first_name<<endl;
cout<<car<<endl;
}
};
int main()
{
Son s;
s.show();
GrandSon gs;
gs.show();
}
2.2.2 protected 繼承
保護(hù)繼承之后,繼承過(guò)來(lái)的內(nèi)容在子類(lèi)中的權(quán)限全變成protected
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
protected:
string car="勞斯萊斯";
private:
string password="123789";
public:
void show(){
cout<<first_name<<" "<<car<<" "<<password<<endl;
}
};
class Son:protected Father{
public:
void show(){
cout<<first_name<<endl;
cout<<car<<endl;
//cout<<password<<endl; //父類(lèi)中私有的不能訪問(wèn)到
}
};
class GrandSon:public Son{
public:
void show(){
cout<<first_name<<endl;
cout<<car<<endl;
}
};
int main()
{
Son s;
s.show();
//cout<<s.first_name<<endl; //父類(lèi)中的public在子類(lèi)中變成protected
GrandSon gs;
gs.show();
}
2.2.3 private繼承
私有繼承會(huì)把繼承過(guò)來(lái)的內(nèi)容,在子類(lèi)中都變成私有
#include <iostream>
using namespace std;
class Father{
public:
string first_name="王";
protected:
string car="勞斯萊斯";
private:
string password="123789";
public:
void show(){
cout<<first_name<<" "<<car<<" "<<password<<endl;
}
};
class Son:private Father{
public:
void show(){
cout<<first_name<<endl;
cout<<car<<endl;
//cout<<password<<endl; //父類(lèi)中私有的不能訪問(wèn)到
}
};
class GrandSon:public Son{
public:
void show(){
//cout<<first_name<<endl; Son中first_name變成私有 訪問(wèn)不到
//cout<<car<<endl; Son中first_name變成私有 訪問(wèn)不到
}
};
int main()
{
Son s;
s.show();
GrandSon gs;
gs.show();
}
3.多態(tài)
多態(tài)(Polymorphism)是面向?qū)ο缶幊痰囊粋€(gè)重要概念,指的是同一類(lèi)型的對(duì)象在不同的情況下表現(xiàn)出不同的行為。
在多態(tài)中,父類(lèi)的指針或引用可以指向子類(lèi)的對(duì)象,通過(guò)調(diào)用相同的方法,可以實(shí)現(xiàn)不同子類(lèi)對(duì)象的不同行為。這樣的特性可以使代碼更加靈活、可擴(kuò)展和可維護(hù)。
多態(tài)有兩種形式:靜態(tài)多態(tài)和動(dòng)態(tài)多態(tài)。
-
靜態(tài)多態(tài)(靜態(tài)多態(tài)性,Static Polymorphism)也稱(chēng)為編譯時(shí)多態(tài)或早期綁定(Early Binding),是通過(guò)函數(shù)重載和模板實(shí)現(xiàn)的。在編譯時(shí),根據(jù)函數(shù)的參數(shù)類(lèi)型或模板的實(shí)例化類(lèi)型,確定要調(diào)用的函數(shù)或方法。
-
動(dòng)態(tài)多態(tài)(動(dòng)態(tài)多態(tài)性,Dynamic Polymorphism)也稱(chēng)為運(yùn)行時(shí)多態(tài)或晚期綁定(Late Binding),是通過(guò)虛函數(shù)(virtual function)和繼承關(guān)系實(shí)現(xiàn)的。在運(yùn)行時(shí),根據(jù)對(duì)象的實(shí)際類(lèi)型來(lái)決定要調(diào)用的函數(shù)或方法。
動(dòng)態(tài)多態(tài)通過(guò)虛函數(shù)和繼承關(guān)系實(shí)現(xiàn),允許子類(lèi)重寫(xiě)父類(lèi)的虛函數(shù),并在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類(lèi)型調(diào)用相應(yīng)的函數(shù)。這使得可以通過(guò)父類(lèi)的指針或引用調(diào)用子類(lèi)特定的實(shí)現(xiàn),實(shí)現(xiàn)了多態(tài)的特性。
使用多態(tài)的好處包括:
- 代碼重用:可以通過(guò)父類(lèi)的指針或引用調(diào)用子類(lèi)的方法,實(shí)現(xiàn)代碼的重用,減少重復(fù)的代碼。
- 靈活性:可以在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類(lèi)型來(lái)決定調(diào)用哪個(gè)函數(shù),實(shí)現(xiàn)不同對(duì)象的不同行為。
- 擴(kuò)展性:通過(guò)添加新的子類(lèi)來(lái)擴(kuò)展功能,而無(wú)需修改現(xiàn)有的代碼。
要使用多態(tài),需要滿(mǎn)足以下條件:
- 使用繼承關(guān)系:子類(lèi)繼承自父類(lèi),子類(lèi)可以重寫(xiě)父類(lèi)的虛函數(shù)。
- 聲明虛函數(shù):在父類(lèi)中聲明虛函數(shù),使用
virtual
關(guān)鍵字進(jìn)行修飾。 - 使用父類(lèi)的指針或引用:通過(guò)父類(lèi)的指針或引用指向子類(lèi)的對(duì)象,來(lái)實(shí)現(xiàn)多態(tài)的調(diào)用。
總而言之,多態(tài)是面向?qū)ο缶幊讨兄匾母拍?,通過(guò)靜態(tài)多態(tài)和動(dòng)態(tài)多態(tài)可以實(shí)現(xiàn)代碼的靈活性和可擴(kuò)展性。動(dòng)態(tài)多態(tài)通過(guò)虛函數(shù)和繼承關(guān)系實(shí)現(xiàn),在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類(lèi)型來(lái)決定調(diào)用哪個(gè)函數(shù)。
總結(jié)如下
一個(gè)接口,多種狀態(tài)。程序運(yùn)行的時(shí)候選擇決定調(diào)用相應(yīng)的代碼
靜態(tài)多態(tài):運(yùn)算符重載,函數(shù)重載 在編譯的時(shí)候就已經(jīng)確定。
動(dòng)態(tài)多態(tài)的條件:
1.公有繼承
2.基類(lèi)指針或者基類(lèi)引用指向派生類(lèi)對(duì)象
3.派生類(lèi)覆蓋基類(lèi)中的虛函數(shù)
3.1 沒(méi)有使用多態(tài)的情況
在編譯的時(shí)候就已經(jīng)確定參數(shù)類(lèi)型,運(yùn)行的時(shí)候直接執(zhí)行參數(shù)類(lèi)型中的方法
#include <iostream>
using namespace std;
class Animla{
public:
void eat(){
cout<<"吃東西"<<endl;
}
};
class Cat:public Animla{
public:
void eat(){
cout<<"吃魚(yú)"<<endl;
}
};
class Dog:public Animla{
public:
void eat(){
cout<<"吃肉"<<endl;
}
};
void test(Animla& a){
a.eat();
}
int main()
{
Dog d;
test(d); //吃東西;
Cat c;
test(c); //吃東西;
}
3.2 使用多態(tài)的情況
函數(shù)覆蓋需要用到虛函數(shù)。虛函數(shù)就是virtual關(guān)鍵字修飾的函數(shù)
函數(shù)覆蓋和虛函數(shù)的一些特點(diǎn):
1.虛函數(shù)具有傳遞性,如果重寫(xiě)父類(lèi)中的虛函數(shù),子類(lèi)加不加virtual關(guān)鍵字都是虛函數(shù)。
2.構(gòu)造函數(shù)和靜態(tài)函數(shù)不能定義成虛函數(shù)。
3.虛函數(shù)聲明和定義分離時(shí),關(guān)鍵字只需要加在聲明處
重寫(xiě):函數(shù)的名稱(chēng) 和參數(shù)列表要完全一致。并且返回值一致或者能自動(dòng)轉(zhuǎn)換
運(yùn)行時(shí)根據(jù)傳入對(duì)象的類(lèi)型,來(lái)判斷應(yīng)該執(zhí)行哪個(gè)對(duì)象里的方法
#include <iostream>
using namespace std;
class Animla{
public:
virtual void eat(){
cout<<"吃東西"<<endl;
}
};
class Cat:public Animla{
public:
void eat(){
cout<<"吃魚(yú)"<<endl;
}
};
class Dog:public Animla{
public:
virtual void eat(){
cout<<"吃肉"<<endl;
}
};
//void test(Cat& c){
// c.eat();
//}
//void test(Dog& d){
// d.eat();
//}
void test(Animla& a){
a.eat();
}
void test2(Animla * a){
a->eat();
}
int main()
{
Dog d;
test(d); //吃肉
Cat c;
test(c); //吃魚(yú)
test2(&c);//吃魚(yú)
}
3.3 多態(tài)原理
多態(tài)的原理是基于虛函數(shù)(virtual function)和動(dòng)態(tài)綁定(dynamic binding)的機(jī)制實(shí)現(xiàn)的。
在多態(tài)中,當(dāng)一個(gè)父類(lèi)的指針或引用指向一個(gè)子類(lèi)的對(duì)象時(shí),通過(guò)調(diào)用相同的函數(shù)名,可以實(shí)現(xiàn)對(duì)不同子類(lèi)對(duì)象的不同行為。這是因?yàn)楦割?lèi)中的函數(shù)被聲明為虛函數(shù),子類(lèi)可以重寫(xiě)(覆蓋)這些虛函數(shù),以提供自己的實(shí)現(xiàn)。
在C++中,當(dāng)使用虛函數(shù)時(shí),編譯器為每個(gè)含有虛函數(shù)的類(lèi)生成一個(gè)虛函數(shù)表(vtable),這個(gè)表存儲(chǔ)了每個(gè)虛函數(shù)的地址。同時(shí),每個(gè)對(duì)象也會(huì)存儲(chǔ)一個(gè)指向其所屬類(lèi)的虛函數(shù)表的指針,這個(gè)指針?lè)Q為虛函數(shù)指針(vptr)。
當(dāng)通過(guò)父類(lèi)的指針或引用調(diào)用虛函數(shù)時(shí),實(shí)際調(diào)用的是虛函數(shù)表中對(duì)應(yīng)的函數(shù)。編譯器在編譯時(shí)并不確定具體調(diào)用哪個(gè)函數(shù),而是在運(yùn)行時(shí)根據(jù)對(duì)象的實(shí)際類(lèi)型來(lái)決定調(diào)用哪個(gè)函數(shù)。這個(gè)過(guò)程稱(chēng)為動(dòng)態(tài)綁定(dynamic binding)或后期綁定(late binding)。
動(dòng)態(tài)綁定的過(guò)程如下:
- 當(dāng)使用父類(lèi)的指針或引用調(diào)用虛函數(shù)時(shí),先根據(jù)指針或引用的靜態(tài)類(lèi)型(父類(lèi)類(lèi)型)找到對(duì)應(yīng)的虛函數(shù)表。
- 然后根據(jù)指針或引用的動(dòng)態(tài)類(lèi)型(實(shí)際指向的子類(lèi)對(duì)象類(lèi)型)在虛函數(shù)表中查找對(duì)應(yīng)的虛函數(shù)。
- 最后調(diào)用找到的虛函數(shù)。
通過(guò)動(dòng)態(tài)綁定,即使使用父類(lèi)的指針或引用,也能夠在運(yùn)行時(shí)調(diào)用到子類(lèi)的實(shí)現(xiàn),實(shí)現(xiàn)了多態(tài)的特性。
需要注意的是,只有通過(guò)指針或引用調(diào)用虛函數(shù)才會(huì)觸發(fā)動(dòng)態(tài)綁定,直接通過(guò)對(duì)象調(diào)用虛函數(shù)會(huì)導(dǎo)致靜態(tài)綁定,即根據(jù)對(duì)象的靜態(tài)類(lèi)型確定調(diào)用的函數(shù)。
總結(jié)起來(lái),多態(tài)的原理是通過(guò)虛函數(shù)和動(dòng)態(tài)綁定機(jī)制實(shí)現(xiàn)的。虛函數(shù)表存儲(chǔ)了每個(gè)虛函數(shù)的地址,通過(guò)指針或引用的動(dòng)態(tài)類(lèi)型在虛函數(shù)表中查找對(duì)應(yīng)的虛函數(shù),并在運(yùn)行時(shí)調(diào)用正確的函數(shù)實(shí)現(xiàn)。這使得通過(guò)父類(lèi)的指針或引用可以實(shí)現(xiàn)對(duì)不同子類(lèi)對(duì)象的不同行為。
3.4 多態(tài)的問(wèn)題
形成多態(tài)可能造成內(nèi)部泄漏
#include <iostream>
using namespace std;
class Animla{
public:
virtual void eat(){
cout<<"吃東西"<<endl;
}
~Animla(){
cout<<"Animal的析構(gòu)函數(shù)"<<endl;
}
};
class Cat:public Animla{
public:
void eat(){
cout<<"吃魚(yú)"<<endl;
}
~Cat(){
cout<<"Cat的析構(gòu)函數(shù)"<<endl;
}
};
class Dog:public Animla{
public:
void eat(){
cout<<"吃肉"<<endl;
}
~Dog(){
cout<<"Dog的析構(gòu)函數(shù)"<<endl;
}
};
int main()
{
Dog * d=new Dog;
delete d; //先調(diào)用dog類(lèi)的析構(gòu) 再Anima的析構(gòu)函數(shù)
Animla * a=new Cat;
delete a; //只會(huì)Animal的析構(gòu)函數(shù)
}
解決方式 :需要在基類(lèi)的析構(gòu)函數(shù)前加virtual關(guān)鍵字
#include <iostream>
using namespace std;
class Animla{
public:
virtual void eat(){
cout<<"吃東西"<<endl;
}
virtual ~Animla(){
cout<<"Animal的析構(gòu)函數(shù)"<<endl;
}
};
class Cat:public Animla{
public:
void eat(){
cout<<"吃魚(yú)"<<endl;
}
~Cat(){
cout<<"Cat的析構(gòu)函數(shù)"<<endl;
}
};
class Dog:public Animla{
public:
void eat(){
cout<<"吃肉"<<endl;
}
~Dog(){
cout<<"Dog的析構(gòu)函數(shù)"<<endl;
}
};
int main()
{
Dog * d=new Dog;
delete d;
Animla * a=new Cat;
delete a;
}
4. 抽象類(lèi)
抽象類(lèi)(Abstract Class)是一種特殊的類(lèi),它不能被實(shí)例化,只能作為其他類(lèi)的基類(lèi)來(lái)使用。抽象類(lèi)用于定義一組相關(guān)的接口和行為,但無(wú)法直接創(chuàng)建對(duì)象。
在C++中,通過(guò)在類(lèi)中聲明純虛函數(shù)(Pure Virtual Function)來(lái)定義抽象類(lèi)。純虛函數(shù)是一種在基類(lèi)中聲明但沒(méi)有具體實(shí)現(xiàn)的虛函數(shù),通過(guò)在函數(shù)聲明的末尾使用 “= 0” 來(lái)表示。
抽象類(lèi)的主要目的是為了提供一個(gè)通用的接口,而具體的實(shí)現(xiàn)則由派生類(lèi)來(lái)完成。派生類(lèi)必須實(shí)現(xiàn)基類(lèi)中的純虛函數(shù),否則它也會(huì)成為一個(gè)抽象類(lèi)。通過(guò)這種方式,抽象類(lèi)可以起到一種約束和規(guī)范的作用。
下面是一個(gè)示例代碼來(lái)說(shuō)明抽象類(lèi)的定義和使用:
class Animal {
public:
virtual void sound() = 0; // 純虛函數(shù),定義了抽象類(lèi)Animal的接口
};
class Dog : public Animal {
public:
void sound() override {
cout << "Woof!" << endl; // 實(shí)現(xiàn)了抽象類(lèi)Animal的純虛函數(shù)
}
};
class Cat : public Animal {
public:
void sound() override {
cout << "Meow!" << endl; // 實(shí)現(xiàn)了抽象類(lèi)Animal的純虛函數(shù)
}
};
int main() {
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
animal1->sound(); // 輸出: Woof!
animal2->sound(); // 輸出: Meow!
delete animal1;
delete animal2;
return 0;
}
在上面的示例中,Animal
是一個(gè)抽象類(lèi),它聲明了純虛函數(shù) sound()
,用于定義動(dòng)物的聲音。Dog
和 Cat
類(lèi)是 Animal
類(lèi)的派生類(lèi),它們必須實(shí)現(xiàn) sound()
函數(shù),否則也會(huì)成為抽象類(lèi)。
在 main()
函數(shù)中,通過(guò) Animal
類(lèi)的指針創(chuàng)建了 Dog
和 Cat
對(duì)象,并調(diào)用了 sound()
函數(shù)。由于 sound()
函數(shù)是虛函數(shù),并且在派生類(lèi)中進(jìn)行了實(shí)現(xiàn),所以在運(yùn)行時(shí)會(huì)根據(jù)對(duì)象的實(shí)際類(lèi)型調(diào)用相應(yīng)的函數(shù)實(shí)現(xiàn)。
需要注意的是,抽象類(lèi)不能被實(shí)例化,因此不能創(chuàng)建抽象類(lèi)的對(duì)象。但可以通過(guò)抽象類(lèi)的指針或引用來(lái)調(diào)用派生類(lèi)的實(shí)現(xiàn)。抽象類(lèi)提供了一種統(tǒng)一的接口,可以方便地?cái)U(kuò)展和實(shí)現(xiàn)多態(tài)的特性。
例
只是一個(gè)抽象的概念,目的是給派生類(lèi)寫(xiě)一個(gè)框架。抽象類(lèi)不能實(shí)例化對(duì)象,抽象類(lèi)不能做函數(shù)參數(shù)和函數(shù)返回值。
一個(gè)類(lèi)中有純虛函數(shù)是就是抽象類(lèi)
抽象類(lèi)中一定有純虛函數(shù)函數(shù)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-481702.html
#include <iostream>
using namespace std;
class Shape{
public:
virtual void area()=0;
virtual void perimeter()=0;
};
int main()
{
Shape s; //錯(cuò)誤 抽象類(lèi)不能實(shí)例化對(duì)象
}
如果繼承抽象類(lèi),需要把抽象類(lèi)中純虛方法全部實(shí)現(xiàn),如果沒(méi)有全部實(shí)現(xiàn),該派生類(lèi)還是抽象類(lèi),不能實(shí)例化對(duì)象文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-481702.html
#include <iostream>
using namespace std;
class Shape{
public:
virtual void area()=0;
virtual void perimeter()=0;
};
class Rectangle:public Shape{
public:
void area(){
cout<<"長(zhǎng)方形的面積"<<endl;
}
void perimeter(){
cout<<"長(zhǎng)方形的周長(zhǎng)"<<endl;
}
};
int main()
{
Rectangle s;
s.area();
s.perimeter();
}
練
#include <iostream>
using namespace std;
//Role 派生出soldier ADC AP
//寫(xiě)一個(gè)公共接口test() 不同的的對(duì)象可以作為參數(shù)傳到test()接口
//不同類(lèi)型對(duì)象執(zhí)行attack()攻擊方法的實(shí)現(xiàn)是不同的
class Role{
public:
virtual void attack(){
cout<<"角色進(jìn)行攻擊"<<endl;
}
};
class soldier:public Role{
void attack(){
cout<<"戰(zhàn)士進(jìn)行攻擊"<<endl;
}
};
class ADC:public Role{
void attack(){
cout<<"ADC進(jìn)行攻擊"<<endl;
}
};
class AP:public Role{
void attack(){
cout<<"AP進(jìn)行攻擊"<<endl;
}
};
void test(Role & r){
r.attack();
}
void test2(Role * r){
r->attack();
}
int main()
{
ADC adc;
soldier s;
AP ap;
test(adc);
test(s);
test(ap);
ADC * adc2=new ADC; //基類(lèi)的指針指向派生類(lèi)對(duì)象
test2(adc2);
}
到了這里,關(guān)于C++ 面向?qū)ο蠛诵?繼承、權(quán)限、多態(tài)、抽象類(lèi))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!