1. 概念
類的繼承,是新的類從已有類那裏得到已有的特性。或從已有類產生新類的過程就是類的派生。原有的類稱爲基類或父類,產生的新類稱爲派生類或子類。
2. 繼承方式
繼承方式規定了如何訪問基類繼承的成員。繼承方式有public, protected, private。繼承方式不影響派生類內原有數據成員的訪問權限,影響了從基類繼承來的成員的訪問權限,包括派生類內的訪問權限和派生類對象。
圖1 繼承方式與成員訪問屬性
派生類的組成:派生類中的成員,包含兩大部分,一類是從基類繼承過來的,一類是自己增加的成員。
從基類繼承過過來的表現其共性,而新增的成員體現了其個性。除了構造器與析構器,派生類全盤接收。基類有可能會造成派生類的成員冗餘,所以說基類是需設計的。派生類有了自己的個性,使派生類有了意義。
3. 派生類的構造
派生類中由基類繼承而來的成員的初始化工作還是由基類的構造函數完成,派生類中新增的成員在派生類的構造函數中初始化。
格式:
派生類名::派生類名(參數總表)
:基類名(參數表),內嵌子對象(參數表)
{
派生類新增成員的初始化語句; //也可出現在參數列表中
}
規則:
- 構造函數的初始化順序並不以上面的順序進行,而是根據聲明的順序初始化;
- 如果基類中沒有默認構造函數(無參),那麼在派生類的構造函數中必須顯示調用基類構造函數,以初始化基類成員。
- 派生類構造函數執行的次序:基類->成員(有可能也是類對象)->子類;
- 子類構造器中,要麼顯示的調用父類的構造器(傳參),要麼隱式的調用。發生隱式調用時,父類要有無參構造器或是可以包含無參構造器的默認參數函數;
4. 派生類的拷貝構造
格式:
派生類::派生類(const 派生類& another)
:基類(another),派生類新成員(another.新成員)
{
函數體;
}
拷貝構造的例子:
#include <iostream>
using namespace std;
class Student {
public:
Student(int n,char s);
Student(const Student &another);
~Student(){}
void dis();
private:
int num;
char sex;
};
Student::Student(int n, char s) //基類的構造函數
:num(n),sex(s){
}
void Student::dis(){
cout<<num<<endl;
cout<<sex<<endl;
}
Student::Student(const Student &another){ //基類的拷貝構造函數
num = another.num;
sex = another.sex;
}
class Graduate: public Student {
public:
Graduate(int in,char cs,float fs);
~Graduate(){}
Graduate(const Graduate & another);
void dump() {
dis();
cout<<salary<<endl;
}
private:
float salary;
};
Graduate::Graduate(int in, char cs, float fs)//派生類的構造函數
:Student(in,cs),salary(fs) {
}
Graduate::Graduate(const Graduate & another) //派生類的拷貝構造函數
:Student(another),salary(another.salary) {
}
int main()
{
Graduate g(2001,'x',2000);
g.dump();
cout<<"----------------------"<<endl;
Graduate gg = g;
gg.dump();
return 0;
}
看一下Graduate類的內存佈局:
Student類的內存佈局:
派生類中的默認拷貝構造器會調用父類中默認或自實現拷貝構造器(也就是說派生類如果沒有顯示的拷貝構造器,它的默認的拷貝構造器也會去調用基類的默認或自實現的拷貝構造器),若派生類中自實現拷貝構造器,則必須顯示的調用父類的拷貝構造器。
5. 派生類的賦值運算符重載
賦值運算符函數不是構造器,所以可以繼承,語法上就沒有構造器的嚴格一些。
格式:
子類& 子類::operator=(const 子類& another)
{
if(this == &another)
return *this; //防止自賦值
父類::operator =(another); // 調用父類的賦值運算符重載
//....... //子類成員初始化
return * this;
}
例子:
#include <iostream>
using namespace std;
class Student {
public:
Student(string sn,int n,char s);
~Student() {}
Student & operator=(const Student & another);
void dis();
private:
string name;
int num;
char sex;
};
Student::Student(string sn, int n, char s) //基類的構造函數
:name(sn),num(n),sex(s){
}
Student &Student::operator=(const Student & another) {
cout<<"基類賦值運算符重載"<<endl;
this->name = another.name;
this->num = another.num;
this->sex = another.sex;
return * this;
}
void Student::dis() {
cout<<name<<endl;
cout<<num<<endl;
cout<<sex<<endl;
}
class Graduate: public Student {
public:
Graduate(string sn,int in,char cs,float fs);
~Graduate() {}
Graduate & operator=(const Graduate & another);
void dump() {
dis();
cout<<salary<<endl;
}
private:
float salary;
};
Graduate::Graduate(string sn, int in, char cs, float fs)//派生類的構造函數
:Student(sn,in,cs),salary(fs) {
}
Graduate & Graduate::operator=(const Graduate & another) {
cout<<"派生類賦值運算符重載"<<endl;
if(this == &another)
return *this;
Student::operator =(another); //基類的賦值運算符
this->salary = another.salary;
return * this;
}
int main() {
Graduate g("liuneng",2001,'x',2000);
g.dump();
Graduate gg = g; //調用默認拷貝構造函數
gg.dump();
cout<<"-----------"<<endl;
Graduate ggg("gege",2001,'x',4000);
ggg.dump();
ggg = g; //調用賦值運算符重載函數
ggg.dump();
return 0;
}
派生類的默認賦值運算符重載函數會調用父類的默認或自實現函數。派生類若自實現,則不會發生調用行爲,也不報錯(區別拷貝),賦值錯誤,若要正確,需要顯示的調用父類的構造器。
6. 派生類友元函數
由於友元函數並非類成員,因引不能被繼承,在某種需求下,可能希望派生類的友元函數能夠使用基類中的友元函數。爲此可以通過強制類型轉換,將派生類的指針或是引用強轉爲其類的引用或是指針,然後使用轉換後的引用或是指針來調用基類中的友元函數。
例子:
#include <iostream>
using namespace std;
class Student {
public:
Student(int _a, int _b):a(_a),b(_b){}
friend ostream &operator<<(ostream & out, Student & stu);
private:
int a;
int b;
};
ostream &operator<<(ostream & out, Student & stu) {
out<<stu.a<<"--"<<stu.b<<endl;
}
class Graduate:public Student {
public:
Graduate(int _a, int _b, int _c, int _d):Student(_a, _b),c(_c),d(_d){}
friend ostream &operator<<(ostream & out, Graduate & gra);
private:
int c;
int d;
};
ostream &operator<<(ostream & out, Graduate & gra) {
out<<(Student&)gra<<endl;
out<<gra.c<<"++"<<gra.d<<endl;
}
int main() {
Graduate g(3, 4, 5, 6);
cout<<g<<endl;
return 0;
}
7. 派生類析構函數的語法
派生類的析構函數的功能是在該對象消亡之前進行一些必要的清理工作,析構函數沒有類型,也沒有參數。析構函數的執行順序與構造函數相反。
析構順序:子類 -> 成員 ->基類。
8. 多繼承
從繼承類別上分,繼承可分爲單繼承和多繼承,上面總結的都是單繼承。
格式:
派生類名::派生類名(參數總表)
:基類名1(參數表1),基類名(參數名2)....基類名n(參數名n),
內嵌子對象1(參數表1),內嵌子對象2(參數表2)....內嵌子對象n(參數表n)
{
派生類新增成員的初始化語句;
}