C++筆記之類的繼承和派生

一. 繼承和派生介紹

1.概念

繼承: 保持已有類的特性而構造新類的過程。
派生: 在已有類的基礎上新增自己的特性而產生新類的過程。
基類(或父類):被繼承的已有類。
派生類(或子類) :派生出的新類。

2.繼承派生目的

繼承的目的:實現代碼重用
派生的目的:當新的問題出現,原有程序無法解決(或不能完全解決)時,需要對原有程序進行改造。

3.派生類定義

class  派生類名:繼承方式  基類名1, 繼承方式 基類名2...繼承方式  基類n
{
	派生類成員聲明;
};

注意:
(1)一個派生類可以同時有多個基類,即多繼承;一個派生類可以只有一個基類,即單繼承
(2)直接派生出某類的基類即直接基類,基類的基類甚至更高的基類爲間接基類
(3)派生類成員是指除了從基類繼承來的所有成員之外,新增加的數據和函數
舉例:

class  TimeDate: public Time, private Date
{
public:
	TimeDate(int y,int m,int d,int h,int n,int s);
	~TimeDate();
};

4.派生類生成過程

(1)吸收基類成員
派生類繼承了基類中除構造函數和析構函數之外的所有成員。
(2)改造基類成員
基類成員的訪問控制問題;基類數據或函數成員的隱藏。
(3)添加新的成員
派生類成員的加入是繼承和派生機制的核心,給派生類添加數據和函數成員實現新增功能。

二.繼承方式

不同繼承方式的影響主要體現在:

  • 派生類成員對基類成員的訪問權限
  • 通過派生類對象對基類成員的訪問權限

1.公有繼承(Public)

基類的public和protected成員的訪問屬性在派生中保持不變,但基類的private成員不可直接訪問

  • 派生類中的成員函數可以直接訪問基類中的public和protected成員,但不能直接訪問基類的private成員
  • 派生類的對象只能訪問基類的public成員

成員理解:

class  A
{
pbulic:
	int a1;
private:
	int a2;
protected:
	int a3;
};
class B:public A
{
public:
	int b1;    //包含 int a1
private:
	int b2;		//包含int a2
protected:
	int b3;		//包含int a3
}

舉例說明:

#include<iostream>
#include<cmath>
using namecpace std;

class Point	//基類Point類的聲明
{
public:	//公有函數成員
	void initP(float xx=0, float yy=0)
    {x=xx;y=yy;}
	void move(float xOff, float yOff)
    {x+=xOff;x+=yOff;}
	float getX() {return x;}
	float getY() {return y;}
private:	//私有數據成員
	float x,y;
};

class Square: public Point  //派生類聲明,公有繼承方式
{
public:	
	void InitR(float x, float y, float l)  //新增公有函數成員
	{InitP(x,y); length =l;} //派生類成員函數調用基類公有成員函數
	float getLength() {return length;}
private:	
	float length;//新增私有數據成員,另外還有基類數據成員 float x, float y
};

int main()
{   Square square;
	square.InitR(1,1,10);    //通過派生類對象訪問基類公有成員函數
	square.move(3,3);  
    cout<<"The data of square(X,Y,Length):"<<endl;
	cout<< square.getX()<<','<< square.getY()<<',' //通過派生類對象訪問基類公有成員函數
		<< square.getLength()<<endl;
	return 0;
}

2.私有繼承(Private)

基類的public和protected成員都以private身份出現在派生類中,但基類的private成員不可直接訪問

  • 派生類中的成員函數可以直接訪問基類中public和protected成員,但不能直接訪問基類的private成員
  • 派生類對象不能直接訪問基類的任何成員

成員理解:

class  A
{
pbulic:
	int a1;
private:
	int a2;
protected:
	int a3;
};
class B:private A
{
public:
	int b1;    
private:
	int b2;		//包含int a1,int a2, int a3
protected:
	int b3;		 
};

舉例說明:

//基類和公有繼承的Point類一樣,此處略去
class Square: private Point	//派生類聲明
{
public:	//新增外部接口
	void InitR(float x, float y, float l){InitP(x,y);length=l;}	//派生類成員函數訪問基類公有成員函數
	void move(float xOff, float yOff) {Point::move(xOff,yOff) ;}
	float getX() {return Point::getX();} //派生類成員函數訪問基類的getX函數
	float getY() {return Point::getY();}
	float getLength() {return length;}
private:	
	//包含Point類的public成員如move函數、getX函數、getY函數、initP函數,private成員float x,float y
	float length; 
};

