最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文徹底讀懂C++的繼承機(jī)制

 更新時(shí)間:2025年09月29日 10:26:23   作者:帶魚(yú)吃貓  
在C++中繼承是通過(guò)指定繼承方式來(lái)實(shí)現(xiàn)的,這篇文章主要介紹了C++繼承機(jī)制的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

一、繼承的概念及定義

1.1 繼承的概念

在沒(méi)有接觸繼承之前我們要設(shè)計(jì)兩個(gè)類(lèi)Student和Teacher,Student和Teacher都有姓名/地址/電話/年齡等成員變量,都有identity身份認(rèn)證的成員函數(shù),設(shè)計(jì)到兩個(gè)類(lèi)里面就是冗余的。當(dāng)然他們也有一些不同的成員變量和函數(shù),比如老師獨(dú)有成員變量是職稱(chēng),學(xué)生的獨(dú)有成員變量是學(xué)號(hào);學(xué)生生獨(dú)有的成員函數(shù)是學(xué)習(xí),老師獨(dú)有的成員函數(shù)是教授。

class Student
{
public:
	void Study()
	{
		//學(xué)習(xí)
	}
	void identity()
	{
		//身份認(rèn)證
	}
private:
	string _name;      //姓名
	string _address;   //地址
	string _tel;       //電話
	int _age;          //年齡

	int _stuid;        //學(xué)號(hào)
};

class Teacher
{
public:
	void teach()
	{
		//教授
	}
	void identity()
	{
		//身份認(rèn)證
	}
private:
	string _name;      //姓名
	string _address;   //地址
	string _tel;       //電話
	int _age;          //年齡

	int _title;        //職工號(hào)
};

利用繼承的方法我們可以將兩個(gè)類(lèi)中的公共部分提取出來(lái)封裝成單獨(dú)一個(gè)類(lèi)Person,再使Teacher/Student分別繼承Person。這樣Teacher/Student中既有自己特有的成員變量(函數(shù))也有公共的成員變量(函數(shù)),大大避免了代碼的冗余。

#include<string.h>
class Person
{
public:
	void identity()
	{
		//身份認(rèn)證
	}
	string _name;      //姓名
	string _address;   //地址
	string _tel;       //電話
	int _age;          //年齡
};

class Student:public Person  //Stuedent繼承Person類(lèi)
{
public:
	void Study()
	{
		//學(xué)習(xí)
	}
private:
	int _stuid;     //學(xué)號(hào)
};

class Teacher:public Person  //Teacher繼承Person類(lèi)
{
public:
	void teach()
	{
		//教授
	}
private:
	int _title;   //職工號(hào)
};

繼承機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許我們?cè)诒3衷蓄?lèi)特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加方法(成員函數(shù))和屬性(成員變量),這樣產(chǎn)生新的類(lèi)叫做派生類(lèi)。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到到復(fù)雜的認(rèn)知過(guò)程。以前我們接觸的都是函數(shù)層面的復(fù)用,繼承是類(lèi)設(shè)計(jì)層次的復(fù)用。                                                             

1.2 繼承的定義

1.2.1 定義格式

下面Person是基類(lèi),也稱(chēng)作父類(lèi);Student是派生類(lèi),也稱(chēng)作子類(lèi)。

1.2.2 繼承基類(lèi)成員訪問(wèn)方式的變化

基類(lèi)private成員在派生類(lèi)中無(wú)論以什么方式繼承都是不可見(jiàn)的。這里的不可見(jiàn)是指基類(lèi)的私有成員還是被繼承到了派生類(lèi)對(duì)象中,但是語(yǔ)法上限制派生類(lèi)對(duì)象不管在類(lèi)里面還是類(lèi)外面都不能去訪問(wèn)它。

基類(lèi)private成員在派生類(lèi)中不能訪問(wèn),如果基類(lèi)不想在類(lèi)外直接訪問(wèn),但還需要在派生類(lèi)中可以訪問(wèn),就定義成protected。可以看出保護(hù)成員限定符是因繼承才出現(xiàn)的。

實(shí)際上面的表格我們總結(jié)一下就會(huì)發(fā)現(xiàn),基類(lèi)的私有成員在派生類(lèi)中都是不可見(jiàn)的?;?lèi)的其他成員在派生類(lèi)中的訪問(wèn)方式==Min(成員在基類(lèi)的訪問(wèn)限定符,繼承方式)public>protected>private。

使用關(guān)鍵字class時(shí)默認(rèn)的繼承方式是private,使用struct時(shí)默認(rèn)的繼承方式是public,不過(guò)最好顯式地寫(xiě)出繼承方式。

