C++ Class6-虛擬函式-虛析構-多型-純虛擬函式-抽象類-靜態聯編和動態聯編-多型小練習(英雄聯盟)
型別相容性原則-重寫
重寫:發生在繼承關係,父類和子類都有相同的函式原型(成員覆蓋)
過載:同名不同作用(過載的函式原型是不用)
相同點:名字相同
虛擬函式
語法: virtual<型別> 成員函式(<引數列表>)
必須是基類中成員函式,且當基類的某個成員函式定義為虛擬函式的時候
他所有的派生類中與基類虛擬函式相同(函式原型相同)的函式都是虛擬函式
基類中宣告過virtual,派生類中不需要再宣告(不用再加virtual)
*通常情況下,基類的某個成員函式被定義為虛擬函式時,要在派生類中對他重新定義也就是重寫,否則這樣的虛擬函式時無意義的。
1、要有繼承
2、要有虛擬函式
3、要用基類的指標或者時引用指向派生類物件;
如何抑制多型的實現,加域解析附::
#include <iostream>
using namespace std;
class student
{
public:
virtual void show()
{
cout<<"student show"<<endl;
}
};
class Graduate:public student
{
public:
void show()
{
cout<< "Graduate show"<<endl;
}
};
int main()
{
student S,*ptr;//當沒有virtual時,早期聯編後ptr指標已經確定為student型別,不管後面做什麼操作,ptr->show()永遠是student show。不管ptr指向哪個派生類,都不會改變為派生類版本。解決方法就是在基類中加入virtual。
Graduate G;
ptr=&S;
ptr->show();
ptr=&G;
ptr->show();
G.show ();
return 0;
}
執行結果:
沒有virtual時:
student show
student show
Graduate show
加了virtual後:
student show
Graduate show
Graduate show
靜態聯編和動態聯編
1、聯編是指一個程式模組、程式碼之間互相關聯的過程。
2、靜態聯編(static binding),是程式的匹配、連線在編譯階段實現,也稱為早期匹配。
過載函式使用靜態聯編。
3、動態聯編是指程式聯編推遲到執行時進行,所以又稱為晚期聯編(遲繫結)。
switch 語句和 if 語句是動態聯編的例子。
4、理論聯絡實際
1、C++與C相同,是靜態編譯型語言
2、在編譯時,編譯器自動根據指標的型別判斷指向的是一個什麼樣的物件;所以編譯器認為父類指標指向的是父類物件。
3、由於程式沒有執行,所以不可能知道父類指標指向的具體是父類物件還是子類物件
從程式安全的角度,編譯器假設父類指標只指向父類物件,因此編譯的結果為呼叫父類的成員函式。這種特性就是靜態聯編。
多型
多型原理:
1、虛擬函式是在執行時候完成,所以需要定址
普通函式在編譯時就確定了需要呼叫的函式
2、定址(時間)+虛擬函式表(空間)虛擬函式是要消耗更多的時間和空間
處於效率考慮,謹慎新增虛擬函式
#include <iostream>
using namespace std;
class Cshape
{
protected:
int x;
int y;
public:
Cshape(int x,int y):x(x),y(y){};
virtual double get_area()
{
return 0;
}
/*虛擬函式表(virtual宣告會額外申請一塊儲存空間)
VPTR指標指向這個虛擬函式表
CShape | double get_V()
BALL | double get_V() (存的是地址)
Rectangle | double get_V()*/
};
class Ball:public Cshape
{
protected:
int r;
public:
Ball(int r,int x,int y):Cshape(x,y)
{
this->r=r;
}
double get_area()
{
return 3.14*r*r;
}
};
class Rectangle:public Cshape
{
protected:
double z;
public:
Rectangle(int z,int x,int y):Cshape(x,y)
{
this->z=z;
}
double get_area()
{
return x*y*z;
}
};
void func(Cshape& ptr)//通過函式介面實現多型(常用)更簡練看著也舒服
{
cout<<ptr.get_area()<<endl;
}
int main()
{
Ball b1(2,0,5.0);
func(b1); // CShape &p = b1
Rectangle r1(3.0,4.0,5.0);
func(r1); // CShape &p = r1
return 0;
}
建構函式中呼叫虛擬函式
這是沒有意義的!
為什麼多型無效:
因為構造的順序是先有基類
在構造基類的時候,派生類還沒有開始構造,自然就沒有函式重寫,也就沒有多型
虛析構!!
重點!
何時使用:當將基類指標或引用 new運算子指向派生類例項時候
作用:為了在釋放派生類例項時能夠呼叫派生類的解構函式,
必須將解構函式設定為虛擬函式
int main()
{
Base* ptr = new Derived;//這一步其實包含了好幾步,1、Base *ptr 2、Derived d 3、ptr= &d;
delete ptr;//ptr是一個基類的指標,在釋放時編譯器只會釋放ptr,但是d卻沒有釋放。用虛解構函式就可以指引編譯器將所有涉及的物件去呼叫響應的解構函式;
return 0;
}
不要用父類指標指向子類陣列
#include <iostream>
using namespace std;
class Test6A
{
protected:
int a;
public:
Test6A(){}
virtual void print ()
{
cout << "base" << endl;
}
};
class Test6B:public Test6A
{
protected:
int b;
public:
Test6B(int b)
{
this->b = b;
}
void print()
{
cout << "b:" << b << endl;
}
};
int main()
{
Test6B b[5] = {Test6B(1),Test6B(2),Test6B(3),Test6B(4),Test6B(5)};
Test6B *pb = b;
Test6A *p = b; //父類指標指向子類陣列
pb->print();
p->print();
pb++; //Test6B *
p++; //Test6A * 指標運算是按照指標型別操作的
//他們倆所佔空間不一樣,所以指標運算的步長不一致
//如果父類指標指向子類陣列 會出現段錯誤
cout << pb<< endl;
cout << p << endl; //兩個地址不一致
pb->print();
p->print();
cout << pb<< endl;
cout << p << endl;
delete[] p;
return 0;
}
純虛擬函式與抽象類
為什麼會有抽象類:
像animal,Shape,Clothes。。。有些基類往往都有些抽象的概念
作用:
func(),提供統一介面,可以將介面和實現分開抽象
抽象類使用規則:
1、抽象類只能做基類,不能建立例項(不可以建立物件)
2、抽象類不能做引數型別/返回值型別/顯示型別轉換
3、可以宣告抽象類的指標/引用,僅僅時用來指向派生類,實現多型
如何定義:
!!不可以直接定義
間接:
(很不常用)1、將該類所有建構函式設定為private/protected
(常用) 2、在該類宣告純虛擬函式,他就是抽象類
virtual<返回值型別> 函式名 (<引數列表>)=0;!!!後面的=0不能少;
純虛擬函式使用規則:
1、宣告純虛擬函式時,不可以定義純虛擬函式實現部分;
因此在重寫純虛擬函式之前不能呼叫;
2、純函式後面的=0沒有實際意義,可以理解為僅僅是個標誌
3、在定義具有春旭函式的類的派生類時,必須對純虛擬函式重寫!
關於多型的小練習
要求實現英雄和面板和技能三個機制,設定了兩個英雄各三個面板。要求可以選擇英雄和面板並顯示,最後輸入qwe後列印響應技能
skin.h
#ifndef SKIN_H_INCLUDED
#define SKIN_H_INCLUDED
#include <iostream>
using namespace std;
class Skin
{
protected:
char* name;
public:
char* get_name(){return name;};
virtual void show_name()=0;
};
class tanglang_skin1:public Skin
{
public:
tanglang_skin1()
{
name = "霸天異形";
}
void show_name()
{
cout<<"你選擇的是"<<"霸天異形"<<endl;
}
};
class tanglang_skin2:public Skin
{
public:
tanglang_skin2()
{
name = "沙之守護";
}
void show_name()
{
cout<<"你選擇的是"<<"沙之守護"<<endl;
}
};
class tanglang_skin3:public Skin
{
public:
tanglang_skin3()
{
name = "死亡綻放";
}
void show_name()
{
cout<<"你選擇的是"<<"死亡綻放"<<endl;
}
};
class shizigou_skin1:public Skin
{
public:
shizigou_skin1()
{
name = "鐵血獵人";
}
void show_name()
{
cout<<"你選擇的是"<<"鐵血獵人"<<endl;
}
};
class shizigou_skin2:public Skin
{
public:
shizigou_skin2()
{
name = "暗黑武士";
}
void show_name()
{
cout<<"你選擇的是"<<"暗黑武士"<<endl;
}
};
class shizigou_skin3:public Skin
{
public:
shizigou_skin3()
{
name = "霸天戰士";
}
void show_name()
{
cout<<"你選擇的是"<<"霸天戰士"<<endl;
}
};
#endif // SKIN_H_INCLUDED
hero.h
#ifndef HERO_H_INCLUDED
#define HERO_H_INCLUDED
#include "skin.h"
class Hero
{
protected:
char* name;
Skin* sk;
public:
char* get_name(){return name;};
Skin* get_skin(){return sk;};
virtual void show_skin()=0;
virtual void set_skin(int cmd)=0;
virtual void show_name()=0;
virtual void show()=0;
virtual void skill1()=0;
virtual void skill2()=0;
virtual void skill3()=0;
};
class tanglang:public Hero
{
public:
tanglang()
{
name = "螳螂";
sk = NULL;
}
void show_skin()
{
cout<<"請選擇面板:"<<endl;
cout<<"1.霸天異形"<<endl;
cout<<"2.沙之守護"<<endl;
cout<<"3.死亡綻放"<<endl;
}
void show_name()
{
cout<<"你選擇的是 "<<name<<endl;
}
void show()
{
cout<<"你選擇的是 "<<name<<endl;
cout<<"你選擇的面板是 "<<sk->get_name()<<endl;
}
void set_skin(int cmd)
{
switch(cmd)
{
case 1:
sk = new tanglang_skin1;
break;
case 2:
sk = new tanglang_skin2;
break;
case 3:
sk = new tanglang_skin3;
break;
}
}
void skill1()
{
cout<<"品嚐恐懼"<<endl;
}
void skill2()
{
cout<<"虛空突刺"<<endl;
}
void skill3()
{
cout<<"躍擊"<<endl;
}
};
class shizigou:public Hero
{
public:
shizigou()
{
name = "獅子狗";
sk = NULL;
}
void show_name()
{
cout<<"你選擇的是 "<<name<<endl;
}
void show()
{
cout<<"你選擇的是 "<<name<<endl;
cout<<"你選擇的面板是 "<<sk->get_name()<<endl;
}
void show_skin()
{
cout<<"請選擇面板:"<<endl;
cout<<"1.鐵血獵人"<<endl;
cout<<"2.暗黑武士"<<endl;
cout<<"3.霸天戰士"<<endl;
}
void set_skin(int cmd)
{
switch(cmd)
{
case 1:
sk = new shizigou_skin1;
break;
case 2:
sk = new shizigou_skin2;
break;
case 3:
sk = new shizigou_skin3;
break;
}
}
void skill1()
{
cout<<"殘忍無情"<<endl;
}
void skill2()
{
cout<<"戰爭咆哮"<<endl;
}
void skill3()
{
cout<<"套索打擊"<<endl;
}
};
#endif // HERO_H_INCLUDED
main.cpp
#include <iostream>
#include <cstring>
#include <conio.h>
#include "hero.h"
#include "skin.h"
using namespace std;
Hero* Set_hero()
{
Hero* hero;
int cmd;
cout<<"請選擇英雄"<<endl;
cin>>cmd;
switch(cmd){
case 1:
hero = new tanglang;
break;
case 2:
hero = new shizigou;
break;
}
hero->show_name();
return hero;
}
void Set_skin(Hero* hero)
{
hero->show_skin();
int cmd;
cin>>cmd;
hero->set_skin(cmd);
hero->get_skin()->show_name();
}
void Game_Start(Hero* hero)
{
cout<<"------------------------"<<endl;
hero->show();
cout<<"-------遊戲開始!-------"<<endl;
char skill;
while(1)
{
skill=_getch();
switch( skill ){
case 'q':
hero->skill1();
break;
case 'w':
hero->skill2();
break;
case 'e':
hero->skill3();
break;
}
}
}
int main()
{
cout<<"請選擇英雄"<<endl;
cout<<"1、螳螂"<<endl;
cout<<"2、獅子狗"<<endl;
Hero * hero = Set_hero();
Set_skin(hero);
Game_Start(hero);
return 0;
}
執行結果: