真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網站制作重慶分公司

C++之繼承-創(chuàng)新互聯(lián)

文章目錄
  • 一、繼承的基本理解
    • 1.繼承的概念
    • 2.繼承的定義
  • 二、基類和派生類對象賦值轉換
  • 三、繼承中的作用域
  • 四、派生類的默認成員函數
  • 五、繼承與友元
  • 六、繼承與靜態(tài)成員
  • 七、復雜的菱形繼承及菱形虛擬繼承
    • 1.繼承關系
    • 2.菱形繼承存在數據冗余和二義性的問題
    • 3.虛擬繼承可以解決菱形繼承數據冗余和二義性的問題
    • 4.虛擬繼承解決菱形繼承數據冗余和二義性的原理
  • 八、繼承的總結和反思

創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網綜合服務,包含不限于成都做網站、網站制作、興慶網絡推廣、成都微信小程序、興慶網絡營銷、興慶企業(yè)策劃、興慶品牌公關、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們大的嘉獎;創(chuàng)新互聯(lián)公司為所有大學生創(chuàng)業(yè)者提供興慶建站搭建服務,24小時服務熱線:18982081108,官方網址:www.cdcxhl.com一、繼承的基本理解 1.繼承的概念

繼承(inheritance)機制是面向對象程序設計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能,這樣產生新的類,稱派生類。繼承呈現了面向對象程序設計的層次結構,體現了由簡單到復雜的認知過程。

繼承是類設計層次的復用。

簡單圖示:

在這里插入圖片描述

一個簡單例子:

// 父類
class Person
{public:
	void Print()
	{cout<< "name:"<< _name<< endl;
		cout<< "age:"<< _age<< endl;
	}

protected:
	string _name = "Peter";  // 姓名
	int _age = 20;           // 年齡
};

// 繼承后父類Person的成員(成員函數+成員變量)都會變成子類的一部分
// 這里體現出了Student和Teacher復用了Person的成員。
// 可以通過監(jiān)視窗口查看Student和Teacher對象
// 可以看到變量的復用。調用Print可以看到成員函數的復用。

// 子類
class Student : public Person
{protected:
	int _stuid;  // 學號
};

// 子類
class Teacher : public Person
{protected:
	int _jobid;  // 工號
};

int main()
{Student s;
	Teacher t;
	s.Print();
	t.Print();

	return 0;
}

查看監(jiān)視窗口:
在這里插入圖片描述

2.繼承的定義

定義格式:
在這里插入圖片描述

繼承關系和訪問限定符:

在這里插入圖片描述


派生類繼承基類成員后,基類成員在派生類中的訪問方式:

  • 基類的私有成員在派生類中都是不可見的。
  • 基類的其他成員在子類的訪問方式 == min(基類成員的訪問限定符,派生類的繼承方式),public >protected >private 。

用一張表來描述,就是:

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

繼承影響的是繼承下來的基類成員,跟子類成員無關,不要混淆。

說明:

  1. 基類 private 成員在派生類中無論以什么方式繼承都是不可見的。這里的不可見,是指基類的私有成員還是被繼承到了派生類對象中,但是語法上限制了派生類對象不管是在類內還是類外都不能去訪問它。換言之,如果父類有一個成員,不想讓子類使用,可以在父類中把該成員定義為 private 。
  2. 基類 private 成員在派生類中是不能被訪問。如果基類成員不想在類外被直接訪問,但需要在派生類中能訪問,就定義為 protected ??梢钥闯?protected 成員限定符是因繼承才出現的。
  3. ① 關于繼承方式,在實際運用中一般都是使用 public 繼承,幾乎不使用 protected/private 繼承,也不提倡使用 protected/private 繼承,因為 protected/private 繼承下來的成員都只能在派生類的類內使用,實際中擴展維護性不強。
    ② 關于訪問限定符,在實際運用中一般都是使用 public/protected ,幾乎不使用 private 。
    換言之,實際中最常見的是,父類成員使用 public/protected 修飾,子類的繼承方式使用 public 繼承。9 種關系里面只用了 2 種。
  4. 使用 class 時默認的繼承方式是 private ,使用 struct 時默認的繼承方式是 public ,不過最好還是顯示地寫出繼承方式,這樣更直觀。