在實(shí)際運(yùn)用中一般使用的都是public繼承,幾乎很少使用protected/private繼承,也不提倡使用protected/private繼承,因?yàn)閜rotected/private繼承下來(lái)的成員都只能在派生類(lèi)中使用,實(shí)際中擴(kuò)展維護(hù)性不強(qiáng)。

1.3 繼承類(lèi)模板

在 C++ 中,類(lèi)模板可以被繼承,這種方式稱(chēng)為 "繼承類(lèi)模板"。通過(guò)繼承類(lèi)模板,我們可以創(chuàng)建更具體或更特殊化的類(lèi),同時(shí)復(fù)用模板的通用邏輯。

繼承類(lèi)模板主要有兩種場(chǎng)景:

  1. 普通類(lèi)繼承類(lèi)模板的實(shí)例化版本
  2. 類(lèi)模板繼承另一個(gè)類(lèi)模板

1.普通類(lèi)繼承類(lèi)模板的實(shí)例化版本

當(dāng)一個(gè)普通類(lèi)繼承自一個(gè)已經(jīng)實(shí)例化的類(lèi)模板時(shí),需要指定模板參數(shù):

// 定義一個(gè)類(lèi)模板
template <typename T>
class Container {
protected:
    T data;
public:
    Container(T val) : data(val) {}
    T get() const { return data; }
    void set(T val) { data = val; }
};

// 普通類(lèi)繼承實(shí)例化的類(lèi)模板
class IntContainer : public Container<int> {
public:
    IntContainer(int val) : Container<int>(val) {}
    
    // 新增方法
    void increment() { data++; }
};

2.類(lèi)模板繼承另一個(gè)類(lèi)模板

類(lèi)模板可以繼承另一個(gè)類(lèi)模板,此時(shí)可以使用自身的模板參數(shù)作為基類(lèi)模板的參數(shù):

#include <iostream>

// 基類(lèi)模板
template <typename T>
class Base {
protected:
    T value;
public:
    Base(T v) : value(v) {}
    void print() const {
        std::cout << "Base value: " << value << std::endl;
    }
};

// 派生類(lèi)模板繼承基類(lèi)模板
template <typename T, typename U>
class Derived : public Base<T> {
private:
    U extra;
public:
    // 注意初始化列表中需要顯式指定基類(lèi)模板
    Derived(T v, U e) : Base<T>(v), extra(e) {}
    
    void show() const {
        // 訪問(wèn)基類(lèi)成員時(shí),可能需要使用this指針或Base<T>::限定
        std::cout << "Derived value: " << this->value 
                  << ", Extra: " << extra << std::endl;
    }
};

int main() {
    Derived<int, std::string> obj(42, "example");
    obj.print();  // 調(diào)用基類(lèi)方法
    obj.show();   // 調(diào)用派生類(lèi)方法
    
    return 0;
}

注意事項(xiàng):

  1. 訪問(wèn)基類(lèi)成員:在派生類(lèi)模板中訪問(wèn)基類(lèi)模板的成員時(shí),可能需要使用this->指針或Base<T>::限定符,幫助編譯器識(shí)別成員。

  2. 模板參數(shù)傳遞:派生類(lèi)模板可以將自身的模板參數(shù)傳遞給基類(lèi)模板,也可以使用固定類(lèi)型:

template <typename T>
class Derived : public Base<double> {  // 固定使用double類(lèi)型
    // ...
};

二、基類(lèi)和派生類(lèi)間的轉(zhuǎn)化

在 C++ 中,public 繼承的派生類(lèi)對(duì)象與基類(lèi)之間存在三種常見(jiàn)的賦值 / 關(guān)聯(lián)方式:派生類(lèi)對(duì)象賦值給基類(lèi)指針、派生類(lèi)對(duì)象賦值給基類(lèi)引用、派生類(lèi)對(duì)象直接賦值給基類(lèi)對(duì)象。這三種方式的本質(zhì)和效果有顯著區(qū)別,核心在于是否保留派生類(lèi)的特性以及是否觸發(fā)多態(tài)行為。

1.派生類(lèi)對(duì)象賦值給基類(lèi)指針(Base* ptr = &derivedObj;)

  • 本質(zhì):基類(lèi)指針指向派生類(lèi)對(duì)象的基類(lèi)部分(但指針可以間接訪問(wèn)派生類(lèi)的完整信息,配合虛函數(shù)實(shí)現(xiàn)多態(tài))。
  • 特性:
    • 指針的靜態(tài)類(lèi)型是Base*,但動(dòng)態(tài)類(lèi)型是Derived*(指向?qū)ο蟮膶?shí)際類(lèi)型)。
    • 可以通過(guò)指針調(diào)用基類(lèi)的成員(包括虛函數(shù)),若調(diào)用虛函數(shù),會(huì)根據(jù)動(dòng)態(tài)類(lèi)型觸發(fā)動(dòng)態(tài)綁定(調(diào)用派生類(lèi)的重寫(xiě)版本)。
    • 不能直接訪問(wèn)派生類(lèi)新增的成員(需顯式類(lèi)型轉(zhuǎn)換,如dynamic_cast)。
