成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

C++繼承

URLOS / 1465人閱讀

摘要:例如,在關(guān)鍵字為的派生類當(dāng)中,所繼承的基類成員的訪問方式變?yōu)?。繼承中的作用域在繼承體系中的基類和派生類都有獨(dú)立的作用域。為了避免類似問題,實(shí)際在繼承體系當(dāng)中最好不要定義同名的成員。

繼承的概念及定義

繼承的概念

繼承(inheritance)機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類,稱為派生類。
繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,而繼承便是類設(shè)計(jì)層次的復(fù)用。

例如,以下代碼中Student類和Teacher類就繼承了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;   //學(xué)號(hào)};//子類class Teacher : public Person{protected:	int _jobid;   //工號(hào)};


繼承后,父類Person的成員,包括成員函數(shù)和成員變量,都會(huì)變成子類的一部分,也就是說,子類Student和Teacher復(fù)用了父類Person的成員。

繼承的定義

定義格式

繼承的定義格式如下:

說明: 在繼承當(dāng)中,父類也稱為基類,子類是由基類派生而來的,所以子類又稱為派生類。

繼承方式和訪問限定符

我們知道,訪問限定符有以下三種:

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

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

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

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

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

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

稍作觀察,實(shí)際上基類成員訪問方式的變化規(guī)則也不是無跡可尋的,我們可以認(rèn)為三種訪問限定符的權(quán)限大小為:public > protected > private,基類成員訪問方式的變化規(guī)則如下:

  1. 在基類當(dāng)中的訪問方式為public或protected的成員,在派生類當(dāng)中的訪問方式變?yōu)椋篗in(成員在基類的訪問方式,繼承方式)。
  2. 在基類當(dāng)中的訪問方式為private的成員,在派生類當(dāng)中都是不可見的。

基類的private成員在派生類當(dāng)中不可見是什么意思?

這句話的意思是,我們無法在派生類當(dāng)中訪問基類的private成員。例如,雖然Student類繼承了Person類,但是我們無法在Student類當(dāng)中訪問Person類當(dāng)中的private成員_name。

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

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

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

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

默認(rèn)繼承方式

在使用繼承的時(shí)候也可以不指定繼承方式,使用關(guān)鍵字class時(shí)默認(rèn)的繼承方式是private,使用struct時(shí)默認(rèn)的繼承方式是public。

例如,在關(guān)鍵字為class的派生類當(dāng)中,所繼承的基類成員_name的訪問方式變?yōu)閜rivate。

//基類class Person{public:	string _name = "張三"; //姓名};//派生類class Student : Person //默認(rèn)為private繼承{protected:	int _stuid;   //學(xué)號(hào)};

而在關(guān)鍵字為struct的派生類當(dāng)中,所繼承的基類成員_name的訪問方式仍為public。

//基類class Person{public:	string _name = "張三"; //姓名};//派生類struct Student : Person //默認(rèn)為public繼承{protected:	int _stuid;   //學(xué)號(hào)};

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

基類和派生類對(duì)象賦值轉(zhuǎn)換

派生類對(duì)象可以賦值給基類的對(duì)象、基類的指針以及基類的引用,因?yàn)樵谶@個(gè)過程中,會(huì)發(fā)生基類和派生類對(duì)象之間的賦值轉(zhuǎn)換。

例如,對(duì)于以下基類及其派生類。

//基類class Person{protected:	string _name; //姓名	string _sex;  //性別	int _age;     //年齡};//派生類class Student : public Person{protected:	int _stuid;   //學(xué)號(hào)};

代碼當(dāng)中可以出現(xiàn)以下邏輯:

Student s;Person p = s;     //派生類對(duì)象賦值給基類對(duì)象Person* ptr = &s; //派生類對(duì)象賦值給基類指針Person& ref = s;  //派生類對(duì)象賦值給基類引用