二、基類和派生類對象賦值轉換
  • 派生類對象可以賦值給基類的對象 / 基類的指針 / 基類的引用。這里有個形象的說法叫切片或者切割,寓意把派生類中父類那部分切來賦值過去,僅限于 public 繼承。

  • 基類對象不能賦值給派生類對象。
    基類的指針或者引用可以通過強制類型轉換賦值給派生類的指針或者引用,但這很危險,存在越界訪問的風險,不要這么用。這里基類如果是多態(tài)類型,可以使用 RTTI(Run-Time Type Identification)的 dynamic_cast 來進行識別后進行安全轉換。

測試代碼:

class Person
{protected:
	string _name;  // 姓名
	string _sex;   // 性別
	int _age;      // 年齡
};

class Student : public Person
{public:
	int _No;  // 學號
};

int main()
{Person p;
	Student s;

	// 子類對象可以賦值給父類的對象/父類的指針/父類的引用
	// 賦值兼容 ->切割/切片
	// 這里不存在類型轉換,是語法天然支持的行為
	p = s;
	Person* pp = &s;
	Person& rp = s;

	// 父類對象不能賦值給子類對象
	//s = p;  s = (Student)p;  // 怎樣都不行
	// 父類的指針或者引用可以通過強制類型轉換賦值給子類的指針或者引用
	// 但這很危險,存在越界訪問的風險,不要這么用
	Student* ps = (Student*)&p;
	//ps->_No = 1;  
	Student& rs = (Student&)p;
	//rs._No = 2;   

	return 0;
}

將上述代碼圖示:在這里插入圖片描述

三、繼承中的作用域
  • 在繼承體系中基類和派生類都有獨立的作用域。
  • 子類和父類中若有同名成員,子類成員將屏蔽父類對同名成員的直接訪問,這種情況叫隱藏 / 重定義(成員函數的隱藏,只需要函數名相同就構成隱藏,跟參數無關)。若要在子類中訪問被隱藏的父類同名成員,需要指明父類類域顯示訪問。
  • 實際中,在繼承體系里面最好不要定義同名的成員。

說明:
?① 類的成員,包括成員變量和成員函數。
?② 構成隱藏 / 重定義之后,到底是操作父類還是子類的成員,本質上還是要看作用域,跟同名的局部變量和全局變量類似,都是就近原則。

測試代碼1:

// Student的_num和Person的_num構成隱藏/重定義關系
// 可以看出這樣的代碼雖然能跑,但是非常容易混淆
class Person
{protected:
	string _name = "小李子";  // 姓名
	int _num = 111;          // 身份證號
};

class Student : public Person
{public:
	void Print()
	{cout<< "姓名:"<< _name<< endl;
		cout<< _num<< endl;          // 就近原則
		cout<< Person::_num<< endl;  // 若要訪問父類同名成員,需要指明類域
	}

protected:
	int _num = 999;  // 學號
};

int a = 0;

int main()
{int a = 1;
	cout<< a<< endl;    // 此處訪問的是局部變量a,因為就近原則 
	cout<< ::a<< endl;  // 要想訪問全局變量a,需指明作用域

	Student s;
	s.Print();

	return 0;
}

測試代碼2:

// B中的func和A中的func不構成函數重載,因為不是在同一作用域
// B中的func和A中的func構成隱藏,成員函數滿足函數名相同就構成隱藏
class A
{public:
	void func()
	{cout<< "func()"<< endl;
	}

	void f()
	{cout<< "f()"<< endl;
	}
};

class B : public A
{public:
	void func(int i)
	{cout<< "func(int i)->"<< i<< endl;
	}
};

int main()
{B b;
	b.func(1);
	//b.func();   // 編譯報錯,因為父類A的func被隱藏了
	b.A::func();  // 若想調用被隱藏的父類同名成員函數,必須指明類域
	b.f();

	return 0;
}
四、派生類的默認成員函數

先拋開繼承不談,類的構造函數和析構函數的行為分別是:
?① 構造函數:先根據成員變量的聲明次序在初始化列表中順序完成成員變量的初始化,再執(zhí)行函數體內的語句。
?② 析構函數:先執(zhí)行函數體內的語句,再根據成員變量的聲明次序逆序完成成員變量的析構。