class Base {
public:
    virtual void func() { cout << "Base::func()" << endl; }
};
class Derived : public Base {
public:
    void func() override { cout << "Derived::func()" << endl; }
    void derivedFunc() { cout << "Derived獨(dú)有的函數(shù)" << endl; }
};

int main() {
    Derived d;
    Base* ptr = &d; // 基類(lèi)指針指向派生類(lèi)對(duì)象
    ptr->func();    // 調(diào)用Derived::func()(動(dòng)態(tài)綁定)
    // ptr->derivedFunc(); // 錯(cuò)誤:不能直接訪問(wèn)派生類(lèi)新增成員
    return 0;
}

這里需要注意的是:基類(lèi)對(duì)象不能賦值給派生類(lèi)對(duì)象。

2. 派生類(lèi)對(duì)象賦值給基類(lèi)引用(Base& ref = derivedObj;)

  • 本質(zhì):基類(lèi)引用綁定到派生類(lèi)對(duì)象的基類(lèi)部分(引用是對(duì)象的別名,不產(chǎn)生新對(duì)象)。
  • 特性:
    • 引用的靜態(tài)類(lèi)型是Base&,動(dòng)態(tài)類(lèi)型是Derived&
    • 行為與基類(lèi)指針類(lèi)似:可調(diào)用基類(lèi)成員,調(diào)用虛函數(shù)時(shí)觸發(fā)動(dòng)態(tài)綁定。
    • 不能直接訪問(wèn)派生類(lèi)新增成員(需顯式轉(zhuǎn)換)。
int main() {
    Derived d;
    Base& ref = d; // 基類(lèi)引用綁定派生類(lèi)對(duì)象
    ref.func();    // 調(diào)用Derived::func()(動(dòng)態(tài)綁定)
    return 0;
}

3. 派生類(lèi)對(duì)象直接賦值給基類(lèi)對(duì)象(Base baseObj = derivedObj;)

  • 本質(zhì):發(fā)生對(duì)象切片(Object Slicing)—— 僅將派生類(lèi)對(duì)象中的基類(lèi)部分復(fù)制到基類(lèi)對(duì)象中,派生類(lèi)獨(dú)有的成員被 "切片" 丟棄。
  • 特性:
    • 生成一個(gè)全新的基類(lèi)對(duì)象(與原派生類(lèi)對(duì)象無(wú)關(guān))。
    • 該對(duì)象的靜態(tài)類(lèi)型和動(dòng)態(tài)類(lèi)型都是Base,不觸發(fā)多態(tài)(調(diào)用函數(shù)時(shí)僅使用基類(lèi)的實(shí)現(xiàn))。
    • 無(wú)法訪問(wèn)派生類(lèi)的任何特性(即使顯式轉(zhuǎn)換也不行,因?yàn)榕缮?lèi)部分已丟失)。
int main() {
    Derived d;
    Base baseObj = d; // 對(duì)象切片:僅復(fù)制基類(lèi)部分
    baseObj.func();   // 調(diào)用Base::func()(無(wú)動(dòng)態(tài)綁定)
    return 0;
}

方式操作本質(zhì)是否產(chǎn)生新對(duì)象動(dòng)態(tài)類(lèi)型是否支持多態(tài)(動(dòng)態(tài)綁定)能否訪問(wèn)派生類(lèi)新增成員
派生類(lèi)→基類(lèi)指針指針指向派生類(lèi)的基類(lèi)部分派生類(lèi)類(lèi)型是(調(diào)用虛函數(shù)時(shí))否(需顯式轉(zhuǎn)換)
派生類(lèi)→基類(lèi)引用引用綁定派生類(lèi)的基類(lèi)部分派生類(lèi)類(lèi)型是(調(diào)用虛函數(shù)時(shí))否(需顯式轉(zhuǎn)換)
派生類(lèi)→基類(lèi)對(duì)象(直接賦值)復(fù)制基類(lèi)部分,切片派生部分是(生成基類(lèi)對(duì)象)基類(lèi)類(lèi)型否(已被切片丟棄)

三、隱藏機(jī)制

3.1 繼承中的隱藏機(jī)制

派生類(lèi)和基類(lèi)中有同名成員,派生類(lèi)成員將屏蔽基類(lèi)對(duì)同名成員的直接訪問(wèn),這種情況叫隱藏。(在派生類(lèi)成員函數(shù)中,要是需要訪問(wèn)基類(lèi)中同名成員可以使用基類(lèi)::基類(lèi)成員顯式訪問(wèn))

