先上一段代碼:
#include<iostream>
using namespace std;
class Base{
public:
int a;
protected:
int b;
private:
int c;
};
struct Derived:public Base{
};
int main(){
Derived inst;
cout<<sizeof(Base)<<endl;
cout<<sizeof(Derived)<<endl;
cout<<sizeof(inst)<<endl;
cout<<inst.a<<endl;
return 0;
}
輸出:
12
12
12
15487104
對應上面的代碼,先啰嗦幾個與“繼承”無關的語法:struct可以繼承自一個class;sizeof()可以施加在一個類型上面,也可以施加在一個對象上面;由於inst是局部變量,所以類中的普通類型數據成員是不被初始化的,所以輸出a=15487104。
我們看到sizeof(Base)==sizeof(Derived)==12,可見Derived繼承了Base的public、protected、private成員。
struct Derived:public Base{
public:
void func(){
cout<<b<<endl;
}
};
int main(){
Derived inst;
inst.func();
return 0;
}
一般在類外是不能訪問protected成員的,所以在main()函數中調用cout<<inst.b;中會發生編譯錯誤的,但是我們可以把對protected成員的訪問封閉在一個public方法中,通過調用public方法來間接訪問protected數據成員。
struct Derived:public Base{
public:
void func(){
cout<<c<<endl;
}
};
上面代碼會有編譯錯誤,protected成員好歹在派生類中還可以訪問,private成員在派生類中都不能訪問。既然成員c在Derived中不能訪問,那sizeof(Derived)又何苦要等於12呢?以Derived分配了3個int的空間,但其中一個int卻是不可訪問的,有什么意義呢?
(1)公有繼承:基類成員保持自己的訪問級別:基類的public成員在派生類中還是public,基類的protected成員在派生類中還是protected。
(2)受保護繼承:基類的public和protected成員在派生類中成為protected成員。
(3)私有繼承:基類的所有成員在派生類中成為private成員。
struct Derived:private Base{
};
class Grandson:public Derived{
public:
void func(){
cout<<a<<endl;
}
};
上面的代碼有編譯錯誤,由於Derived私有繼承自Base,所以a在Derived中已經是private了,在Grandson中不能訪問Derived中的私有成員。
struct Derived:private Base{
public:
using Base::b;
protected:
using Base::a;
};
由於Derived私有繼承自Base,按說a、b、c在Derived中應該都是私有的,但通過上述方式,在Derived中b中公有的,a中受保護的。
struct Derived:Base
Derived為struct時默認為公有繼承。
class Derived:Base
Derived為class時默認為私有繼承。
友元類可以訪問private和protected成員數據,友元關系不能繼承。
#include<iostream>
using namespace std;
class Base{
friend class Frnd;
private:
int a;
};
class Derived:public Base{
};
class Frnd{
public:
void func(){
Base base;
cout<<base.a<<endl; //OK
Derived derived;
cout<<derived.a<<endl; //error
}
};
虛函數
當父類中的某個函數標記為virtual時,子類中如果重載了該方法則自動也具有virtual屬性,不管你是否使用了"virtual“關鍵字。比如下面的代碼:
#include<iostream>
using namespace std;
class A{
public:
virtual void func(){
cout<<"A.func"<<endl;
}
};
class B:public A{
public:
void func(){ //雖然沒有顯式寫明virtual,但該函數依然是虛函數
cout<<"B.func"<<endl;
}
};
class C:public B{
public:
virtual void func(){
cout<<"C.func"<<endl;
}
};
int main(){
A *a=new A();
A *b=new B();
A *c=new C();
a->func();
b->func();
c->func();
return 0;
}
輸出:
A.func
B.func
C.func
內部類可以訪問外部類的所有成員。
class Base{
public:
int a;
class Inner{
public:
void func(){
Base base;
cout<<base.c<<endl;
}
};
protected:
int b;
private:
int c;
};
int main(){
Base::Inner inst;
inst.func();
return 0;
}
注意在main函數中訪問Inner類時要加Base::前綴,另外Inner只有定義在Base的public域下時,在Base之外才可以訪問Inner。
#include<iostream>
using namespace std;
class Base{
public:
Base(){
cout<<"Base construct"<<endl;
}
virtual void foo(){
cout<<"Base foo"<<endl;
}
~Base(){
cout<<"Base destruct"<<endl;
}
};
class Derived:public Base{
public:
Derived(){
cout<<"Derived construct"<<endl;
}
virtual void foo(){
cout<<"Derived foo"<<endl;
}
~Derived(){
cout<<"Derived destruct"<<endl;
}
};
int main(){
Base *inst=new Derived(); //調用Derived的構造函數,在此之前會調用Base的構造函數
inst->foo(); //調用Derived的foo方法
delete inst; //只調用了Base的析構函數
}
通常情況下Derived擁有比Base更多的數據成員,在Derived的析構函數中需要釋放更多的資源,但是我們看到上面的代碼中Base和Derived的構造函數都調用了,最后卻只調用了Base的析構函數。解決方法:Base中使用虛的析構函數。
#include<iostream>
using namespace std;
class Base{
public:
Base(){
cout<<"Base construct"<<endl;
}
virtual void foo(){
cout<<"Base foo"<<endl;
}
virtual ~Base(){
cout<<"Base destruct"<<endl;
}
};
class Derived:public Base{
public:
Derived(){
cout<<"Derived construct"<<endl;
}
virtual void foo(){
cout<<"Derived foo"<<endl;
}
~Derived(){
cout<<"Derived destruct"<<endl;
}
};
int main(){
Base *inst=new Derived(); //調用Derived的構造函數,在此之前會調用Base的構造函數
inst->foo(); //調用Derived的foo方法
delete inst; //調用Derived的析構函數,在此之后會調用Base的析構函數
}
最佳實踐:即便析構函數不做任何工作,繼承層次的根類也應該定義一個虛析構函數。
class Base{
public:
double price(std::size_t) const =0;
};
在函數形參表后面寫上=0表示該函數為純虛函數,含有一個或多個純虛函數的類為抽象類,不能創建抽象類的實例。抽象類為后代提供了可以覆蓋的接口。
void func(Base &base); Derived inst; func(inst);
當把基類對象傳遞給func()函數時,引用base直接綁定到inst對象,沒有發生對象的復制,沒有發生類型的轉換,inst也沒有發生任何改變不。
void func(Base base); Derived inst; func(inst);
此時情況完全不同,形參的類型是固定的,在編譯和運行時都是基類類型對象,如果把派生類對象傳遞過去,則派生類對象中的基類部分會發生復制。