int main()
{  //通過派生類對象只能訪問本類成員
    Square square;
	square.InitR(1,1,10);  //派生類對象不能訪問基類的任何成員,現在這些函數都是派生類自己的函數
	square.move(3,3);
    cout << "The data of square(X, Y, Lenght) :" ;
	cout << square.getX( ) << "," << square.getY( ) << ",";
	cout << square.getLength( ) << endl ;
	return 0;
}

3.保護繼承(Protected)

基類的public和protected成員都以protected身份出現在派生類中,但基類的private成員不可直接訪問.

  • 派生類成員函數可以直接訪問基類的public和protected成員,但不能直接訪問基類的private成員
  • 派生類對象不能直接訪問基類中的任何成員

protected成員的特點與作用:

  • 其所在類的對象來說,它與 private 成員的性質相同。
  • 對於其派生類來說,它與 public 成員的性質相同。
  • 既實現了數據隱藏,又方便繼承,實現代碼重用.

成員理解:

class  A
{
pbulic:
	int a1;
private:
	int a2;
protected:
	int a3;
};
class B:protected A
{
public:
	int b1;    
private:
	int b2;		 //包含 int a2
protected:
	int b3;	//包含int a1, int a3	 
};

舉例說明:

class A
{
protected:
	int x;
};
class B:public A
{
public:
	void Function() { x=5;}  //正確,protected成員對象其派生類來說是public
};
int main()
{   A a;
	a.x=5;  //報錯,protected成員對其對象來說是私有,不能直接訪問
}

Alt

三.類型兼容規則