class Person
{
protected:
	int _num = 111;
	string _name = "?李?"; 
};
class Student : public Person
{
public:
	void Print()
	{
		cout <<_num<<endl;//這里打印結(jié)果為999因?yàn)镻erson中的_num被隱藏了
	}
protected:
	int _num = 999; 
};
int main()
{
	Student s1;
	s1.Print();
	return 0;
};

需要注意的是對(duì)于成員函數(shù)來(lái)講,只要函數(shù)名相同就構(gòu)成了隱藏。也就是說(shuō),如果派生類(lèi)與基類(lèi)中的成員函數(shù)名相同,通過(guò)派生類(lèi)的實(shí)例化對(duì)象調(diào)用該函數(shù)默認(rèn)調(diào)用派生類(lèi)中定義的重名函數(shù),需要調(diào)用基類(lèi)中的重名函數(shù)時(shí)需要指定類(lèi)名:

class Person
{
public:
	void Print()
	{
		cout << "我是基類(lèi)中的Print函數(shù)" << endl;
	}
};
class Student : public Person
{
public:
	void Print()
	{
		cout <<"我是派生類(lèi)中的Print函數(shù)" << endl;
	}
};
int main()
{
	Student s1;
	s1.Print();
	return 0;
};

要是想顯式調(diào)用基類(lèi)中的Print函數(shù),需要指定Person::Print();如下示例:

int main()
{
	Student s1;
	s1.Person::Print();
	return 0;
};

這也從側(cè)面說(shuō)明,對(duì)于繼承關(guān)系中的隱藏機(jī)制,基類(lèi)中的重名的成員函數(shù)或者成員變量確確實(shí)實(shí)被派生類(lèi)繼承下來(lái)了,只是由于隱藏機(jī)制默認(rèn)訪問(wèn)或調(diào)用的是派生類(lèi)中的成員變量或函數(shù),需要訪問(wèn)基類(lèi)中的時(shí)需要指明類(lèi)域。

3.2 重載與隱藏

這里我們?cè)囍胍幌胂旅婊?lèi)與派生類(lèi)func函數(shù)之間的關(guān)系:

A:重載 B:隱藏 

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

這里我們發(fā)現(xiàn)基類(lèi)和派生類(lèi)中都有一重名的func函數(shù),根據(jù)上述提到的隱藏機(jī)制的形成條件基類(lèi)的func函數(shù)與派生類(lèi)中的func函數(shù)構(gòu)成隱藏。

什么是重載?

在 C++ 中,它允許在同一個(gè)作用域內(nèi)定義多個(gè)同名的函數(shù)或運(yùn)算符,但這些同名的函數(shù)或運(yùn)算符的參數(shù)列表(參數(shù)個(gè)數(shù)、參數(shù)類(lèi)型或參數(shù)順序)必須有所不同,返回值類(lèi)型可以相同也可以不同。

根據(jù)概念我們就可以發(fā)現(xiàn)兩者的區(qū)別,重載強(qiáng)調(diào)在同一作用域也就是同一個(gè)類(lèi)中的同名函數(shù)之間構(gòu)成重載,繼承中的隱藏機(jī)制始終離不開(kāi)基類(lèi)與派生類(lèi)兩個(gè)作用域。

四、派生類(lèi)的默認(rèn)成員函數(shù)

4.1 構(gòu)造函數(shù)

派生類(lèi)的構(gòu)造函數(shù)必須調(diào)用基類(lèi)的構(gòu)造函數(shù)初始化基類(lèi)的那?部分成員。如果基類(lèi)沒(méi)有默認(rèn)的構(gòu)造 函數(shù),則必須在派生類(lèi)構(gòu)造函數(shù)的初始化列表階段顯式調(diào)用。

默認(rèn)構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù),它不需要任何參數(shù),或者所有參數(shù)都有默認(rèn)值。當(dāng)創(chuàng)建類(lèi)的對(duì)象時(shí)如果沒(méi)有提供實(shí)參,編譯器會(huì)自動(dòng)調(diào)用默認(rèn)構(gòu)造函數(shù)。

class A
{
public:
	//這里基類(lèi)沒(méi)有默認(rèn)構(gòu)造函數(shù)需要傳遞參數(shù)
	A(string _name)
	{
		name = _name;
	}

	string name;
};
class B : public A
{
public:
	//派生類(lèi)中需要顯式調(diào)用基類(lèi)構(gòu)造函數(shù)初始化基類(lèi)的成員變量
	B(int _n1,string _name)
		:A(_name)
		,n1(_n1)
	{}
	void Print()
	{
		cout << name << n1 << std::endl;
	}
private:
	int n1;
};
int main()
{
	B ss(18,"張三");
	ss.Print();
	return 0;
};

