柚子快報邀請碼778899分享:C++——繼承
柚子快報邀請碼778899分享:C++——繼承
目錄
引言
繼承的概念和定義
1.繼承的概念
2.繼承的定義
2.1 繼承的語法形式
2.2 繼承中類的叫法
2.3?繼承后的子類成員訪問權(quán)限
基類與派生類的賦值轉(zhuǎn)換
1.派生類對象賦值給基類對象
2.派生類對象的引用賦值給基類對象
3.派生類對象的指針賦值給基類對象
4.基類指針賦值給派生類指針
繼承的作用域
1.同名變量
2.同名函數(shù)
派生類的默認成員函數(shù)
1.對象的構(gòu)造和析構(gòu)遵循特定的順序
2.派生類構(gòu)造函數(shù)調(diào)用基類構(gòu)造函數(shù)
3.析構(gòu)函數(shù)的特殊處理
4.拷貝構(gòu)造與賦值重載必須調(diào)用基類的拷貝構(gòu)造與賦值重載完成對基類的初始化
繼承與友元
繼承與靜態(tài)成員
菱形繼承與虛擬繼承
1.菱形繼承
1.1 單繼承
2.多繼承
3.菱形繼承
2.虛擬繼承
結(jié)束語
引言
C++是一門功能強大的面向?qū)ο缶幊陶Z言,其中繼承是其面向?qū)ο筇匦缘闹匾M成部分。今天我們就來學(xué)習(xí)一下,C++中的繼承這一重要內(nèi)容。
繼承的概念和定義
1.繼承的概念
繼承(inheritance)機制是面向?qū)ο蟪绦蛟O(shè)計使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進行擴展,增加功能,這樣產(chǎn)生新的類,稱派生類。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計的層次結(jié)構(gòu),體現(xiàn)了由簡單到復(fù)雜的認知過程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,繼承是類設(shè)計層次的復(fù)用。
舉個簡單的例子:
比如說一個人,他具有年齡,姓名等個人信息,然后我們可以將這些信息整合為一個Person類,如果說我們還想要定義一個Student的類,這些學(xué)生當然也是人,因此我們可以復(fù)用Person這個類,然后再添加一些其他的信息,例如學(xué)號之類的。
2.繼承的定義
2.1 繼承的語法形式
繼承的定義是通過在子類的聲明中使用基類名,并加上冒號(:)和關(guān)鍵字public(或其他訪問修飾符,如protected或private)來實現(xiàn)的。例如:
class BaseClass
{
// 基類的成員
};
class DerivedClass : public BaseClass // 這里的public可以替換成protected或者private
{
// 子類的成員
};
下面是一個簡單的使用例子:
class Person
{
public:
void Print()
{
cout << _name << endl;
cout << _age << endl;
cout << _height << endl;
}
private:
string _name = "Kana"; //姓名
int _age = 18; //年齡
double _height = 1.50; //身高
};
class Student :public Person
{
private:
int _id = 233333; //學(xué)號
int _grade = 10; //年級
};
可以通過監(jiān)視窗口來看看:
顯然,我們可以看到 Student類 繼承了 Person類 的成員與函數(shù)。
2.2 繼承中類的叫法
(1)子類(或派生類):這是指繼承其他類(即父類)的類。子類可以使用父類的所有非私有屬性和方法,同時也可以添加自己的屬性和方法或重寫父類的方法。
(2)父類(或基類):這是指被其他類(即子類)繼承的類。父類提供了通用的屬性和方法,這些可以被子類繼承和使用。
2.3?繼承后的子類成員訪問權(quán)限
不同的繼承方式產(chǎn)生的繼承效果自然也不一樣。
如下表所示:
類成員/繼承方式public繼承protected繼承private繼承基類的public成員派生類的public成員派生類的protected成員派生類的private成員基類的protected成員派生類的protected成員派生類的protected成員派生類的private成員基類的private成員在派生類中不可見在派生類中不可見在派生類中不可見
1. 基類private成員在派生類中無論以什么方式繼承都是不可見的。這里的不可見是指基類的私有成員還是被繼承到了派生類對象中,但是語法上限制派生類對象不管在類里面還是類外面 都不能去訪問它。
2. 基類private成員在派生類中是不能被訪問,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就定義為protected??梢钥闯霰Wo成員限定符是因繼承才出現(xiàn)的。
3. 實際上面的表格我們進行一下總結(jié)會發(fā)現(xiàn),基類的私有成員在子類都是不可見?;惖钠渌蓡T在子類的訪問方式 == Min(成員在基類的訪問限定符,繼承方式),public > protected > private。
4. 使用關(guān)鍵字class時默認的繼承方式是private,使用struct時默認的繼承方式是public,不過 最好顯示的寫出繼承方式。
5. 在實際運用中一般使用都是public繼承,幾乎很少使用protetced/private繼承,也不提倡使用protetced/private繼承,因為protetced/private繼承下來的成員都只能在派生類的類里面使用,實際中擴展維護性不強。
基類與派生類的賦值轉(zhuǎn)換
在面向?qū)ο缶幊讨校惡团缮愔g的賦值轉(zhuǎn)換涉及到對象的類型兼容性和多態(tài)性。
1.派生類對象賦值給基類對象
派生類對象可以賦值給基類的對象。這里有個形象的說法叫切片或者切割。寓意把派生類中父類那部分切來賦值過去。
相反,基類成員無法賦值給派生類成員,因為有些成員派生類有,而基類沒有。
例如這樣:
class Person
{
public:
Person(string name = "Kana", int age = 18, double height = 1.50):
_name(name),
_age(age),
_height(height)
{
// ...
}
void Print() const
{
cout << _name << endl;
cout << _age << endl;
cout << _height << endl;
}
private:
string _name; // 姓名
int _age; // 年齡
double _height; // 身高
};
class Student : public Person
{
public:
Student(string name = "Kana", int age = 18, double height = 1.50, int id = 233333, int grade = 10)
: Person(name, age, height), _id(id), _grade(grade) {}
void Print() const
{
// 首先調(diào)用基類的 Print 方法
Person::Print();
// 然后打印學(xué)生特有的屬性
cout << "ID: " << _id << endl;
cout << "Grade: " << _grade << endl;
}
private:
int _id; // 學(xué)號
int _grade; // 年級
};
int main()
{
Student s;
s.Print();
// 嘗試將 Student 對象切片為 Person 對象(不推薦,因為會丟失信息)
Person p = s;
p.Print(); // 僅打印 Person 的信息(姓名、年齡、身高)
return 0;
}
輸出結(jié)果為:
2.派生類對象的引用賦值給基類對象
我們可以將一個派生類對象的引用賦值給一個基類類型的引用,而不需要const修飾符。
class Person
{
public:
void Print()
{
cout << "Person Print()" << endl;
}
};
class Student :public Person
{
public:
void Print()
{
cout << "Student Print()" << endl;
}
};
int main()
{
Student s;
Person& p = s;
p.Print();
return 0;
}
輸出結(jié)果為:
3.派生類對象的指針賦值給基類對象
派生類對象的指針可以賦值給基類對象的指針。
class Person
{
public:
void Print() const
{
cout << "Person: Name = " << _name << ", Age = " << _age << endl;
}
Person(string name, int age) : _name(name), _age(age) {}
private:
string _name;
int _age;
};
class Student : public Person
{
public:
void Print() const
{
cout << "Student: ID = " << _id << ", Grade = " << _grade << endl;
}
Student(string name, int age, int id, int grade) : Person(name, age), _id(id), _grade(grade) {}
private:
int _id; // 學(xué)號
int _grade; // 年級
};
int main()
{
Student student("Kana", 18, 12345, 10);
// 將Student對象的指針賦值給Person對象的指針
Person* p = &student;
p->Print();
// 如果要訪問Student特有的成員,需要使用Student類型的指針或引用
Student* s = &student;
s->Print();
return 0;
}
輸出結(jié)果為:
4.基類指針賦值給派生類指針
在C++中,將基類指針直接強制轉(zhuǎn)換為派生類指針是一種危險的做法,通常是不被推薦的,因為它違反了類型安全的原則,并且可能導(dǎo)致未定義行為,包括越界訪問或訪問無效內(nèi)存。
Person p;
Student *s = (Student*) & p; // right
總結(jié):
1.派生類對象可以賦值給 基類的對象 / 基類的指針 / 基類的引用。這里有個形象的說法叫切片或者切割。寓意把派生類中父類那部分切來賦值過去。
2.基類對象不能賦值給派生類對象。
3.基類的指針或者引用可以通過強制類型轉(zhuǎn)換賦值給派生類的指針或者引用。但是必須是基類的指針是指向派生類對象時才是安全的。
繼承的作用域
繼承的作用域決定了從基類繼承到派生類的成員(包括變量和方法)的訪問權(quán)限。
在C++的繼承體系中,基類和派生類各自擁有獨立的作用域。當派生類和基類中定義了同名的成員(無論是變量還是函數(shù)),派生類中的成員會“隱藏”或“重定義”基類中的同名成員。這意味著在派生類的作用域內(nèi),直接訪問該同名成員將引用派生類的成員,而不是基類的成員。
1.同名變量
來看看這段代碼:基類和派生類都有_height這個變量
class Person
{
protected:
string _name = "Kana";
int _age = 18;
double _height = 1.50;
};
class Student :public Person
{
public:
void Print()
{
cout << _name << endl;
cout << _age << endl;
cout << _height << endl;
}
private:
double _height = 1.70;
int _id = 123456;
int _grade = 10;
};
int main()
{
Student s;
s.Print();
return 0;
}
輸出結(jié)果為:
如果想要打印基類中的_height,則需要使用 :: 限定符:
void Print()
{
cout << _name << endl;
cout << _age << endl;
cout << Person::_height << endl;
}
輸出結(jié)果為:
2.同名函數(shù)
如果基類和派生類存在同名函數(shù),會發(fā)生什么呢?
來看看這段代碼:
class A
{
public:
void fun()
{
cout << "fun()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "fun(int i)->" << i << endl;
}
};
int main()
{
B b;
b.fun(1);
};
輸出結(jié)果為:
B 中的 fun 和 A 中的 fun 構(gòu)成隱藏,成員函數(shù)滿足函數(shù)名相同就構(gòu)成隱藏。
由于函數(shù)重載針對的是同一個作用域的函數(shù),而基類與派生類直接作用域不同。因此不是函數(shù)重載。
同樣的,如果需要訪問其他作用域的函數(shù),我們需要使用 :: 操作符:
B b;
b.A::fun(); // 訪問A中的fun函數(shù)
輸出結(jié)果為:
派生類的默認成員函數(shù)
我們知道:在類中有6個默認成員函數(shù),如果不顯示定義,編譯會自動生成。
那么在派生類中,這些成員函數(shù)如何生成?我們來學(xué)習(xí)一下:
1.對象的構(gòu)造和析構(gòu)遵循特定的順序
對象的構(gòu)造和析構(gòu)遵循特定的順序,以確保對象的正確初始化和清理
構(gòu)造函數(shù)調(diào)用順序:
(1)創(chuàng)建派生類對象時,從最頂層的基類開始,逐層向下調(diào)用構(gòu)造函數(shù),直到派生類。
(2)接著,按照派生類中成員變量的聲明順序初始化成員變量(若成員是對象,則調(diào)用其構(gòu)造函數(shù))。
(3)最后,執(zhí)行派生類構(gòu)造函數(shù)體中的代碼。
析構(gòu)函數(shù)調(diào)用順序:
(1)銷毀派生類對象時,首先調(diào)用派生類的析構(gòu)函數(shù)。
(2)然后,按照成員變量聲明的逆序調(diào)用成員變量的析構(gòu)函數(shù)(若成員是對象)。
(3)最后,從最頂層的基類開始,逐層向上調(diào)用析構(gòu)函數(shù),直到派生類的基類。
來看看這個示例:
class Person
{
public:
Person(string name = "Kana")
: _name(name)
{
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
private:
string _name; // 姓名
};
class Student : public Person
{
public:
Student()
{
cout << "Student()" << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
private:
int _id;
};
int main()
{
Student s;
return 0;
}
輸出結(jié)果為:
2.派生類構(gòu)造函數(shù)調(diào)用基類構(gòu)造函數(shù)
(1)派生類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)來初始化基類的成員。
(2)如果基類沒有默認的構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表中顯式調(diào)用基類的一個構(gòu)造函數(shù)。
class Person
{
public:
// Person類的構(gòu)造函數(shù),用于初始化名字
Person(const char* name) :
_name(name)
{
// ...
}
// Person類的拷貝構(gòu)造函數(shù)
Person(const Person& p) :
_name(p._name)
{
}
private:
string _name;
};
class Student : public Person
{
public:
// Student類的構(gòu)造函數(shù),接收學(xué)號和名字
Student(int id, const char* name) :
_id(id),
Person(name)
{
// ...
}
// Student類的默認構(gòu)造函數(shù)
Student() :
Person("Default Student Name"),
_id(0)
{
// ...
}
private:
int _id;
};
3.析構(gòu)函數(shù)的特殊處理
因為后續(xù)一些場景析構(gòu)函數(shù)需要構(gòu)成重寫,重寫的條件之一是函數(shù)名相同。那么編譯器會對析構(gòu)函數(shù)名進行特殊處理,處理成destructor()。
如下所示:
class Person
{
public:
Person(const char* name)
: _name(name)
{
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
private:
string _name;
};
class Student : public Person
{
public:
Student(int num, const char* name)
: _num(num), Person(name)
{
cout << "Student()" << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
private:
int _num;
};
輸出結(jié)果為:
4.拷貝構(gòu)造與賦值重載必須調(diào)用基類的拷貝構(gòu)造與賦值重載完成對基類的初始化
在派生類中實現(xiàn)拷貝構(gòu)造函數(shù)和賦值操作符重載時,需要確保調(diào)用基類的相應(yīng)函數(shù)來完成基類部分的拷貝或賦值操作。
來看個簡單的例子:
class Person
{
public:
// 默認構(gòu)造函數(shù)
Person(const string& name) :
_name(name)
{
// ...
}
// 拷貝構(gòu)造函數(shù)
Person(const Person& p) :
_name(p._name)
{
cout << "Copy Person(" << _name << ")" << endl;
}
// 賦值操作符重載
Person& operator=(const Person& p)
{
if (this != &p)
{
_name = p._name;
cout << "Assign Person(" << _name << ")" << endl;
}
return *this;
}
// 析構(gòu)函數(shù)
~Person()
{
cout << "~Person(" << _name << ")" << endl;
}
string _name;
};
class Student : public Person
{
public:
// 構(gòu)造函數(shù)
Student(int num, const string& name) : Person(name), _num(num) {
cout << "Student(" << _num << ", " << _name << ")" << endl;
}
// 拷貝構(gòu)造函數(shù)
Student(const Student& s) : Person(s), _num(s._num) {
cout << "Copy Student(" << _num << ", " << _name << ")" << endl;
}
// 賦值操作符重載
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s); // 調(diào)用基類的賦值操作符
_num = s._num;
}
return *this;
}
// 析構(gòu)函數(shù)
~Student()
{
cout << "~Student(" << _num << ", " << _name << ")" << endl;
}
int _num;
};
繼承與友元
友元關(guān)系不能繼承。友元關(guān)系(friendship)是一種單向的、非傳遞性的關(guān)系,它不能被繼承。這意味著,如果一個類是另一個類的友元,那么這個友元類可以訪問那個類的私有(private)和保護(protected)成員。但是,這種訪問權(quán)限不會延伸到該類的子類。
class Student;//聲明
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 學(xué)號
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
報錯信息為:
繼承與靜態(tài)成員
基類定義了static靜態(tài)成員,則整個繼承體系里面只有一個這樣的成員。無論派生出多少個子類,都只有一個static成員實例。
來看看這段代碼:
class Person
{
public:
Person()
{
++_count; // 每次創(chuàng)建對象時增加計數(shù)
}
protected:
string _name; // 姓名
public:
static int _count;
};
int Person::_count = 0; // 初始化靜態(tài)成員變量
class Student : public Person
{
protected:
int _stuNum = 0; // 學(xué)號
};
class Graduate : public Student
{
protected:
string _seminarCourse; // 研究科目
};
int main()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout << "人數(shù): " << Person::_count << endl;
// 重置計數(shù)
Person::_count = 0;
cout << "人數(shù): " << Person::_count << endl;
return 0;
}
輸出結(jié)果為:
菱形繼承與虛擬繼承
1.菱形繼承
1.1 單繼承
一個子類只有一個直接父類的繼承關(guān)系為單繼承。
2.多繼承
一個子類有兩個或以上直接父類時稱這個繼承關(guān)系為多繼承。
3.菱形繼承
菱形繼承是多繼承的一種特殊情況。
菱形繼承的問題:從下面的對象成員模型構(gòu)造,可以看出菱形繼承有數(shù)據(jù)冗余和二義性的問題。
來看看這段代碼:
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //學(xué)號
};
class Teacher : public Person
{
protected:
int _id; // 職工編號
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修課程
};
int main()
{
Assistant a;
// a._name = "peter"; 這樣會產(chǎn)生二義性無法明確知道訪問的是哪一個類
// 需要顯示指定訪問哪個父類的成員可以解決二義性問題,但是數(shù)據(jù)冗余問題無法解決
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
return 0;
}
2.虛擬繼承
虛擬繼承可以解決菱形繼承的二義性和數(shù)據(jù)冗余的問題。如上面的繼承關(guān)系,在Student和 Teacher的繼承Person時使用虛擬繼承,即可解決問題。需要注意的是,虛擬繼承不要在其他地方去使用。
像這樣:
class Person
{
public:
string _name; // 姓名
};
//虛繼承
class Student : virtual public Person
{
protected:
int _num; //學(xué)號
};
//虛繼承
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;
}
輸出結(jié)果為:
結(jié)束語
假期太擺了,導(dǎo)致寫得太慢了。。。
求點贊收藏評論關(guān)注!?。?/p>
感謝各位大佬的支持?。?!
柚子快報邀請碼778899分享:C++——繼承
相關(guān)閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。