/**************************************************************
虛擬函式:
1、在基類用【virtual宣告】的成員函式即為虛擬函式
2、在派生類中的定義此函式,要求
函式名、函式型別、函式引數個數和型別全部與基類的虛擬函式相同
並根據派生類的需要重新定義函式體
3、當一個成員函式宣告為虛擬函式後,其派生類中的同名函式都自動成為虛擬函式,無論是否加virtual關鍵字
4、用基類的指標或引用指向派生類的物件
通過用基類的指標或引用呼叫虛擬函式
實際執行的將使派生類物件中定義的虛擬函式
虛擬函式表:
1、如果類中包含有虛成員函式,在用該類例項化物件時,
物件的第一個成員將是一個指向虛擬函式表的指標[pvftable]。
2、虛擬函式表記錄執行過程中實際應該呼叫的虛擬函式的入口地址
3、類中所有的虛擬函式形成虛擬函式表
[可以通過除錯檢視:
cout<<sizeof(aa3)<<endl;]
虛解構函式: -- 防止記憶體洩漏
1、如果一個基類的指標指向派生類的物件,
並且想通過該指標delete派生類物件,
系統將只會執行基類的解構函式
而不會執行派生類的解構函式
2、為避免這種情況的發生,往往把解構函式宣告為虛的,
此時,系統將 先執行派生類物件的解構函式
然後再執行基類的解構函式
3、如果基類的解構函式宣告為虛的,
派生類的解構函式也將自動成為虛構函式
無論派生類解構函式宣告中是否加virtual
4、防止記憶體洩漏
當派生類中有指標時,物件銷燬時,
也需要釋放指標所指向的地址
5、特別注意:
5.1、
基類名* 指標 = new 子類名();
只有當一個父類型別的指標 指向 子類物件[new 的物件]時
只有將 基類的解構函式 宣告為 虛擬函式virtual
在 delete 指標時-->
才會:
先執行派生類物件的解構函式
然後再執行基類的解構函式
否則:
只執行基類的解構函式
5.2、
類名* 指標 = new 類名();
當指標的型別是物件型別的本身時
在 delete 指標時-->
基類的解構函式 【不是】虛擬函式virtual
也會:
先執行派生類物件的解構函式
然後再執行基類的解構函式
5.3、在棧中分配分配空間時【子類分配空間】
5.3.1、不需要 delete
在 該區域 執行完後 會自動釋放 該棧空間
5.3.2、基類的解構函式 【不是】虛擬函式virtual
也會:
先執行派生類物件的解構函式
然後再執行基類的解構函式
純虛擬函式和抽象類:
1、純虛擬函式具有如下的一般形式:
virtual 返回型別 函式名(引數列表)=0;
2、純虛擬函式沒有函式體,即只有函式的宣告而沒有函式的定義
3、通常在基類中宣告純虛擬函式,在派生類中定義該虛擬函式
如果派生類中也沒有定義該純虛擬函式,則該函式在派生類中任然為純虛擬函式
4、不能例項化物件的類稱為[抽象類],
具有純虛擬函式的類是不能被例項化物件的
所以具有純虛擬函式的類是一種抽象類
5、雖然抽象類不能例項化物件,但是可以用抽象類的指標指向派生類物件
並呼叫派生類的虛擬函式的實際實現
6、子類在繼承抽象類時:
必須全部實現抽象類的 純虛擬函式,該派生類才能被例項化
如果只實現部分 純虛擬函式,則該派生類還是 抽象類
7、當一個的所有函式都是 純虛擬函式,
則該類就是 介面【C++沒有介面】
介面是抽象類的一種特例
子類呼叫基類的函式:
基類類名::函式名();
多型:
1、父類的函式為 虛擬函式, 即在 返回型別前加 virtual
virtual void Print(){.....};
2、子類對該函式進行重寫[返回型別 和 函式名 相同]
void Print(){......};
3、父類的[指標]指向父類物件或子類物件
//通過指標
void test1(a* temp){
temp->Print();
}
3.1、在 【棧中】 分配空間
父類名* 地址變數名 = &父類名[子類名]();
a* aa = &b();
//等價於
a* aa;
b bb;
aa=&bb;
test1(aa);
3.2、在 【堆中】 分配空間
父類名* 地址變數名 = new 父類名[子類名]();
a* cc = new c();
test1(cc);
注意點:
1、指標呼叫函式的方式:
指標名->函式名();
2、引用呼叫函式的方式:
引用名.函式名();
3、繼承方式
class 子類名:public 父類名{....};
***************************************************************/
class Person{ //抽象類
public:
char* name;
public:
virtual void Eat()=0;
virtual void Sleep()=0;
};
class Stu:public Person{
public:
void Eat(){
cout<<"Eat......."<<endl;
}
void Sleep(){
cout<<"Sleep.........."<<endl;
}
};
void main(){
Person* person = new Stu();
person->Eat();
}
class a{
public:
virtual void Print(){
cout<<"a............."<<endl;
}
virtual void Prafint(){
cout<<"a............."<<endl;
}
public:
a(){
cout<<"new a............"<<endl;
}
public:
virtual ~a(){
cout<<"destory a............"<<endl;
}
};
//通過指標
void test1(a* temp){
temp->Print();
}
//通過引用
void test2(a& temp){
temp.Print();
}
class b:public a{
public:
void Print(){
cout<<"b............."<<endl;
}
public:
b(){
cout<<"new b............"<<endl;
}
public:
~b(){
cout<<"destory b............"<<endl;
}
};
class c:public a{
public:
void Print(){
cout<<"c............."<<endl;
}
public:
c(){
cout<<"new c............"<<endl;
}
public:
~c(){
cout<<"destory c............"<<endl;
}
};
void test22(){
/*
b bb;
注意:
在棧中例項化 派生類
1、先 呼叫父類的 建構函式 再呼叫派生類的建構函式
2、不需要手動釋放記憶體
3、在 該 區域執行完後 會自動釋放記憶體空間
new a............
new b............
destory b............
destory a............
*/
//a aa = bb;
//aa.Print();
a* aa = new b();
//a* aa = new b();
//aa->Print();
delete aa;
/*
a* aa = new b();
在堆中分配空間:
1、先 呼叫父類的 建構函式 再呼叫派生類的建構函式
2、先呼叫派生類的解構函式 再呼叫基類的解構函式
2.1、2的滿足條件:需要把 基類的解構函式 宣告 虛擬函式
3、如果基類的 解構函式 不是虛擬函式
3.1、在 delete 時只會呼叫基類的解構函式
*/
/*
new a............
new b............
b.............
destory b............
destory a............
*/
}
int test11(){
/*通過指標*/
a* aa1 = &b();
/*等價於
a* aa1;
b bb;
aa1=&bb;
*/
test1(aa1);
a* cc1 = new c(); //動態繫結 就是虛擬函式中的內容 在例項化的時候動態載入
test1(cc1);
/*通過引用*/
a& aa2 = b();
test2(aa2);
a* cc2 = new c();
test1(cc2);
a aa3;
cout<<sizeof(aa3)<<endl;
//cout<<&(a::Print)<<endl;
/*
//列印函式的地址
printf("a::Print=%x\n", &(a::Print));
printf("b::Print=%x\n", &(b::Print));
printf("c::Print=%x\n", &(c::Print));
列印結果:
a::Print=401271
b::Print=401271
c::Print=401271
分析:
a
/ \
b c
1、b繼承a , c繼承a
2、Print()在a類中是 虛擬函式
3、類b類c 重寫了a的虛擬函式,所以該函式也為虛擬函式
4、三個類中的函式統一載入一個地址區:
意味著三個只能有一個存在
5、具體 401271 裡面的內容就由呼叫的例項在例項化的時候動態載入
*/
return 0;
}