4.2 拷貝構(gòu)造函數(shù)

派生類(lèi)的拷貝構(gòu)造函數(shù)必須調(diào)用基類(lèi)的拷貝構(gòu)造完成拷貝初始化。

在一般情況下,拷貝構(gòu)造函數(shù)編譯器自動(dòng)生成的就夠用了,但是如果類(lèi)中有資源需要深拷貝時(shí)就需要我們手動(dòng)實(shí)現(xiàn)拷貝構(gòu)造函數(shù)。

當(dāng)創(chuàng)建派生類(lèi)對(duì)象時(shí),先調(diào)用基類(lèi)的構(gòu)造函數(shù)再調(diào)用派生類(lèi)的構(gòu)造函數(shù)(與析構(gòu)函數(shù)的調(diào)用順序完全相反)。

class A
{
public:
	//這里基類(lèi)沒(méi)有默認(rèn)構(gòu)造函數(shù)需要傳遞參數(shù)
	A(string _name)
	{
		name = _name;
	}

	A(const A& _a)
	{
		name = _a.name;
	}
	string name;
};
class B : public A
{
public:
	B(int _n1,string _name)
		:A(_name)
		,n1(_n1)
	{}
	B(const B& _b)
		:A(_b)//顯式調(diào)用基類(lèi)的拷貝構(gòu)造完成構(gòu)造初始化
		,n1(_b.n1)
	{}
	void Print()
	{
		cout << name << n1 << std::endl;
	}
private:
	int n1;
};
int main()
{
	B ss(18,"張三");
	B pp(ss);
	pp.Print();
	return 0;
};

4.3 operator =

派生類(lèi)的operator=必須要調(diào)用基類(lèi)的operator=完成基類(lèi)的復(fù)制。需要注意的是派生類(lèi)的 operator=隱藏了基類(lèi)的operator=,所以需要指定基類(lèi)作用域顯式調(diào)用基類(lèi)的operator=。

與前面的拷貝構(gòu)造一樣,在一般情況下,賦值運(yùn)算符重載編譯器自動(dòng)生成的就夠用了,但是如果類(lèi)中有資源需要深拷貝時(shí)就需要我們手動(dòng)實(shí)現(xiàn)賦值運(yùn)算符重載。

這里我們也來(lái)演示一下:

class A
{
public:
	//這里基類(lèi)沒(méi)有默認(rèn)構(gòu)造函數(shù)需要傳遞參數(shù)
	A(string _name)
	{
		name = _name;
	}

	A& operator=(A& _a)
	{
		if (this != &_a)
		{
			name = _a.name;
		}
		return *this;
	}
	string name;
};
class B : public A
{
public:
	B(int _n1,string _name)
		:A(_name)
		,n1(_n1)
	{}
	B& operator=(B& _b)
	{
		if (this != &_b)
		{
            //這里不能是operator(_b)因?yàn)闃?gòu)成隱藏會(huì)遞歸調(diào)用B中的operator=導(dǎo)致棧溢出
			A::operator=(_b);
			n1 = _b.n1;
		}
		return *this;
	}
	void Print()
	{
		cout << name << n1 << std::endl;
	}
private:
	int n1;
};
int main()
{
	B ss(18,"張三");
	B pp=ss;
	pp.Print();
	return 0;
};

4.4 析構(gòu)函數(shù)

當(dāng)派生類(lèi)對(duì)象銷(xiāo)毀時(shí),先調(diào)用派生類(lèi)的析構(gòu)函數(shù),再調(diào)用基類(lèi)的析構(gòu)函數(shù)(與構(gòu)造函數(shù)的調(diào)用順序相反)。

派生類(lèi)的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類(lèi)的析構(gòu)函數(shù)清理基類(lèi)成員。因?yàn)檫@樣才能保證派 生類(lèi)對(duì)象先清理派生類(lèi)成員再清理基類(lèi)成員的順序。

一般來(lái)說(shuō),編譯器自動(dòng)生成的析構(gòu)函數(shù)就已經(jīng)夠用了,如果有需要顯式釋放的資源,才需要自己實(shí)現(xiàn):

#include <iostream>
class Base {
public:
    ~Base() { std::cout << "Base destructor" << std::endl; }
};

class Derived : public Base {
public:
    ~Derived() { std::cout << "Derived destructor" << std::endl; }
};

int main() {
    Derived d;  // 銷(xiāo)毀時(shí)先調(diào)用 ~Derived(),再調(diào)用 ~Base()
    return 0;
}
// 輸出:
// Derived destructor
// Base destructor

五、繼承與友元

5.1 什么是友元

