C++——繼承與派生
1、類的繼承與派生 保持已有類的特性而構造新類的過程成為繼承;
在已有類的基礎上新增自己的特性而產生新類的過程稱為派生;
被繼承的已有類為基類;派生出的新類成為派生類。繼承和派生其實是一回事。
繼承的目的是實現代碼的重用,派生的目的是當新的問題出現的時候,原有的程序不能解決時,需要對原程序進行改造。派生類的聲明: class 派生類名:繼承方式 基類名{成員聲明;}
不同的繼承方式的影響主要體現在:派生類成員對基類成員的訪問權限;通過派生類對象對基類成員的訪問權限
三種繼承方式:公有、私有和保護。
公有繼承:基類的public和protected成員的訪問屬性在派生類中保持不變,但基類的private成員不可以直接訪問;
派生類的成員函數可以直接訪問基類中public和protected成員,但不能直接訪問基類的private成員;
通過派生類的對象只能訪問基類的public成員。
//Rectangle.h
class Point //基類Point類的聲明
{
public: //公有函數成員
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private: //私有數據成員
float X,Y;
};
class Rectangle: public Point //派生類聲明部分
{
public: //新增公有函數成員
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;} //調用基類公有成員函數
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有數據成員
float W,H;
};//End of Rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
Rectangle rect; //聲明Rectangle類的對象
rect.InitR(2,3,20,10); //設置矩形的數據
rect.Move(3,2); //移動矩形位置
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //輸出矩形的特征參數
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
私有繼承:基類的public和protected成員以private身份出現在派生類中,但是基類的private成員不可以直接訪問;派生類的成員函數可以直接訪問基類的public和protected成員,但不能直接訪問private成員;通過派生類的對象不能直接訪問基類中的任何成員。
//rectangle.h
class Point //基類聲明
{
public:
void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
float GetX() {return X;}
float GetY() {return Y;}
private:
float X,Y;
};
class Rectangle: private Point //派生類聲明
{
public: //新增外部接口
void InitR(float x, float y, float w, float h)
{InitP(x,y);W=w;H=h;} //派生類訪問基類公有成員
void Move(float xOff, float yOff) {Point::Move(xOff,yOff);}//自己重新定義一個move函數,其實是間接調用基類函數,
//為的是方便在類外(主函數中)使用move函數。
float GetX() {return Point::GetX();}
float GetY() {return Point::GetY();}
float GetH() {return H;}
float GetW() {return W;}
private: //新增私有數據
float W,H;
};
//End of rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
Rectangle rect; //聲明Rectangle類的對象
rect.InitR(2,3,20,10); //設置矩形的數據
rect.Move(3,2); //移動矩形位置,使用的是派生類的函數。
cout<<"The data of rect(X,Y,W,H):"<<endl;
cout<<rect.GetX()<<"," //輸出矩形的特征參數
<<rect.GetY()<<","
<<rect.GetW()<<","
<<rect.GetH()<<endl;
}
保護繼承:基類的public和protected成員都已protected身份出現在派生類中,但基類的private成員不可直接訪問;派生中成員函數可以直接訪問類中的public和protected成員,但是不可以直接訪問private成員;通過派生類的對象不能直接訪問基類的任何成員。
protected成員的特點與作用:在類外通過類的對象是不可以訪問的,對於派生類來說,在派生類中是與public一樣的,既實現了數據隱藏,又方便繼承,實現了代碼重用。
class A{protected:int x;} …… class A{protected:int x;} class B:public A{public: void f();}
int main(){ A a; a.x=5;//錯誤} …… void B:f(){x=5;}//正確
類型兼容規則:一個公有派生類的對象在使用上被當做基類的對象,反之則禁止。
表現在:(輪胎和汽車的關系),派生類的對象含有的信息大於基類。
派生類的對象可以被賦值給基類對象;
派生類的對象可以初始化基類的引用;
指向基類的指針也可以指向派生類。
通過基類的對象名、指針只能使用從基類繼承的成員。
#include <iostream>
using namespace std;
class B0 //基類B0聲明
{
public:
void display(){cout<<"B0::display()"<<endl;} //公有成員函數
};
class B1: public B0 //公有派生類B1聲明
{
public:
void display(){cout<<"B1::display()"<<endl;} //公有成員函數
};
class D1: public B1 //公有派生類D1聲明
{
public:
void display(){cout<<"D1::display()"<<endl;} //公有成員函數
};
void fun(B0 *ptr) //普通函數
{ //參數為指向基類對象的指針
ptr->display(); //"對象指針->成員名"
}
int main() //主函數
{
B0 b0; //聲明B0類對象
B1 b1; //聲明B1類對象
D1 d1; //聲明D1類對象
B0 *p; //聲明B0類指針
p=&b0; //B0類指針指向B0類對象
fun(p);
p=&b1; //B0類指針指向B1類對象
fun(p);
p=&d1; //B0類指針指向D1類對象
fun(p);
}//運行結果:
B0::display()
B0::display()
B0::display()
多繼承時派生類的聲明:class 派生類名:繼承方式1 基類名1,繼承方式2 基類2,……{成員聲明;};
每一個繼承方式只用於限制對緊隨其後的基類的繼承。
class A{
public:
void setA(int);
void showA();
private:
int a;
};
class B{
public:
void setB(int);
void showB();
private:
int b;
};
class C : public A, private B{
public:
void setC(int, int, int);
void showC();
private:
int c;
};
void A::setA(int x)
{ a=x; }
void B::setB(int x)
{ b=x; }
void C::setC(int x, int y, int z)
{ //派生類成員直接訪問基類的
//公有成員
setA(x);
setB(y);
c=z;
}
//其他函數實現略
int main()
{
C obj;
obj.setA(5);
obj.showA();
obj.setC(6,7,9);
obj.showC();
// obj.setB(6); 錯誤,私有繼承的B,在類外不可以直接訪問。
// obj.showB(); 錯誤
return 0;
}
繼承時的構造函數:基類的構造函數是不被繼承的,因為基類的構造函數不足以為派生類新增的成員初始化,派生類需要聲明自己的構造函數,聲明構造函數時,只需要對本類中的新增成員初始化,對繼承來的基類的成員初始化,自動調用基類的構造函數完成;派生類的構造函數需要給基類的構造函數傳遞參數。
單繼承:派生類名::派生類名(基類所需形參,本類成員所需形參)基類名(參數表){本類成員初始化;};
多繼承:派生類名::派生類名(基類1形參,...基類n形參,本類形參):基類名1(參數),...基類名n(參數){本類成員初始化賦值語句;};
當基類中聲明有默認形式的構造函數或者沒有聲明構造函數時,派生類構造函數可以不向基類構造函數傳遞參數;若基類沒有聲明構造函數,派生類中也可以不聲明,全部采用默認構造函數;當基類中聲明有帶形參的構造函數時,派生類也應聲明帶形參的構造函數,並將參數傳遞給基類的構造函數。
多繼承且有內嵌對象(組合類對象成員)時的構造函數:派生類名::派生類名(基類1形參,...基類n形參,本類形參):基類名1(參數),...基類名n(參數),對象數據成員的初始化{本類成員初始化賦值語句;};
構造函數的調用順序:調用基類構造函數,調用順序按照它們被繼承時聲明的順序(從左到右);調用成員對象的構造函數,調用順序按照它們在類中的聲明順序;派生類的構造函數體中的內容。
拷貝構造函數:若建立派生類對象時調用默認拷貝構造函數,則自動調用基類的默認構造函數;若編寫派生類的拷貝構造函數,則需要為基類相應的拷貝構造函數傳遞參數。C::C(C&b):B(b){};
#include <iostream>
using namespace std;
class B1 //基類B1,構造函數有參數
{
public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;}
};
class B2 //基類B2,構造函數有參數
{
public:
B2(int j) {cout<<"constructing B2 "<<j<<endl;}
};
class B3 //基類B3,構造函數無參數
{
public:
B3(){cout<<"constructing B3 *"<<endl;}
};
class C: public B2, public B1, public B3 //派生新類C
//註意基類名的順序
{
public: //派生類的公有成員
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//註意基類名的個數與順序
//註意成員對象名的個數與順序
private: //派生類的私有對象成員
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{
C obj(1,2,3,4);//傳遞參數順序,首先基類:B2(b),B1(a),B3不要參數,接著是內嵌成員memberB1,memberB2,memberB3
}//運行結果:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
析構函數也不被繼承,需要自行聲明,聲明方法與一般析構函數一樣,不需要顯式的調用基類的析構函數,系統會自動調用,析構函數的調用順序和構造函數相反。
#include <iostream>
using namespace std;
class B1 //基類B1聲明
{
public:
B1(int i) {cout<<"constructing B1 "<<i<<endl;} //B1的構造函數
~B1() {cout<<"destructing B1 "<<endl;} //B1的析構函數
};
class B2 //基類B2聲明
{
public:
B2(int j) {cout<<"constructing B2 "<<j<<endl;} //B2的構造函數
~B2() {cout<<"destructing B2 "<<endl;} //B2的析構函數
};
class B3 //基類B3聲明
{
public:
B3(){cout<<"constructing B3 *"<<endl;} //B3的構造函數
~B3() {cout<<"destructing B3 "<<endl;} //B3的析構函數
};
class C: public B2, public B1, public B3 //派生類C聲明
{
public:
C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
//派生類構造函數定義
private:
B1 memberB1;
B2 memberB2;
B3 memberB3;
};
int main()
{ C obj(1,2,3,4);
}
//運行結果
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2
2、同名隱藏規則
當派生類中和基類中有相同成員時,若沒有強行指明,通過派生類對象使用的派生類的同名成員;如果通過派生類對象訪問基類中被覆蓋的同名成員,應使用基類名限定。
#include <iostream>
using namespace std;
class B1 //聲明基類B1
{
public: //外部接口
int nV;
void fun(){cout<<"Member of B1"<<endl;}
};
class B2 //聲明基類B2
{
public: //外部接口
int nV;
void fun(){cout<<"Member of B2"<<endl;}
};
class D1: public B1, public B2 //聲明派生類D1
{
public:
int nV; //同名數據成員
void fun(){cout<<"Member of D1"<<endl;} //同名函數成員
};
int main()
{
D1 d1;
d1.nV=1; //對象名.成員名標識
d1.fun(); //訪問D1類成員
d1.B1::nV=2; //作用域分辨符標識
d1.B1::fun(); //訪問B1基類成員
d1.B2::nV=3; //作用域分辨符標識
d1.B2::fun(); //訪問B2基類成員
}
二義性:在多繼承時,基類和派生類之間,或者基類之間出現同名成員,將出現訪問時的二義性(不確定性),這種情況采用虛函數或者同名隱藏規則來解決;當派生類從多個基類派生,而這些基類又從同一個基類派生,則在訪問此共同基類中的成員時,將產生二義性,采用虛函數解決。
class A
{
public:
void f();
};
class B
{
public:
void f();
void g()
};
class C: public A, piblic B
{ public:
void g();
void h();
};
如果聲明:C c1;
則 c1.f(); 具有二義性,解決方法1:用類名限定c1.A::f(); or c1.B::f();解決方法2:同名覆蓋在c中聲明一個同名函數f(),在根據需要調用A::f or B::f.
而 c1.g(); 無二義性(同名覆蓋)
class B
{ public: ……
int b; ……
} ……
class B1 : public B ……
{ ……
private: ……
int b1; ……
} ……
class B2 : public B ……
{ ……
private: ……
int b2; ……
}; ……
class C : public B1,public B2……
{ ……
public: ……二義性 無二義性
int f(); …… C c; c.B1::b;
private: ……c.b; c.B2::b;
int d; ……c.B::b;
} //不光產生二義性,還將基類成員b產生了多次拷貝。 ……
虛基類:主要用於有共同基類的場合。
聲明用virtual修飾基類;如class B1:virtual public B,其作用是用來解決多繼承時可能發生的對同一基類繼承多次而產生的二義性問題;為最遠的派生類提供唯一的基類成員,而不重復產生多次拷貝。
註意:在第一級繼承時就要將共同基類設計成虛基類。
class B{ private: int b;};
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class C : public B1, public B2{ private: float d;}
下面的訪問是正確的:
C cobj;
cobj.b;
虛基類的派生類對象存儲結構示意圖:
#include <iostream>
using namespace std;
class B0 //聲明基類B0
{ public: //外部接口
int nV;
void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0 //B0為虛基類,派生B1類
{ public: //新增外部接口
int nV1;
};
class B2: virtual public B0 //B0為虛基類,派生B2類
{ public: //新增外部接口
int nV2;
};
class D1: public B1, public B2 //派生類D1聲明
{ public: //新增外部接口
int nVd;
void fund(){cout<<"Member of D1"<<endl;}
};
int main() //程序主函數
{ D1 d1; //聲明D1類對象d1
d1.nV=2; //使用最遠基類成員的唯一一份拷貝
d1.fun();
}
虛基類和派生類構造函數:建立對象時所指定的類成為最(遠)派生類,虛基類的成員是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化的;在整個繼承結構中,直接或間接繼承虛基類的所有派生類都必須在構造函數成員初始化表中給出對虛基類構造函數的調用,否則表示調用該虛基類的默認構造函數;在建立對象時,只有最遠派生類的構造函數調用虛基類的構造函數,該派生類的其他基類對虛基類構造函數的調用被忽略;
#include <iostream>
using namecpace std;
class B0 //聲明基類B0
{ public: //外部接口
B0(int n){ nV=n;}
int nV;
void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0
{ public:
B1(int a) : B0(a) {}
int nV1;
};
class B2: virtual public B0
{ public:
B2(int a) : B0(a) {}
int nV2;
};
class D1: public B1, public B2
{
public:
D1(int a) : B0(a), B1(a), B2(a){}
int nVd;
void fund(){cout<<"Member of D1"<<endl;}
};
int main()
{
D1 d1(1);
d1.nV=2;
d1.fun();
}
C++——繼承與派生