也就是說,類的構造和析構保證符合棧的后進先出。

加入繼承后,可以把繼承下來的父類理解成子類的一個自定義類型成員變量,且在成員變量中的聲明次序是順序第一位。

測試代碼1:

class Person
{public:
	Person(const char* name = "Peter")
		: _name(name)
	{cout<< "Person()"<< endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{cout<< "Person(const Person& p)"<< endl;
	}
	
	Person& operator=(const Person& p)
	{cout<< "Person operator=(const Person& p)"<< endl;
		if (this != &p)
		{	_name = p._name;
		}
		return *this;
	}
	
	~Person()
	{cout<< "~Person()"<< endl;
	}

protected:
	string _name;  // 姓名
};

class Student : public Person
{public:

protected:
	int _num = 1;  // 學號
	string _s = "hello world";
};

// 派生類的重點的四個默認成員函數,我們不寫,編譯器默認生成的:

// 我們不寫默認生成的派生類的構造和析構:
// a.父類繼承下來的(調用父類默認構造和析構處理)   b.自己的(按普通類處理)
// 我們不寫默認生成的派生類的拷貝構造和operator=:
// a.父類繼承下來的(調用父類拷貝構造和operator=) b.自己的(按普通類處理)

// 總結:繼承下來的調用父類處理,自己的按普通類處理

int main()
{Student s1;
	Student s2(s1);
	Student s3;
	s1 = s3;

	return 0;
}

測試代碼2:

class Person
{public:
	Person(const char* name)
		: _name(name)
	{cout<< "Person()"<< endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{cout<< "Person(const Person& p)"<< endl;
	}
	
	Person& operator=(const Person& p)
	{cout<< "Person operator=(const Person& p)"<< endl;
		if (this != &p)
		{	_name = p._name;
		}
		return *this;
	}
	
	~Person()
	{cout<< "~Person()"<< endl;
		//delete[] _ptr;
	}

protected:
	string _name;  // 姓名
	//int* _ptr = new int[10];
};

class Student : public Person
{public:
	Student(const char* name = "張三", int num = 4)
		:Person(name)
		, _num(num)
	{}

	// s2(s1)
	// 其實可以不寫,默認生成的就可以了
	Student(const Student& s)
		:Person(s)  // 傳參時將子類對象賦值給父類的引用,即切片
		,_num(s._num)
	{}

	// s2 = s1
	// 其實可以不寫,默認生成的就可以了
	Student& operator=(const Student& s)
	{if (this != &s)
		{	Person::operator=(s);  // 傳參時將子類對象賦值給父類的引用,即切片
			_num = s._num;
		}

		return *this;
	}

	// 析構函數名字會被統(tǒng)一處理成destructor()
	// 那么子類的析構函數跟父類的析構函數就構成隱藏
	// 所以,要調用父類的析構函數,需要指明父類類域
	~Student()
	{//Person::~Person();
		//delete[] _ptr;
	}
	// 但是子類析構函數結束時,會自動調用父類的析構函數(保證符合棧的后進先出)
	// 所以我們自己實現子類析構函數時,不需要顯式調用父類析構函數
	// 否則會調用兩次父類析構函數,可能導致運行出錯

protected:
	int _num;  // 學號
	//string _s = "hello world";
	//int* _ptr = new int[10];
};

// 什么情況下必須自己寫子類的默認成員函數?
// 1.父類沒有默認構造,需要顯式寫構造
// 2.若子類有資源需要釋放,就需要顯式寫析構
// 3.若子類存在淺拷貝問題,就需要自己實現拷貝構造和賦值重載解決淺拷貝問題

// 若必須寫子類的默認成員函數,如何寫?
// 1.父類成員,調用父類的對應構造、拷貝構造、operator=和析構處理
// 2.自己成員,按普通類處理

// 總結:繼承下來的調用父類處理,自己的按普通類處理

int main()
{Student s1;
	Student s2(s1);
	Student s3("Jack", 19);
	s1 = s3;

	return 0;
}

說明:

  1. 派生類對象初始化:一定是先調用基類構造再調用派生類構造。
  2. 派生類對象析構:一定是先調用派生類析構再調用基類析構。
  3. 因為后續(xù)一些場景析構函數需要構成重寫,重寫的條件之一是函數名相同。那么編譯器會對析構函數名統(tǒng)一進行特殊處理,處理成destructor(),所以父類析構函數不加 virtual 關鍵字的情況下,子類析構函數和父類析構函數構成隱藏關系。
五、繼承與友元

友元關系不能繼承,也就是說基類友元不能訪問子類私有和保護成員。

測試代碼:

class Student;

class Person
{friend void Display(const Person& p, const Student& s);
public:

protected:
	string _name;  // 姓名
};

class Student : public Person
{protected:
	int _stuNum;   // 學號
};

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 成員,則整個繼承體系里面只有一個這樣的成員。無論派生出多少個子類,都只有一個 static 成員實例。

測試代碼:

class Person
{public:
	Person() 
	{++_count; 
	}

protected:
	string _name;  // 姓名

public:
	static int _count;  // 統(tǒng)計人的個數
};

int Person::_count = 0;

class Student : public Person
{protected:
	int _stuNum;  // 學號
};

class Graduate : public Student
{protected:
	string _seminarCourse;  // 研究科目
};

int main()
{Person p;
	Student s;
	Graduate g;

	cout<< Person::_count<< endl;
	cout<< Student::_count<< endl;
	cout<< Graduate::_count<< endl;

	cout<< &Person::_count<< endl;
	cout<< &Student::_count<< endl;
	cout<< &Graduate::_count<< endl;

	return 0;
}
七、復雜的菱形繼承及菱形虛擬繼承 1.繼承關系
  • 單繼承:一個子類只有一個直接父類時稱這個繼承關系為單繼承。
    在這里插入圖片描述

  • 多繼承:一個子類有兩個或以上直接父類時稱這個繼承關系為多繼承。
    在這里插入圖片描述