在 C++ 中,友元(Friend) 是一種特殊的訪問(wèn)權(quán)限機(jī)制,它允許一個(gè)類(lèi)或函數(shù)訪問(wèn)另一個(gè)類(lèi)中的私有(private) 和保護(hù)(protected) 成員,即使它們不屬于該類(lèi)的成員。

通常友元具有以下三種形式:

1. 友元函數(shù)

普通函數(shù)可以被聲明為某個(gè)類(lèi)的友元,從而訪問(wèn)該類(lèi)的私有成員。

class MyClass {
private:
    int secret;
public:
    MyClass(int s) : secret(s) {}
    // 聲明友元函數(shù)
    friend void printSecret(MyClass obj);
};

// 友元函數(shù)定義(無(wú)需加friend關(guān)鍵字)
void printSecret(MyClass obj) {
    // 可以直接訪問(wèn)私有成員
    cout << "Secret value: " << obj.secret << endl;
}

2. 友元類(lèi)

一個(gè)類(lèi)可以被聲明為另一個(gè)類(lèi)的友元,此時(shí)友元類(lèi)的所有成員函數(shù)都能訪問(wèn)該類(lèi)的私有成員。

class A {
private:
    int value;
public:
    A(int v) : value(v) {}
    // 聲明B為A的友元類(lèi)
    friend class B;
};

class B {
public:
    void showA(A a) {
        // B的成員函數(shù)可以訪問(wèn)A的私有成員
        cout << "A's value: " << a.value << endl;
    }
};

3. 類(lèi)的成員函數(shù)作為友元

可以將一個(gè)類(lèi)的特定成員函數(shù)聲明為另一個(gè)類(lèi)的友元(更精確的權(quán)限控制)。

class B; // 前向聲明

class A {
public:
    void showB(B b);
};

class B {
private:
    int data;
public:
    B(int d) : data(d) {}
    // 僅將A的showB函數(shù)聲明為友元
    friend void A::showB(B b);
};

// 實(shí)現(xiàn)A的showB函數(shù)
void A::showB(B b) {
    cout << "B's data: " << b.data << endl;
}

5.2 繼承中的友元關(guān)系

這里只需要記住一句話:友元關(guān)系不可以繼承,也就是說(shuō)基類(lèi)的友元不能訪問(wèn)派生類(lèi)的私有或者保護(hù)成員:

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é)號(hào)

};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}
int main()
{
	Person p;
	Student s;
	// 編譯報(bào)錯(cuò):error C2248 : “Student::_stuNum” :?法訪問(wèn)protected成員
    // 解決?案:Display也變成Student的友元即可
	Display(p, s);
	return 0;
}

六、繼承與靜態(tài)成員

靜態(tài)成員(變量或函數(shù))屬于整個(gè)類(lèi),而非某個(gè)具體對(duì)象,它被該類(lèi)的所有實(shí)例以及所有派生類(lèi)的實(shí)例共享。
在繼承關(guān)系中,基類(lèi)的靜態(tài)成員不會(huì)被派生類(lèi)復(fù)制,而是在整個(gè)繼承體系中只存在一份副本。

派生類(lèi)可以直接訪問(wèn)基類(lèi)的靜態(tài)成員,但需遵循訪問(wèn)權(quán)限控制(public/protected/private):

  • 基類(lèi)的public靜態(tài)成員:派生類(lèi)可直接訪問(wèn)(通過(guò)類(lèi)名或?qū)ο螅?/li>
  • 基類(lèi)的protected靜態(tài)成員:派生類(lèi)的成員函數(shù)中可訪問(wèn),但類(lèi)外不可直接訪問(wèn)。
  • 基類(lèi)的private靜態(tài)成員:派生類(lèi)無(wú)法訪問(wèn)(即使是成員函數(shù)中)。
#include <iostream>
using namespace std;

class Base {
public:
    static int public_static;  // 公有靜態(tài)成員
protected:
    static int protected_static;  // 保護(hù)靜態(tài)成員
private:
    static int private_static;  // 私有靜態(tài)成員
};

// 初始化基類(lèi)靜態(tài)成員
int Base::public_static = 0;
int Base::protected_static = 0;
int Base::private_static = 0;

class Derived : public Base {
public:
    void print() {
        // 訪問(wèn)基類(lèi)的public靜態(tài)成員
        cout << "public_static: " << public_static << endl;
        
        // 訪問(wèn)基類(lèi)的protected靜態(tài)成員(僅在派生類(lèi)內(nèi)部可訪問(wèn))
        cout << "protected_static: " << protected_static << endl;
        
        // 錯(cuò)誤:無(wú)法訪問(wèn)基類(lèi)的private靜態(tài)成員
        // cout << "private_static: " << private_static << endl;
    }
};