對(duì)于這種做法,有個(gè)形象的說法叫做切片/切割,寓意把派生類中基類那部分切來賦值過去。
派生類對(duì)象賦值給基類對(duì)象圖示:

派生類對(duì)象賦值給基類指針圖示:

派生類對(duì)象賦值給基類引用圖示:

注意: 基類對(duì)象不能賦值給派生類對(duì)象,基類的指針可以通過強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針,但是此時(shí)基類的指針必須是指向派生類的對(duì)象才是安全的。

繼承中的作用域

在繼承體系中的基類和派生類都有獨(dú)立的作用域。若子類和父類中有同名成員,子類成員將屏蔽父類對(duì)同名成員的直接訪問,這種情況叫隱藏,也叫重定義。

例如,對(duì)于以下代碼,訪問成員_num時(shí)將訪問到子類當(dāng)中的_num。

#include #include 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(); //999	return 0;}

若此時(shí)我們就是要訪問父類當(dāng)中的_num成員,我們可以使用作用域限定符進(jìn)行指定訪問。

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

需要注意的是,如果是成員函數(shù)的隱藏,只需要函數(shù)名相同就構(gòu)成隱藏。

例如,對(duì)于以下代碼,調(diào)用成員函數(shù)fun時(shí)將直接調(diào)用子類當(dāng)中的fun,若想調(diào)用父類當(dāng)中的fun,則需使用作用域限定符指定類域。

#include #include using namespace std;//父類class Person{public:	void fun(int x)	{		cout << x << endl;	}};//子類class Student : public Person{public:	void fun(double x)	{		cout << x << endl;	}};int main(){	Student s;	s.fun(3.14);       //直接調(diào)用子類當(dāng)中的成員函數(shù)fun	s.Person::fun(20); //指定調(diào)用父類當(dāng)中的成員函數(shù)fun	return 0;}

特別注意: 代碼當(dāng)中,父類中的fun和子類中的fun不是構(gòu)成函數(shù)重載,因?yàn)楹瘮?shù)重載要求兩個(gè)函數(shù)在同一作用域,而此時(shí)這兩個(gè)fun函數(shù)并不在同一作用域。為了避免類似問題,實(shí)際在繼承體系當(dāng)中最好不要定義同名的成員。

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

默認(rèn)成員函數(shù),即我們不寫編譯器會(huì)自動(dòng)生成的函數(shù),類當(dāng)中的默認(rèn)成員函數(shù)有以下六個(gè):

下面我們看看派生類當(dāng)中的默認(rèn)成員函數(shù),與普通類的默認(rèn)成員函數(shù)的不同之處。

例如,我們以下面這個(gè)Person類為基類。

//基類class Person{public:	//構(gòu)造函數(shù)	Person(const string& name = "peter")		:_name(name)	{		cout << "Person()" << endl;	}	//拷貝構(gòu)造函數(shù)	Person(const Person& p)		:_name(p._name)	{		cout << "Person(const Person& p)" << endl;	}	//賦值運(yùn)算符重載函數(shù)	Person& operator=(const Person& p)	{		cout << "Person& operator=(const Person& p)" << endl;		if (this != &p)		{			_name = p._name;		}		return *this;	}	//析構(gòu)函數(shù)	~Person()	{		cout << "~Person()" << endl;	}private:	string _name; //姓名};

我們用該基類派生出Student類,Student類當(dāng)中的默認(rèn)成員函數(shù)的基本邏輯如下:

//派生類class Student : public Person{public:	//構(gòu)造函數(shù)	Student(const string& name, int id)		:Person(name) //調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員		, _id(id) //初始化派生類的成員	{		cout << "Student()" << endl;	}	//拷貝構(gòu)造函數(shù)	Student(const Student& s)		:Person(s) //調(diào)用基類的拷貝構(gòu)造函數(shù)完成基類成員的拷貝構(gòu)造		, _id(s._id) //拷貝構(gòu)造派生類的成員	{		cout << "Student(const Student& s)" << endl;	}	//賦值運(yùn)算符重載函數(shù)	Student& operator=(const Student& s)	{		cout << "Student& operator=(const Student& s)" << endl;		if (this != &s)		{			Person::operator=(s); //調(diào)用基類的operator=完成基類成員的賦值			_id = s._id; //完成派生類成員的賦值		}		return *this;	}	//析構(gòu)函數(shù)	~Student()	{		cout << "~Student()" << endl;		//派生類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)	}private:	int _id; //學(xué)號(hào)};

派生類與普通類的默認(rèn)成員函數(shù)的不同之處概括為以下幾點(diǎn):

  1. 派生類的構(gòu)造函數(shù)被調(diào)用時(shí),會(huì)自動(dòng)調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員,如果基類當(dāng)中沒有默認(rèn)的構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表當(dāng)中顯示調(diào)用基類的構(gòu)造函數(shù)。
  2. 派生類的拷貝構(gòu)造函數(shù)必須調(diào)用基類的拷貝構(gòu)造函數(shù)完成基類成員的拷貝構(gòu)造。
  3. 派生類的賦值運(yùn)算符重載函數(shù)必須調(diào)用基類的賦值運(yùn)算符重載函數(shù)完成基類成員的賦值。
  4. 派生類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類成員。
  5. 派生類對(duì)象初始化時(shí),會(huì)先調(diào)用基類的構(gòu)造函數(shù)再調(diào)用派生類的構(gòu)造函數(shù)。
  6. 派生類對(duì)象在析構(gòu)時(shí),會(huì)先調(diào)用派生類的析構(gòu)函數(shù)再調(diào)用基類的析構(gòu)函數(shù)。

在編寫派生類的默認(rèn)成員函數(shù)時(shí),需要注意以下幾點(diǎn):

  1. 派生類和基類的賦值運(yùn)算符重載函數(shù)因?yàn)楹瘮?shù)名相同構(gòu)成隱藏,因此在派生類當(dāng)中調(diào)用基類的賦值運(yùn)算符重載函數(shù)時(shí),需要使用作用域限定符進(jìn)行指定調(diào)用。
  2. 由于多態(tài)的某些原因,任何類的析構(gòu)函數(shù)名都會(huì)被統(tǒng)一處理為destructor();。因此,派生類和基類的析構(gòu)函數(shù)也會(huì)因?yàn)楹瘮?shù)名相同構(gòu)成隱藏,若是我們需要在某處調(diào)用基類的析構(gòu)函數(shù),那么就要使用作用域限定符進(jìn)行指定調(diào)用。
  3. 在派生類的拷貝構(gòu)造函數(shù)和operator=當(dāng)中調(diào)用基類的拷貝構(gòu)造函數(shù)和operator=的傳參方式是一個(gè)切片行為,都是將派生類對(duì)象直接賦值給基類的引用。

說明一下:
基類的構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、賦值運(yùn)算符重載函數(shù)我們都可以在派生類當(dāng)中自行進(jìn)行調(diào)用,而基類的析構(gòu)函數(shù)是當(dāng)派生類的析構(gòu)函數(shù)被調(diào)用后由編譯器自動(dòng)調(diào)用的,我們?nèi)羰亲孕姓{(diào)用基類的構(gòu)造函數(shù)就會(huì)導(dǎo)致基類被析構(gòu)多次的問題。
我們知道,創(chuàng)建派生類對(duì)象時(shí)是先創(chuàng)建的基類成員再創(chuàng)建的派生類成員,編譯器為了保證析構(gòu)時(shí)先析構(gòu)派生類成員再析構(gòu)基類成員的順序析構(gòu),所以編譯器會(huì)在派生類的析構(gòu)函數(shù)被調(diào)用后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)。

繼承與友元