  • 菱形繼承:菱形繼承是多繼承的一種特殊情況。
    在這里插入圖片描述

2.菱形繼承存在數據冗余和二義性的問題

菱形繼承存在數據冗余和二義性的問題。

比如下面的對象成員模型構造:
在這里插入圖片描述
在 Assistant 對象中 Person 成員會有兩份,若要訪問 _name ,是訪問從 Student 繼承過來的還是訪問從 Teacher 繼承過來的呢?所以說 Assistant 存在數據冗余和二義性的問題。

class Person
{public:
	string _name;  // 姓名
	int _a[10000];
};

class Student : public Person
{public:
	int _num;  // 學號
};

class Teacher : public Person
{public:
	int _id;  // 職工編號
};

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

int main()
{Assistant a;
	a._num = 1;
	a._id = 2;
	
	//a._name = "張三";  // 編譯報錯,對_name訪問不明確
	//指明父類類域,可以解決二義性的問題
	a.Student::_name = "小張";
	a.Teacher::_name = "張老師";

	//但是數據冗余的問題無法解決,萬一數據很大就會浪費掉很多空間
	cout<< sizeof(a)<< endl;
	
	return 0;
}
3.虛擬繼承可以解決菱形繼承數據冗余和二義性的問題

虛擬繼承可以解決菱形繼承數據冗余和二義性的問題。

比如上面的繼承關系,在 Student 和 Teacher 繼承 Person 時使用虛擬繼承,即可解決問題。

class Person
{public:
	string _name;  // 姓名
	int _a[10000];
};

class Student : virtual public Person   // 虛擬繼承 
{public:
	int _num;  // 學號
};

class Teacher : virtual public Person   // 虛擬繼承
{public:
	int _id;  // 職工編號
};

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

int main()
{Assistant a;
	// 虛擬繼承可以解決菱形繼承的數據冗余和二義性的問題
	a.Student::_name = "小張";
	a.Teacher::_name = "張老師";
	a._name = "張三";

	cout<< sizeof(a)<< endl;

	return 0;
}
4.虛擬繼承解決菱形繼承數據冗余和二義性的原理

為了研究虛擬繼承的原理,我們給出了一個簡化的菱形繼承繼承體系,再借助內存窗口觀察對象成員的模型。

測試代碼1:不使用虛擬繼承。

class A
{public:
	int _a;
};

class B : public A
//class B : virtual public A
{public:
	int _b;
};

class C : public A
//class C : virtual public A
{public:
	int _c;
};

class D : public B, public C
{public:
	int _d;
};

int main()
{D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}

下圖是菱形繼承的內存對象成員模型:這里可以看到數據冗余。在這里插入圖片描述


測試代碼2:使用虛擬繼承。

class A
{public:
	int _a;
};

//class B : public A
class B : virtual public A
{public:
	int _b;
};

//class C : public A
class C : virtual public A
{public:
	int _c;
};

class D : public B, public C
{public:
	int _d;
};

int main()
{D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	d._a = 0;
	
	return 0;
}

下圖是菱形虛擬繼承的內存對象成員模型:這里可以分析出 D 對象中將 A 放到了對象組成的最下面,這個 A 是 B 和 C 的公共基類,那么 B 和 C 如何去找到公共的 A 呢?這里是通過了 B 和 C 里的兩個指針,各指向一張表。這兩個指針叫虛基表指針,這兩張表叫虛基表。虛基表中存有偏移量,通過偏移量可以找到 A 。
在這里插入圖片描述
A 是被虛擬繼承的基類,稱之為虛基類。B 或 C 需要找 A ,就要通過虛基表中的偏移量進行計算。
此時的 A 既不屬于 B ,也不屬于 C ,而是屬于 D 。

八、繼承的總結和反思
  1. 很多人說 C++ 的語法復雜,其實多繼承就是一個體現。有了多繼承,就存在菱形繼承,有了菱形繼承就有菱形虛擬繼承,底層實現就很復雜。所以一般不建議設計出多繼承,最好不要設計出菱形繼承。
  2. 多繼承可以認為是 C++ 的缺陷之一,后來的很多語言都沒有多繼承,如 Java 。
  3. 繼承和組合:
    ① public 繼承是一種 is-a 的關系。也就是說每個派生類對象都是一個基類對象。
    組合是一種 has-a 的關系。假設 B 組合了 A ,每個 B 對象中都有一個 A 對象。
    繼承和組合都能達到復用的目的。
    ② 優(yōu)先使用對象組合,而不是類繼承。
    ③ 繼承允許你根據基類的實現來定義派生類的實現。這種通過生成派生類的復用通常被稱為白箱復用(white-box reuse)。術語“白箱”是相對可視性而言:在繼承方式中,基類的內部細節(jié)對子類可見 。繼承一定程度破壞了基類的封裝,基類的改變,對派生類有很大的影響。派生類和基類間的依賴關系很強,耦合度高。
    ④ 對象組合是類繼承之外的另一種復用選擇。新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接口。這種復用風格被稱為黑箱復用(black-box reuse),因為對象的內部細節(jié)是不可見的。對象只以“黑箱”的形式出現。組合類之間沒有很強的依賴關系,耦合度低。優(yōu)先使用對象組合有助于你保持每個類被封裝。
    ⑤ 實際盡量多去用組合,因為組合的耦合度低,代碼維護性好。有些關系就適合繼承那就用繼承,另外要實現多態(tài),也必須要繼承。類之間的關系可以用繼承,也可以用組合,優(yōu)先用組合。
// BMW和Benz與Car構成is-a的關系
class Car 
{protected:
	string _colour = "白色";    // 顏色
	string _num = "粵A XXX00";  // 車牌號
};

class BMW : public Car 
{public:
	void Drive() {cout<< "好開-操控"<< endl; }
};

class Benz : public Car 
{public:
	void Drive() {cout<< "好坐-舒適"<< endl; }
};
// Car和Tire構成has-a的關系
class Tire 
{protected:
	string _brand = "Michelin";  // 品牌
	size_t _size = 17;           // 尺寸
};

class Car 
{protected:
	string _colour = "白色";     // 顏色
	string _num = "粵A XXX00";  // 車牌號
	Tire _t;                   // 輪胎
};

你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧


新聞名稱:C++之繼承-創(chuàng)新互聯(lián)
網頁地址:http://weahome.cn/article/deidjh.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部