int main() {
    Derived d;
    d.print();  // 正確:通過(guò)派生類(lèi)對(duì)象調(diào)用,訪問(wèn)基類(lèi)的public/protected靜態(tài)成員
    
    // 直接訪問(wèn)基類(lèi)的public靜態(tài)成員(通過(guò)基類(lèi)名或派生類(lèi)名)
    cout << Base::public_static << endl;    // 正確
    cout << Derived::public_static << endl; // 正確(派生類(lèi)共享基類(lèi)的靜態(tài)成員)
    
    return 0;
}

七、多繼承與菱形繼承

7.1 單繼承與多繼承

單繼承指一個(gè)派生類(lèi)只從一個(gè)基類(lèi)繼承成員的方式。這是最簡(jiǎn)單、最常用的繼承形式,邏輯清晰,不易產(chǎn)生歧義。

多繼承指一個(gè)派生類(lèi)同時(shí)從多個(gè)基類(lèi)繼承成員的方式。它能讓派生類(lèi)整合多個(gè)不同基類(lèi)的功能,但也可能帶來(lái)復(fù)雜性。

多繼承的語(yǔ)法格式:

class 基類(lèi)1 { ... };
class 基類(lèi)2 { ... };

class 派生類(lèi) : 繼承方式 基類(lèi)1, 繼承方式 基類(lèi)2, ... {
    // 派生類(lèi)成員
};

7.1.1 多繼承中的指針偏移

這里我們先來(lái)看一道題:

例題:下面說(shuō)法正確的是( )

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;
}

要解答這道題,我們首先來(lái)看指針p3,p3指向整個(gè)Derive類(lèi):

再看p1與p2,因?yàn)閜1比p2早聲明所以p1<p2

接下來(lái)我們來(lái)考慮p1與p3的關(guān)系,我們發(fā)現(xiàn)p3與p1分別是派生類(lèi)與基類(lèi)的指針,并且p1指向的是第一個(gè)基類(lèi)(最先聲明的)。前面我們講過(guò)派生類(lèi)對(duì)象可以賦值給基類(lèi)的指針/基類(lèi)的引用,基類(lèi)指針或引用指向的是派生類(lèi)中切出來(lái)的基類(lèi)那部分。如圖:

所以正確結(jié)果是p2>p1==p3,也就是p1==p3!=p2選C。

7.2 菱形繼承(鉆石問(wèn)題)

當(dāng)兩個(gè)基類(lèi)繼承自同一個(gè)間接基類(lèi),而派生類(lèi)同時(shí)繼承這兩個(gè)基類(lèi)時(shí),會(huì)導(dǎo)致間接基類(lèi)的成員在派生類(lèi)中存在兩份副本,引發(fā)歧義。

在這里Student類(lèi)與Teacher類(lèi)同時(shí)繼承了Person類(lèi),此時(shí)Assistant又同時(shí)繼承了Student類(lèi)與Teacher類(lèi),這時(shí)會(huì)導(dǎo)致Person類(lèi)中的成員在Assistant類(lèi)中出現(xiàn)兩次。當(dāng)我們?cè)L問(wèn)這類(lèi)成員時(shí)編譯器不知道我們?cè)L問(wèn)的是Student中Person類(lèi)的成員還是Teacher中Person類(lèi)的成員:

如果出現(xiàn)這種情況,我們建議應(yīng)該聲明要訪問(wèn)的這類(lèi)變量的類(lèi)域讓編譯器知道我們要訪問(wèn)的是哪一個(gè)父類(lèi)中的重名變量。         

class Person
{
public:
	string _name; // 姓名

};
class Student : public Person
{
protected:
	int _num; //學(xué)號(hào)

};
class Teacher : public Person
{
protected:
	int _id; // 職?編號(hào)

};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修課程

};
int main()
{
	// 編譯報(bào)錯(cuò):error C2385 :對(duì)“_name”的訪問(wèn)不明確
	Assistant a;
	a._name = "peter";
	// 需要顯?指定訪問(wèn)哪個(gè)基類(lèi)的成員可以解決?義性問(wèn)題,但是數(shù)據(jù)冗余問(wèn)題?法解決
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
	return 0;
}

7.2.1 虛繼承

虛繼承是面向?qū)ο缶幊讨械囊环N技術(shù),主要用于解決多繼承時(shí)可能出現(xiàn)的菱形繼承問(wèn)題(也稱(chēng)為鉆石問(wèn)題)。

當(dāng)一個(gè)派生類(lèi)同時(shí)繼承自?xún)蓚€(gè)基類(lèi),而這兩個(gè)基類(lèi)又共同繼承自同一個(gè)間接基類(lèi)時(shí),就會(huì)形成菱形繼承結(jié)構(gòu)。這時(shí),派生類(lèi)會(huì)包含間接基類(lèi)的兩份副本,可能導(dǎo)致數(shù)據(jù)冗余和二義性。

