定義
繼承,是從已經存在的類中獲得屬性,再此基礎之上添加自己屬性的過程。原有的類成爲基類或父類,產生的新類則爲派生類或子類。
class deriveClass:deriveMode baseClass
{
statement;
}
當然也可以從多個基類中進行派生,此時叫多重繼承。從一個基類中進行派生,稱爲單繼承。
繼承方式
- 有三種繼承方式:public,protected,private
- 繼承方式規定了訪問基類成員的方式
- 影響派生類從基類繼承成員的訪問權限,但不影響派生類成員的訪問權限
- 需要注意的是,派生類會全部繼承基類的成員,因此在定義時需要特別注意
成員\繼承方式 | public | protected | private |
public | public | protected | private |
protected | protected | protected | inaccessable |
private | inaccessable | inaccessable | inaccessable |
- 雖然派生類能夠繼承基類中的全部成員,但是對於基類中的成員卻存在四種訪問方式:public,protected,private,inaccessable
- public 繼承:基類中的公有成員和保護成員的訪問屬性在派生類中不變,私有成員不可訪問
- protected 繼承:基類中的公有成員和保護成員的訪問屬性在派生類中都爲 protected,私有成員不可訪問
- private 繼承:基類中的公有成員和保護成員的訪問屬性在派生類中爲 private,私有成員不可訪問
派生類構造
派生類構造函數分爲兩部分,一部分是從基類繼承來的成員的初始化,此部分初始化由基類的構造函數完成,另一部分則是派生類自身的成員,此部分初始化由派生類的構造函數完成。
格式
deriveClass::dericeClass(argument):baseClass(argument),var(argument)
{
statement;
}
- 上述派生類構造函數格式中的 var 表示派生類新增的數據成員,函數體當然也就是派生類新增的數據成員
- 構造函數的初始化順序同樣不是根據參數初始化列表的順序,而是根據數據成員的聲明順序
- 基類中如果不存在無參形式的構造(無參或默認),那麼在派生類的構造函數中需要顯示調用基類的構造函數,對基類成員進行初始化
- 如果含有多層類繼承關係,各個構造函數之間的執行順序爲:
- 調用基類構造函數,調用順序爲被繼承時的聲明順序(多重繼承)
- 調用內嵌成員對象的構造函數,調用順序爲在對應類中聲明的順序
- 派生類的構造函數
實例
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
using std::cout;
using std::endl;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
void displayp();
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
private:
char * name;
char sex;
};
class JOB
{
public:
JOB(char * name_ = "***"):name(name_)
{
cout<<"JOB(char * name_ = \"***\"):name(name_)"<<endl;
}
void displayj();
~JOB()
{
cout<<"~JOB()"<<endl;
}
private:
char * name;
};
class CLUB
{
public:
CLUB(char * name_ = "***"):name(name_)
{
cout<<"CLUB(char * name_ = \"***\"):name(name_)"<<endl;
}
void displayc();
~CLUB()
{
cout<<"~CLUB()"<<endl;
}
private:
char * name;
};
class STUDENT:public PERSON
{
public:
STUDENT(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***")
:PERSON(name_,sex_),num(num_),club(club_)
{
cout<<"STUDENT(char * name_ = \"***\",char sex_ = '*',char *num_ = \"***\",char * club_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_ = "***",char * club_ = "***"):PERSON(person_),num(num_),club(club_){}
void displays();
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
CLUB club;
};
class POSTGRADUATE:public STUDENT,public JOB
{
public:
POSTGRADUATE(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***",char *job_ = "***",int salary_ = 0)
:STUDENT(name_,sex_,num_,club_),JOB(job_),salary(salary_)
{
cout<<"POSTGRADUATE(char * name_ = \"***\",char sex_ = '*',char *num_ = \"***\",char * club_ = \"***\",char *job_ = \"***\",int salary_ = 0)"<<endl;
}
POSTGRADUATE(STUDENT student,JOB job,int salary_ = 0):STUDENT(student),JOB(job),salary(salary_){}
void displayps();
~POSTGRADUATE()
{
cout<<"~POSTGRADUATE()"<<endl;
}
private:
int salary;
};
#endif // MYCLASS_H
// myclass.cpp
#include <iostream>
#include "myclass.h"
using std::cout;
using std::endl;
void PERSON::displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
void JOB::displayj()
{
cout<<"The job name is "<<name<<"."<<endl;
}
void CLUB::displayc()
{
cout<<"The club name is "<<name<<"."<<endl;
}
void STUDENT::displays()
{
displayp();
club.displayc();
cout<<"The student num is "<<num<<"."<<endl;
}
void POSTGRADUATE::displayps()
{
displays();
displayj();
cout<<"The postgarduate saraly is "<<salary<<"."<<endl;
}
// main.cpp
#include <iostream>
#include "myclass.h"
using namespace std;
int main()
{
POSTGRADUATE st("zhangsan",'x',"math","100","paper",100);
st.displayps();
return 0;
}
結果爲:
PERSON(char * name_ = "***",char sex_ = '*')
CLUB(char * name_ = "***"):name(name_)
STUDENT(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***")
JOB(char * name_ = "***"):name(name_)
POSTGRADUATE(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***",char *job_ = "***",int salary_ = 0)
The person name is zhangsan.
The person sex is x.
The club name is 100.
The student num is math.
The job name is paper.
The postgarduate saraly is 100.
~POSTGRADUATE()
~JOB()
~STUDENT()
~CLUB()
~PERSON()
在上邊的例子中,我們構造了兩個基類 PERSON 和 JOB,其中 PERSON 派生出 STUDENT,STUDENT 和 JOB 派生出 POSTGRADUATE,在 STUDENT 中還存在一個 CLUB 的成員,從上邊的結果可以看出:
- 雖然我們只定義了一個 POSTGRADUATE,但卻調用了包括該派生類和基類的構造函數
- 派生類的構造順序爲:基類->類成員->派生類
- 上邊結果中第一行爲基類 STUDENT 的基類 PERSON 的構造函數
- 上邊結果中第二行爲基類 STUDENT 的成員 CLUB 對象的構造函數
- 上邊結果中第三行爲基類 STUDENT 的構造函數
- 上邊結果中第四行爲基類 JOB 的構造函數
- 上邊結果中第五行爲派生類 POSTRADUATE 的構造函數
- 調用函數 st.displayps() 時按次序調用各自的 display()
- 析構函數的調用順序與構造函數的調用順序相反
- 需要注意的是,在子類的構造函數中,要麼顯示調用父類的構造器,要麼就會進行隱式調用。而爲了隱式調用時程序能夠正常運行,在基類中需要定義構造函數的無參類型或者默認類型。上邊的程序中就是採用了默認參數的形式
派生類的拷貝構造
定義
deriveclass::deriveclass(const deriveclass &obj):baseclass(obj),deriveclass_var(obj.var)
{
statement;
}
實例
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex)
{
cout<<"PERSON(const PERSON &obj)"<<endl;
}
void displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
private:
char * name;
char sex;
};
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
:PERSON(name_,sex_),num(num_)
{
cout<<"STUDENT(char *name_ = \"***\",char sex_ = \"***\",char *num_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_= "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj)
:PERSON(obj),num(obj.num)
{
cout<<"STUDENT(const STUDENT &obj)"<<endl;
}
void displays()
{
displayp();
cout<<"The student num is "<<num<<"."<<endl;
}
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd(st);
sd.displays();
return 0;
}
結果爲:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = "***",char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(const PERSON &obj)
STUDENT(const STUDENT &obj)
The person name is zhangsan.
The person sex is x.
The student num is 100.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
上邊的結果爲:
- 定義的 STUDENT 對象 st 首先調用基類的構造函數,然後調用派生類的構造函數,最後打印
- 定義的 STUDENT 對象 sd 首先調用基類的拷貝構造函數,然後調用派生類的拷貝構造函數,最後打印
- 析構函數仍然與構造函數的調用順序相反
- 派生類中的默認拷貝構造函數會調用基類中的拷貝構造函數(默認或自實現)
- 派生類中如果自實現了拷貝構造函數,則必須顯式調用基類的拷貝構造函數(此時基類中的拷貝構造函數不一定是自實現的)
派生類的賦值運算符重載
賦值運算符重載本身不算是構造器,因此可以被繼承。
定義
deriveclass &deriveclass::operator=(const deriveclass &obj)
{
if(this == &obj)
return *this;
baseclass::operator=(obj);
this->var = obj->var;
return *this;
}
實例
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex)
{
cout<<"PERSON(const PERSON &obj)"<<endl;
}
PERSON &operator=(const PERSON &obj)
{
cout<<"PERSON &operator=(const PERSON &obj)"<<endl;
this->name = obj.name;
this->sex = obj.sex;
return *this;
}
void displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
private:
char * name;
char sex;
};
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
:PERSON(name_,sex_),num(num_)
{
cout<<"STUDENT(char *name_ = \"***\",char sex_ = '*',char *num_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_ = "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj)
:PERSON(obj),num(obj.num)
{
cout<<"STUDENT(const STUDENT &obj)"<<endl;
}
STUDENT &operator=(const STUDENT &obj)
{
cout<<"STUDENT &operator=(const STUDENT &obj)"<<endl;
if(this == &obj)
return *this;
PERSON::operator=(obj);
this->num = obj.num;
return *this;
}
void displays()
{
displayp();
cout<<"The student num is "<<num<<"."<<endl;
}
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd;
sd.displays();
sd= st;
sd.displays();
return 0;
}
結果爲:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is ***.
The person sex is *.
The student num is ***.
STUDENT &operator=(const STUDENT &obj)
PERSON &operator=(const PERSON &obj)
The person name is zhangsan.
The person sex is x.
The student num is 100.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
從上邊的結果可以看出,利用 = 重載可以實現派生類之間的相互賦值。
- 上邊 main 函數中語句如果變成:
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd= st;
sd.displays();
return 0;
}
結果爲:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(const PERSON &obj)
STUDENT(const STUDENT &obj)
The person name is zhangsan.
The person sex is x.
The student num is 100.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
可以發現在前者的實現中,並沒有調用賦值運算符重載函數。可以理解爲只有當對象存在時才能夠調用運算符重載函數。
- 派生類中的默認賦值運算符重載函數會調用基類中的賦值運算符重載函數(默認或自實現)
- 派生類中如果自實現了賦值運算符重載函數,則必須顯式調用基類的賦值運算符重載函數(此時基類中的賦值運算符重載函數不一定是自實現的)
派生類友元函數
之前我們說過友元函數可以是全局函數或者類函數,但兩者均不是類的成員函數,因此不能夠被繼承。但通過強制類型轉換,將派生類的指針或者引用強制轉換爲基類的指針或者引用,然後就可以使用轉換後的指針或者引用調用基類中的友元函數。
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex)
{
cout<<"PERSON(const PERSON &obj)"<<endl;
}
PERSON &operator=(const PERSON &obj)
{
cout<<"PERSON &operator=(const PERSON &obj)"<<endl;
this->name = obj.name;
this->sex = obj.sex;
return *this;
}
void displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
friend ostream &operator<<(ostream &out,const PERSON &obj);
private:
char * name;
char sex;
};
ostream &operator<<(ostream &out,const PERSON &obj)
{
out<<"The person name is "<<obj.name<<"."<<endl;
out<<"The person sex is "<<obj.sex<<"."<<endl;
return out;
}
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
:PERSON(name_,sex_),num(num_)
{
cout<<"STUDENT(char *name_ = \"***\",char sex_ = '*',char *num_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_ = "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj)
:PERSON(obj),num(obj.num)
{
cout<<"STUDENT(const STUDENT &obj)"<<endl;
}
STUDENT &operator=(const STUDENT &obj)
{
cout<<"STUDENT &operator=(const STUDENT &obj)"<<endl;
if(this == &obj)
return *this;
PERSON::operator=(obj);
this->num = obj.num;
return *this;
}
void displays()
{
displayp();
cout<<"The student num is "<<num<<"."<<endl;
}
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd("zhangsan",'x',"100");
cout<<sd;
return 0;
}
結果爲:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
上邊的程序沒有進行顯式轉換,但也調用了流輸出符。也就是其中經過了某種程度的強制轉換,但是在實際編程的時候,最好還是顯式轉換,爲了安全也爲了可讀性。
派生類析構函數
派生類的析構函數進行的工作也是和之前提到過的析構函數作用一樣:
- 進行內存的釋放
- 沒有返回值
- 沒有參數
- 沒有類型
- 析構函數的執行順序與構造函數的執行順序相反:派生類->成員類對象->基類
派生類成員的標識和訪問
作用域運算符
作用域除了可以用於命名空間中變量的定位之外,還能夠用來標識派生類中的成員。也可以說類的繼承關係本來就算做是命名空間的一部分。
如果派生類和基類中存在同名的成員,派生類成員會覆蓋掉同名的成員,因此就需要作用域運算符來解決這個問題。
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*'):name(name_),sex(sex_){}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex){}
void display()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
private:
char * name;
char sex;
};
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***"):PERSON(name_,sex_),num(num_){}
STUDENT(PERSON person_,char *num_ = "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj):PERSON(obj),num(obj.num){}
void display()
{
PERSON::display();
cout<<"The student num is "<<num<<"."<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.display();
return 0;
}
結果爲:
The person name is zhangsan.
The person sex is x.
The student num is 100.
在之前的程序中,我們在基類和派生類中在 display 後加上不重名的字符來區分不同類中的 display 函數,但是這樣的函數命名不容易進行區分,因此可以使用作用域運算符進行限定標識,一樣能夠得到同樣的結果。
公有繼承
在我們之前提到的例子當中,都沒有見到過除了公有繼承之外的繼承方式,根據之前提到的繼承方式之間的差別:
- 基類中的私有成員不管怎麼被繼承,都不能被派生類訪問,基類中的私有成員只能被基類的成員函數訪問
- 公有繼承不管繼承多少級,都能夠保證基類中的 public 和 protected 訪問屬性不變,保證了數據的隱蔽性,接口傳遞和數據傳遞
- 保護繼承經過多次繼承之後,基類中的成員都不能在類外調用,保證了數據的隱蔽性和數據傳遞
- 私有繼承經過多次繼承之後,基類中的成員都不能被訪問,此時數據的隱蔽性就顯得沒有用了
多繼承
之前提到過,如果只是從一個類中進行派生就是單繼承,而如果要同時從多個類中進行派生,這就是多繼承問題了。
定義
class deriveclass:derivemode baseclass1,derivemode baseclass2,...
{
statement;
}
參數初始化列表
deriveclass::deriveclass(argument):baseclass1(argument),baseclass2(argument),classmember1(argument),classmember2(argument),...
{
statement;
}
實例
在最初的例子中,我們就簡單說到過多繼承的問題:
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
using std::cout;
using std::endl;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_){}
void display();
private:
char * name;
char sex;
};
class CLUB
{
public:
CLUB(char * name_ = "***"):name(name_){}
void display();
private:
char * name;
};
class STUDENT:public PERSON,public CLUB
{
public:
STUDENT(char * name_ = "***",char sex_ = '*',char * club_ = "***",char *num_ = "***")
:PERSON(name_,sex_),CLUB(club_),num(num_){}
STUDENT(PERSON person_,CLUB club_,char *num_ = "***"):PERSON(person_),CLUB(club_),num(num_){}
void display();
private:
char *num;
};
#endif // MYCLASS_H
// myclass.cpp
#include <iostream>
#include "myclass.h"
using std::cout;
using std::endl;
void PERSON::display()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
void CLUB::display()
{
cout<<"The club name is "<<name<<"."<<endl;
}
void STUDENT::display()
{
PERSON::display();
CLUB::display();
cout<<"The student num is "<<num<<"."<<endl;
}
// main.cpp
#include <iostream>
#include "myclass.h"
using namespace std;
int main()
{
STUDENT st("zhangsan",'x',"math","100");
st.display();
return 0;
}
結果爲:
The person name is zhangsan.
The person sex is x.
The club name is math.
The student num is 100.
上邊的例子中,STUDENT 類繼承了 PERSON 和 CLUB 類,兩個類中都含有 sidplay 函數,因此在這種問題中如果想要調用某個類中的該函數時,需要加上作用域運算符進行限定。
上邊的例子中說明的是上圖左邊的情況,此種情況下,派生類的基類之間沒有發生過交叉。但是如果是右圖中的繼承關係,就有點不太一樣了,此時 B 中含有 A 的成員,C 中也含有 A 的成員,D 同時含有 B 和 C 的成員,也就說含有 A 的兩份成員,這樣的情況是需要避免的。
#include <iostream>
using namespace std;
class A
{
public:
A(int i):data(i){}
int data;
};
class B:public A
{
public:
B(int d):A(d){}
void setData(int i){data = i;}
};
class C:public A
{
public:
C(int d):A(d){}
int getData(){return data;}
};
class D:public B,public C
{
public:
D():B(2),C(3){}
void dis()
{
cout<<B::data<<endl;
cout<<C::data<<endl;
}
};
int main()
{
D d;
d.dis();
d.setData(5);
cout<<d.getData()<<endl;
return 0;
}
結果爲:
2
3
3
上邊的例子中,我們想要改變借用 B 中的 setData 函數改變 D 中的 data 值,但是由於特殊的繼承結構卻得不到正確的結果。C++ 提出了虛繼承的概念來解決這個問題。
虛繼承
#include <iostream>
using namespace std;
class A
{
public:
A(int i):data(i){}
int data;
};
class B:virtual public A
{
public:
B(int d):A(d){}
void setData(int i){data = i;}
};
class C:virtual public A
{
public:
C(int d):A(d){}
int getData(){return data;}
};
class D:public B,public C
{
public:
D():B(2),C(3),A(10){}
void dis()
{
cout<<B::data<<endl;
cout<<C::data<<endl;
cout<<data<<endl;
}
};
int main()
{
D d;
d.dis();
d.setData(5);
cout<<d.getData()<<endl;
return 0;
}
結果爲:
10
10
10
5
上邊的程序中將 B 和 C 的繼承關係聲明爲 virtual,在 D 的構造函數中順便構造了 A 的參數列表,避免了二義性的出現。這種繼承關係就叫做虛繼承。
定義
class deriveclass:virtual derivemode baseclass
{
statement;
}
參數初始化列表
需要在交叉點處(上例爲 D)爲虛基類構建參數初始化列表。
D():B(2),C(3),A(10){}
作用
虛繼承作爲繼承方式的一種擴展,可以避免特殊繼承結構之間造成的多重數據成員問題,有效避免程序的二義性。