友元關(guān)系不能繼承,也就是說基類的友元可以訪問基類的私有和保護(hù)成員,但是不能訪問派生類的私有和保護(hù)成員。

例如,以下代碼中Display函數(shù)是基類Person的友元,當(dāng)時(shí)Display函數(shù)不是派生類Student的友元,即Display函數(shù)無法訪問派生類Student當(dāng)中的私有和保護(hù)成員。

#include #include using namespace std;class Student;class Person{public:	//聲明Display是Person的友元	friend void Display(const Person& p, const Student& s);protected:	string _name; //姓名};class Student : public Person{protected:	int _id; //學(xué)號(hào)};void Display(const Person& p, const Student& s){	cout << p._name << endl; //可以訪問	cout << s._id << endl; //無法訪問}int main(){	Person p;	Student s;	Display(p, s);	return 0;}

若想讓Display函數(shù)也能夠訪問派生類Student的私有和保護(hù)成員,只能在派生類Student當(dāng)中進(jìn)行友元聲明。

class Student : public Person{public:	//聲明Display是Student的友元	friend void Display(const Person& p, const Student& s);protected:	int _id; //學(xué)號(hào)};

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

若基類當(dāng)中定義了一個(gè)static靜態(tài)成員變量,則在整個(gè)繼承體系里面只有一個(gè)該靜態(tài)成員。無論派生出多少個(gè)子類,都只有一個(gè)static成員實(shí)例。

例如,在基類Person當(dāng)中定義了靜態(tài)成員變量_count,盡管Person又繼承了派生類Student和Graduate,但在整個(gè)繼承體系里面只有一個(gè)該靜態(tài)成員。
我們?nèi)羰窃诨怭erson的構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)當(dāng)中設(shè)置_count進(jìn)行自增,那么我們就可以隨時(shí)通過_count來獲取該時(shí)刻已經(jīng)實(shí)例化的Person、Student以及Graduate對(duì)象的總個(gè)數(shù)。

#include #include using namespace std;//基類class Person{public:	Person() 	{ 		_count++; 	}	Person(const Person& p) 	{		_count++;	}protected:	string _name; //姓名public:	static int _count; //統(tǒng)計(jì)人的個(gè)數(shù)。};int Person::_count = 0; //靜態(tài)成員變量在類外進(jìn)行初始化//派生類class Student : public Person{protected:	int _stuNum; //學(xué)號(hào)};//派生類class Graduate : public Person{protected:	string _seminarCourse; //研究科目};int main(){	Student s1;	Student s2(s1);	Student s3;	Graduate s4;	cout << Person::_count << endl; //4	cout << Student::_count << endl; //4	return 0;}

此時(shí)我們也可以通過打印Person類和Student類當(dāng)中靜態(tài)成員_count的地址來證明它們就是同一個(gè)變量。

cout << &Person::_count << endl; //00F1F320cout << &Student::_count << endl; //00F1F320

繼承的方式

單繼承:一個(gè)子類只有一個(gè)直接父類時(shí)稱這個(gè)繼承關(guān)系為單繼承。

多繼承:一個(gè)子類有兩個(gè)或兩個(gè)以上直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承。

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


從菱形繼承的模型構(gòu)造就可以看出,菱形繼承的繼承方式存在數(shù)據(jù)冗余和二義性的問題。

例如,對(duì)于以上菱形繼承的模型,當(dāng)我們實(shí)例化出一個(gè)Assistant對(duì)象后,訪問成員時(shí)就會(huì)出現(xiàn)二義性問題。

#include #include using namespace std;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(){	Assistant a;	a._name = "peter"; //二義性:無法明確知道要訪問哪一個(gè)_name	return 0;}

Assistant對(duì)象是多繼承的Student和Teacher,而Student和Teacher當(dāng)中都繼承了Person,因此Student和Teacher當(dāng)中都有_name成員,若是直接訪問Assistant對(duì)象的_name成員會(huì)出現(xiàn)訪問不明確的報(bào)錯(cuò)。