虛繼承的作用就是讓派生類(lèi)只保留間接基類(lèi)的一份副本,從而解決上述問(wèn)題。

在 C++ 中,通過(guò)在繼承時(shí)使用virtual關(guān)鍵字來(lái)實(shí)現(xiàn)虛繼承,例如:

// 間接基類(lèi)
class A {
public:
    int x;
};

// 虛繼承自A
class B : virtual public A { };
class C : virtual public A { };

// 繼承自B和C,此時(shí)A只會(huì)有一份副本
class D : public B, public C {
public:
    void func() {
        x = 10;  // 不會(huì)產(chǎn)生二義性,因?yàn)锳只有一份
    }
};

通過(guò)虛繼承,類(lèi) D 中只會(huì)包含 A 的一個(gè)實(shí)例,避免了數(shù)據(jù)重復(fù)和訪問(wèn)沖突問(wèn)題。這種技術(shù)在需要實(shí)現(xiàn)復(fù)雜的多繼承結(jié)構(gòu)時(shí)特別有用,例如在一些大型框架的類(lèi)層次設(shè)計(jì)中。

八、繼承與組合

繼承允許你根據(jù)基類(lèi)的實(shí)現(xiàn)來(lái)定義派生類(lèi)的實(shí)現(xiàn)。這種通過(guò)生成派生類(lèi)的復(fù)用通常被稱(chēng)為白箱復(fù)用(white-box reuse)。術(shù)語(yǔ)“白箱”是相對(duì)可視性而言:在繼方式中,基類(lèi)的內(nèi)部細(xì)節(jié)對(duì)派生類(lèi)可見(jiàn)。繼承?定程度破壞了基類(lèi)的封裝,基類(lèi)的改變,對(duì)派生類(lèi)有很?的影響。派生類(lèi)和基類(lèi)間的依賴(lài)關(guān)系很強(qiáng),耦合度高。

對(duì)象組合是類(lèi)繼承之外的另?種復(fù)用選擇。新的更復(fù)雜的功能可以通過(guò)組裝或組合對(duì)象來(lái)獲得。對(duì) 象組合要求被組合的對(duì)象具有良好定義的接口。這種復(fù)用風(fēng)格被稱(chēng)為黑箱復(fù)用(black-boxreuse), 因?yàn)閷?duì)象的內(nèi)部細(xì)節(jié)是不可見(jiàn)的。對(duì)象只以“黑箱”的形式出現(xiàn)。組合類(lèi)之間沒(méi)有很強(qiáng)的依賴(lài)關(guān)系,耦合度低。優(yōu)先使用對(duì)象組合有助于你保持每個(gè)類(lèi)被封裝。

// Tire(輪胎)和Car(?)更符合has - a的關(guān)系
class Tire {
protected:
	// 品牌
	string _brand = "Michelin";  
	// 尺?
	size_t _size = 17;
};
class Car {
protected:
	string _colour = "??";           
	string _num = "陜ABIT00";         
	Tire _t1;
	Tire _t2;
	Tire _t3;
	Tire _t4;
};


class BMW : public Car {
public:
	void Drive() 
	{
		cout << "好開(kāi)操控" << endl; 
	}
};
// Car和BMW / Benz更符合is - a的關(guān)系
class Benz : public Car {
public:
	void Drive()
	{
		cout << "好坐舒適" << endl; 
	}
};

template<class T>
class vector
{};
// stack和vector的關(guān)系,既符合is - a,也符合has - a
template<class T>
class stack : public vector<T>
{};
template<class T>
class stack
{
public:
	vector<T> _v;
};
int main()
{
	return 0;
}

優(yōu)先使用組合,而不是繼承。實(shí)際盡量多去用組合,組合的耦合度低,代碼維護(hù)性好。不過(guò)也不太 那么絕對(duì),類(lèi)之間的關(guān)系就適合繼承(is-a)那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類(lèi)之間的 關(guān)系既適合用繼承(is-a)也適合組合(has-a),就用組合。

總結(jié)

到此這篇關(guān)于C++繼承機(jī)制的文章就介紹到這了,更多相關(guān)C++繼承機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

集贤县| 普兰县| 道真| 泾源县| 弋阳县| 卢湾区| 岳西县| 顺平县| 林州市| 禹城市| 尼木县| 富裕县| 兴海县| 库尔勒市| 观塘区| 德安县| 泗阳县| 绥阳县| 昂仁县| 大足县| 彩票| 云南省| 岗巴县| 安泽县| 吴堡县| 新郑市| 柳林县| 和平区| 慈溪市| 崇左市| 班玛县| 孙吴县| 峨眉山市| 高青县| 佛山市| 呼伦贝尔市| 无棣县| 柞水县| 高密市| 桑植县| 奈曼旗|