一個公有派生類的對象在使用上可以被當做基類對象,反之則禁止。(大材小用可以, 小材大用不行

  1. 派生類的對象可以被賦值給基類對象
  2. 派生類對象可以初始化基類的引用
  3. 指向基類的指針也可以指向派生類

另外注意,通過基類對象名、指針,只能使用從基類繼承的成員。

幫助理解:

class B
{ ...};
class C:public B
{ ... };
B b1,*pb1;
C c1;

b1 = c1; //派生類的對象可以被賦值給基類
B &bb = c1; //派生類的對象可以初始化基類的引用
pb1 = &c1; //指向基類的指針也可以指向派生類

基類指針指向派生類對象,只能使用繼承下來的基類成員

#include <iostream>
using namespace std;
class B0	//基類B0聲明
{ 
public:
	void display() {cout<<"B0::display()"<<endl;}//公有成員函數
};

class B1: public B0	
{
 public:
	void display(){cout<<"B1::display()"<<endl;}	
};

void fun(B0 *ptr)	
{	ptr->display();	//"對象指針->成員名"  
}
int main()	//主函數
{	B0 b0;	//聲明B0類對象
	B1 b1;	//聲明B1類對象
	B0 *p;	//聲明B0類指針
	p=&b0;	//B0類指針指向B0類對象
	fun(p); //運行結果 B0::display()
	p=&b1;	//B0類指針指向B1類對象
	fun(p); //運行結果 B0::display()

四.派生類的構造函數和析構函數

1.繼承時的構造函數

(1)**基類的構造函數不被繼承,派生類需要聲明自己的構造函數
(2) 聲明構造函數時
只對本類新增成員進行初始化**,對繼承而來的基類成員的初始化自動調用基類構造函數完成。
(3) 當基類中聲明有默認形式的構造函數或未聲明構造函數時,派生類構造函數可以不向基類構造函數傳遞參數。
(4) 當基類聲明有帶形參的構造函數時,派生類也應聲明帶形參的構造函數,並將參數傳遞給基類構造函數。

派生類名::派生類名(基類名1所需形參,基類名2所需形參,本類成員所需形參):基類名1(參數表),基類名2(參數表)
{
	本類成員初始化;
}

2.構造函數的調用次序

(1)調用基類構造函數,按照基類被繼承時的聲明次序(從左到右)
(2) 調用內嵌成員對象的構造函數,調用順序按照它們在類中聲明的順序
(3) 派生類中的構造函數體中的內容。
構造函數的執行順序和派生類構造函數中列出的名稱順序無關

3.複製構造函數

  • 若建立派生類對象時調用缺省拷貝構造函數,則編譯器將自動調用基類的缺省拷貝構造函數
  • 若編寫派生類的拷貝構造函數,則需要爲基類相應的拷貝構造函數傳遞參數
C::C(C &c1):B(c1)
{}

4.繼承時的析構函數

(1) 析構函數也不被繼承,派生類自行聲明
(2) 聲明方法與一般(無繼承關係時)類的析構函數相同。
(3) 不需要顯式地調用基類的析構函數,系統會自動隱式調用。
(4)析構函數的調用次序與構造函數相反

舉例說明:

#include<iostream>
#include<string>
using namespace std;
class Person {
public:
	Person(string n, char s, int a);
	Person(Person &p);
	void input();
	void display();
private:
	string name;
	char sex;
	int age;
};
Person::Person(string n, char s, int a) {
	name = n;
	sex = s;
	age = a;
}
Person::Person(Person &p) {
	name = p.name;
	sex = p.sex;
	age = p.age;
}
void Person::input() {
	cout << "請輸入姓名:";
	cin >> name;
	cout << "年齡:";
	cin >> age;
	cout << "性別:";
	cin >> sex;
}
void Person::display() {
	cout << "姓名:" << name << ", 年齡:" << age << ", 性別:" << sex;
}
class Student :public Person {
public:
	Student(string n, char s, int a, int num, int cn, int math, int eng, int chin);
	Student(Student &st);
	void input();
	void display();
private:
	int number, classnum, Math, English, Chinese;
};
Student::Student(string n, char s, int a, int num, int cn, int math, int eng, int chin) :Person(n, s, a) {
	Math = math;
	English = eng;
	Chinese = chin;
	number = num;
	classnum = cn;
}
Student::Student(Student &stu):Person(stu) {
	number = stu.number;
	classnum = stu.classnum;
	Math = stu.Math;
	English = stu.English;
	Chinese = stu.Chinese;
}
void Student::input() {
	Person::input();
	cout << "學號:";
	cin >> number;
	cout << "班級:";
	cin >> classnum;
	cout << "數學、英語、語文:" << endl;
	cin >> Math >> English >> Chinese;
}
void Student::display() {
	Person::display();
	cout << ", 學號:" << number << ", 班級:" << classnum << ", 數學:" << Math << ", 英語:" << English << ", 語文:" << Chinese << endl;
}
int main() {
	Student stu1("小王",'m',20,20200509,1,90,89,99);
	stu1.display();

	Student stu2(stu1);
	stu2.display();

	stu1.input();
	stu1.display();
	return 0;
}

運行結果:
Alt

五.同名隱藏規則和二義性問題

1.同名隱藏規則

當派生類與基類中有相同成員時:

  • 若未強行指名,則通過派生類對象使用的是派生
    類中的同名成員
  • 如要通過派生類對象訪問基類中被覆蓋的同名成
    員,應使用基類名限定。
#include <iostream>
using namecpace std;
class B1	//聲明基類B1
{ 
public:	
	int number;
	void fun()  {cout<<"Member of B1"<<endl;}
};
class D1: public B1	 
{ 
public:
	int number;	//同名數據成員
	void fun() {cout<<"Member of D1"<<endl;}	//同名函數成員
};
int main()
{	
    D1 d1;
	d1.number=1;  //對象名.成員名標識, 訪問D1類成員
	d1.fun();	         
	d1.B1::number=2;  //作用域分辨符標識, 訪問基類B1成員
	d1.B1::fun();	
}

2.二義性問題

多繼承時基類與派生類之間,或基類之間出現同名成員時,將出現訪問時的二義性(不確定性)——採用虛函數同名隱藏規則來解決。

派生類從多個基類派生,而這些基類又從同一個基類派生,則在訪問此共同基類中的成員時,將產生二義性——採用虛基類來解決。

舉例一:

class A
{
public:
	void f();
};
class B
{
public:
	void f();
	void g();
}
class C:public A, public B
{
public:
	void g();
};
int main()
{   C  c1;
	c1.f(); //二義性, 基類之間同名函數
	c1.g(); //無二義性,同名隱藏
	
	//解決方法1:用類名限定
	c1.A::f()  或  c1.B::f()
	//解決方法2:同名覆蓋
	在C中聲明一個同名成員函數f()
	在其中調用A::f()  或  B::f()
}

舉例二:

class Employee
{         
public:
     int salary;
}; 
class salesMan : public Employee
{
private:
     int a;
};
class manager : public Employee
{
private:
     int b;
};
class salesManager: public salesMan,public manager
{
public:
     int f();
private:
     int c;
};
int main()
{   salesManager s1;
	s1.salary;    //報錯,有二義性
	s1.Employee::salary;   //報錯,有二義性
	s1.salesMan::salary; //無二義性
	s1.manager::salary; //無二義性
}

六.虛基類

1.虛基類介紹

(1) 用於有共同基類的場合。 在第一級繼承時就要將共同基類設計爲虛基類
(2) 關鍵字 virtual
如: class B1: virtual public B
(3) 作用:

  • 主要用來解決多繼承時可能發生的對同一基類繼承多次而產生的二義性問題.
  • 爲最遠的派生類提供唯一的基類成員,而不重複產生多次拷貝

舉例說明:

class B{ public: int b; void fun(){} };
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class D1 : public B1, public B2{ private: int b3;}

//下面的訪問是正確的:
D1 d;
d.b; //使用最遠基類成員
d.fun();//使用最遠基類成員

Alt

B1
B
B2
D1

2. 虛基類及其派生類構造函數

(1) 建立對象時所指定的類稱爲最遠派生類
(2) 虛基類的成員是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化的。
(3) 在建立對象時,只有最遠派生類的構造函數調用虛基類的構造函數,該派生類的其它基類對虛基類構造函數的調用被忽略。
(4) 在整個繼承結構中,直接或間接繼承虛基類的所有派生類,都必須在構造函數的成員初始化表中給出對虛基類的構造函數的調用。如果未列出,則表示調用該虛基類的缺省構造函數。

舉例說明:
定義Person(人員類),包含姓名、年齡、性別、地址、電話等數據成員。再分別定義派生類Teacher(教師)類Cadre(幹部)類,然後採用多重繼承方式由這兩個類派生出新類Teacher_ Cadre(教師兼幹部)類
要求:
① 在Teacher類中還包含數據成員title(職稱),在Cadre類中還包含數據成員post(職務),在Teacher Cadre類中還包含數據成員wages(工資)。
② 在類體中聲明成員函數,在類外定義成員函數。
③輸出Teachers_Cadre類對象的姓名、年齡、性別、職稱、地址、電話、職務與工資。
④使用對象數組保存輸入的對象

#include<iostream>   
#include<string>   
using namespace std;
class Person 
{
public:
	Person(string n, int a, char s, string add, int te);
	void show();
private:
	string name;
	int age;
	char sex;
	string address;
    int tel;
};
Person::Person(string n, int a, char s, string add, int te) 
{
	name = n;
	age = a;
	sex = s;
	address = add;
	tel = te;
}
void Person::show() 
{
	cout << "姓名:" << name << " 年齡:" << age << " 性別:" << sex << " 地址:" << address << " 電話:" << tel;
}

class Teacher:virtual public Person
{
public:
	Teacher(string n, int a, char s, string add, int te, string ti);
	void show();
	void getTitle();
private:
	string title;  
};
Teacher::Teacher(string n, int a, char s, string add, int te, string ti):Person(n,a,s,add,te)
{
	title = ti;
}
void Teacher::show() 
{
	Person::show();
	cout << " 職稱:" << title;
}
void Teacher::getTitle() {
	cout << " 職稱:" << title;
}
class Cadre:virtual Person
{
public:
	Cadre(string n, int a, char s, string add, int t, string p);
	void show();
	void getPost();
private:
	string post;  
};
Cadre::Cadre(string n, int a, char s, string add, int te, string p):Person(n, a, s, add, te)
{
	post = p;
}
void Cadre::show() 
{
	Person::show();
	cout << " 職務:" << post;
}
void Cadre::getPost() 
{
	cout << " 職務:" << post;
}
class Teacher_Cadre :public Teacher, public Cadre  
{  // 聲明多重繼承的Teacher_Cadre類
public:
	Teacher_Cadre(string n, int a, char s, string add, int te, string ti, string p, double w);
	void show();
private:
	double wages;
};
Teacher_Cadre::Teacher_Cadre(string n, int a, char s, string add, int te, string ti, string p, double w):Person(n, a, s, add, te),Teacher(n, a, s, add, te,ti),Cadre(n, a, s, add, te,p)
{
	wages = w;
}
void Teacher_Cadre::show()  
{
	Person::show();
	Teacher::getTitle();
	Cadre::getPost();
	cout << " wages: " << wages << endl;

}
int main()
{
	Teacher_Cadre *tc = new Teacher_Cadre[3]{
		Teacher_Cadre("zhangsan", 30, 'm', "廣C213", 85290111,"tutor(助教)","普通職工",6000),
		Teacher_Cadre("lisi", 40, 'm', "廣C504", 85290222,"associate professor(副教授)","副院長",8000),
		Teacher_Cadre("wangwu", 32, 'f', "廣C502", 85290333,"lectuer(講師)","教研室主任",7000)
	};
	for (int i = 0;i < 3;i++) {
		tc[i].show();
	}
	delete[]tc;
	return 0;
}

運行結果
在這裏插入圖片描述

歡迎關注微信公衆號:學編程的金融客,作者:小笨聰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章