對(duì)于此,我們可以顯示指定訪問Assistant哪個(gè)父類的_name成員。

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

雖然該方法可以解決二義性的問題,但仍然不能解決數(shù)據(jù)冗余的問題。因?yàn)樵贏ssistant的對(duì)象在Person成員始終會(huì)存在兩份。

菱形虛擬繼承

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

虛擬繼承代碼如下:

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

此時(shí)就可以直接訪問Assistant對(duì)象的_name成員了,并且之后就算我們指定訪問Assistant的Student父類和Teacher父類的_name成員,訪問到的都是同一個(gè)結(jié)果,解決了二義性的問題。

cout << a.Student::_name << endl; //petercout << a.Teacher::_name << endl; //peter

而我們打印Assistant的Student父類和Teacher父類的_name成員的地址時(shí),顯示的也是同一個(gè)地址,解決了數(shù)據(jù)冗余的問題。

cout << &a.Student::_name << endl; //0136F74Ccout << &a.Teacher::_name << endl; //0136F74C

菱形虛擬繼承原理

在此之前,我們先看看不使用菱形虛擬繼承時(shí),以下菱形繼承當(dāng)中D類對(duì)象的各個(gè)成員在內(nèi)存當(dāng)中的分布情況。


測(cè)試代碼如下:

#include using namespace std;class A{public:	int _a;};class B : public A{public:	int _b;};class C : public A{public:	int _c;};class D : public B, public C{public:	int _d;
            
                     
             
               

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/122420.html

相關(guān)文章

  • C++重溫筆記(四): 繼承和派生

    摘要:繼承繼承,就是子類繼承父親的特征和行為,使得子類具有父類的成員變量和方法。此時(shí),被繼承的類稱為父類或基類,而繼承的類稱為子類或派生類。,如果存在繼承關(guān)系的時(shí)候,和就不一樣了基類中的成員可以在派生類中使用,但是基類中的成員不能再派生類中使用。 ...

    DevWiki 評(píng)論0 收藏0
  • C++學(xué)習(xí)資料和視頻

    摘要:博主在公眾號(hào)后臺(tái)設(shè)置了關(guān)鍵字回復(fù),回復(fù)下面的里面的內(nèi)容,可免費(fèi)獲得學(xué)習(xí)視頻和資料。 博主在公眾號(hào)后臺(tái)設(shè)置了關(guān)鍵字回復(fù), 回復(fù)下面的【】里面的內(nèi)容, 可免費(fèi)獲得C++學(xué)習(xí)視頻和資料。 如回復(fù):C++基礎(chǔ) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? 【C++】 【1】...

    wangshijun 評(píng)論0 收藏0
  • C++繼承

    摘要:基類中的構(gòu)造函數(shù)和析構(gòu)函數(shù)不能被繼承,在派生類中需要定義新的構(gòu)造函數(shù)和析構(gòu)函數(shù),私有成員不能被繼承。對(duì)象訪問在派生類外部,通過派生類的對(duì)象對(duì)從基類繼承來的成員的訪問。 ...

    不知名網(wǎng)友 評(píng)論0 收藏0
  • 淺析 Python 的類、繼承和多態(tài)

    摘要:類的定義假如要定義一個(gè)類,表示二維的坐標(biāo)點(diǎn)最最基本的就是方法,相當(dāng)于的構(gòu)造函數(shù)。嚴(yán)格來講,并不支持多態(tài)。靜態(tài)類型的缺失,讓很難實(shí)現(xiàn)那樣嚴(yán)格的多態(tài)檢查機(jī)制。有時(shí)候,需要在子類中調(diào)用父類的方法。 類的定義 假如要定義一個(gè)類 Point,表示二維的坐標(biāo)點(diǎn): # point.py class Point: def __init__(self, x=0, y=0): se...

    shadajin 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<