C++ 多型
- C++ 多型
- 靜態聯編動態聯編
- 靜態多型
- 動態多型
- 多型原理
- 呼叫慣例
- 純虛函式和抽象類
- 虛析構和純虛析構
- 虛析構語法:
- 純虛析構語法:
- 向上型別轉換和向下型別轉換
- 多載、重寫、重定義
- 多載
- 重寫(覆寫)
- 重定義(隱藏)
- 多型案例2 - 電腦組裝案例
C++ 多型
靜態聯編動態聯編
- 靜態多型在編譯階段系結地址,地址早系結,靜態聯編,
- 動態多次在運行階段系結地址,地址晚系結,動態聯編,
靜態多型
函式多載(函式名相同,函式串列不同),運算子多載
動態多型
- 先有繼承關系
- 父類中有虛函式,子類重寫父類中的虛函式
- 父類的指標或參考 指向子類的物件
class Animal
{
public:
//虛函式
virtual void speak()
{
cout << "動物在說話" << endl;
}
virtual void eat(int a )
{
cout << "動物在吃飯" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小貓在說話" << endl;
}
void eat(int a)
{
cout << "小貓在吃飯" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在說話" << endl;
}
};
//動態多型產生條件:
//先有繼承關系
//父類中有虛函式,子類重寫父類中的虛函式
//父類的指標或參考 指向子類的物件
//對于有父子關系的兩個類 指標或者參考 是可以直接轉換的,
//父類的指標或參考 指向子類的物件
void doSpeak(Animal & animal) //Animal & animal = cat;
{
//如果地址早就系結好了,地址早系結,屬于靜態聯編
//如果想呼叫小貓說話,這個時候函式的地址就不能早就系結好,而是在運行階段再去系結函式地址,屬于地址晚系結,叫動態聯編
animal.speak();
}
void test01()
{
//呼叫doSpeak()方法,若父類中的doSpeak()方法未指定為虛方法(virtual),
//則呼叫時使用的是父類中的方法,會輸出:動物在說話,
//若父類中的doSpeak()方法指定為虛方法(virtual),則呼叫時doSpeak()方法后,會使用子類的doSpeak()方法
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
多型原理
class Animal
{
public:
//虛函式
void speak()
{
cout << "動物在說話" << endl;
}
};
//此時不占位元組,sizeof Animal = 1
//加上virtual關鍵字后 sizeof Animal = 4
cout << "sizeof Animal = " << sizeof (Animal) << endl;
- 當父類寫了虛函式后,類內部結構發生改變,多了一個vfptr
- vfptr 虛函式表指標 ---- > vftable 虛函式表
- 虛函式表內部記錄著 虛函式的入口地址
- 當父類指標或參考指向子類物件,發生多型,呼叫是時候從虛函式中找函式入口地址
虛函式 關鍵字 virtual
利用指標的偏移呼叫 函式

Cat未發生重寫時

Cat重寫后

使用指標偏移方式呼叫虛函式
void test02()
{
Animal * animal = new Cat;
//默認呼叫
animal->speak();
// *(int *)animal 解參考到虛函式表中
// *(int *)(*(int *)animal) 解參考到函式speak地址
//呼叫貓說話,函式指標(void(*)()
((void(*)()) (*(int *)(*(int *)animal))) ();
//C/C++默認呼叫慣例 __cdecl
//用下列呼叫時候 真實呼叫慣例 是 __stdcall
//呼叫貓吃飯,若虛函式中存在形參,要統一呼叫慣例,否則程式呼叫會出問題
typedef void( __stdcall *FUNPOINT)(int);
(FUNPOINT (*((int*)*(int*)animal + 1)))(10);
}
呼叫慣例
- 主調函式和被調函式必須要有一致約定,才能正確的呼叫函式,這個約定我們稱為呼叫慣例
- 呼叫慣例 包含內容: 出堆疊方、引數傳遞順序、函式名稱修飾
C/C++下默認呼叫慣例:cdecl從右到左 ,主調函式管理出堆疊
((void(*)()) (*(int *)*(int *)animal)) ();
//指定函式的 呼叫慣例
typedef void( __stdcall *FUNPOINT)(int);
(FUNPOINT (*((int*)*(int*)animal + 1)))(10);
純虛函式和抽象類
- 語法:
virtual int getResult() = 0; - 如果一個類中包含了純虛函式,那么這個類就無法實體化物件了,這個類通常我們稱為
抽象類 - 抽象類的子類 必須要重寫 父類中的純虛函式,否則也屬于
抽象類
//利用多型實作計算器
class AbstractCalculator
{
public:
//純虛函式
//如果一個類中包含了純虛函式,那么這個類就無法實體化物件了,這個類通常我們稱為 抽象類
//抽象類的子類 必須要重寫 父類中的純虛函式,否則也屬于抽象類
virtual int getResult() = 0;
//純函式
virtual int getVirtualResult()
{
return 0;
}
int m_A;
int m_B;
};
虛析構和純虛析構
虛析構語法:
virtual ~Animal(){}- 如果子類中有指向堆區的屬性,那么要利用虛析構技術 ,在
delete的時候 呼叫子類的解構式,添加虛析構后才會呼叫子類的解構式
純虛析構語法:
virtual ~Animal() = 0;Animal::~Animal(){ .. }- 純虛析構,需要有宣告,也需要有實作(類內宣告,類外實作)
- 如果一個類中 有了 純虛解構式,那么這個類也屬于抽象類,無法實體化物件了
class Animal
{
public:
Animal()
{
cout << "Animal的建構式呼叫" << endl;
}
virtual void speak()
{
cout << "動物在說話" << endl;
}
//如果子類中有指向堆區的屬性,那么要利用虛析構技術 在delete的時候 呼叫子類的解構式
//virtual ~Animal()
//{
// cout << "Animal的解構式呼叫" << endl;
//}
//純虛析構 需要有宣告 也需要有實作
//如果一個類中 有了 純虛解構式,那么這個類也屬于抽象類,無法實體化物件了
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal的純虛解構式呼叫" << endl;
}
class Cat :public Animal
{
public:
Cat(const char* name)
{
cout << "Cat的建構式呼叫" << endl;
this->m_Name = new char[strlen(name) + 1];
strcpy(this->m_Name, name);
}
virtual void speak()
{
cout << this->m_Name << " 小貓在說話" << endl;
}
~Cat()
{
if (this->m_Name)
{
cout << "Cat的解構式呼叫" << endl;
delete[] this->m_Name;
this->m_Name = NULL;
}
}
char* m_Name;
};

向上型別轉換和向下型別轉換
- 父轉子 :向下型別轉換 ,不安全,會出現地址越界
Animal * animal = new Animal;
Cat * cat = (Cat *) animal;
- 子轉父 向上型別轉換 ,安全,僅僅是取址范圍縮小
Cat * cat = new Cat;
Animal * animal = (Animal *) cat;
- 如果發生多型,那么轉換永遠都是安全的,父類子針或參考指向子類物件
Animal * animal = new Cat;
Cat * cat = (Cat * ) animal;
Animal * animal = new Cat;執行時已經開辟出了Cat所需要的記憶體,只是當用animal指標指向時取址范圍為Animal大小,當強轉回Cat時,使用原始地址范圍,

多載、重寫、重定義
多載
同一作用域的同名函式
- 同一個作用域下
- 引數個數,引數順序,引數型別不同
- 和函式回傳值,沒有關系
const也可以作為多載條件
do(const Teacher& t){}
do(Teacher& t){}
重寫(覆寫)
子類重寫父類中的虛函式,函式回傳值、函式名、形參串列完全一致
- 有繼承
- 子類重寫父類的
virtual函式 - 函式回傳值,函式名字,函式引數必須和基類中的虛函式一致
重定義(隱藏)
子類重新定義父類中的同名成員函式,隱藏掉父類中同名成員函式,如果想呼叫加作用域
- 有繼承
- 子類重新定義父類的同名成員(非
virtual函式)
多型案例2 - 電腦組裝案例
//純虛函式
//CPU基類
class CPU
{
public:
virtual void calculate() = 0;
};
//顯卡基類
class VideoCard
{
public:
virtual void display() = 0;
};
//記憶體基類
class Memory
{
public:
virtual void storage() = 0;
};
//電腦類
class computer
{
public:
computer(CPU * cpu, VideoCard * vc, Memory * mem)
{
cout << "電腦構造呼叫" << endl;
this->m_Cpu = cpu;
this->m_Vc = vc;
this->m_Mem = mem;
}
void doWork()
{
this->m_Cpu->calculate();
this->m_Vc->display();
this->m_Mem->storage();
}
~computer()
{
cout << "電腦析構呼叫" << endl;
if (this->m_Cpu)
{
delete this->m_Cpu;
this->m_Cpu = NULL;
}
if (this->m_Vc)
{
delete this->m_Vc;
this->m_Vc = NULL;
}
if (this->m_Mem)
{
delete this->m_Mem;
this->m_Mem = NULL;
}
}
CPU * m_Cpu;
VideoCard * m_Vc;
Memory * m_Mem;
};
//inter廠商
class intelCPU :public CPU
{
public:
void calculate()
{
cout << "intelCPU開始計算了" << endl;
}
};
class intelVideoCard :public VideoCard
{
public:
void display()
{
cout << "intel 顯卡開始顯示了" << endl;
}
};
class intelMemory :public Memory
{
public:
void storage()
{
cout << "intel 記憶體條開始存盤了" << endl;
}
};
//Lenovo 廠商
class LenovoCPU :public CPU
{
public:
void calculate()
{
cout << "Lenovo CPU開始計算了" << endl;
}
};
class LenovoVideoCard :public VideoCard
{
public:
void display()
{
cout << "Lenovo 顯卡開始顯示了" << endl;
}
};
class LenovoMemory :public Memory
{
public:
void storage()
{
cout << "Lenovo 記憶體條開始存盤了" << endl;
}
};
void test01()
{
cout << "第一臺電腦組成:" << endl;
CPU * intelCpu = new intelCPU;
VideoCard * lenovoVC = new LenovoVideoCard;
Memory * lenovoMem = new LenovoMemory;
computer c1(intelCpu, lenovoVC, lenovoMem);
c1.doWork();
cout << "第二臺電腦組成:" << endl;
CPU * intelCpu2 = new LenovoCPU;
VideoCard * lenovoVC2 = new intelVideoCard;
Memory * lenovoMem2 = new intelMemory;
computer c2(intelCpu2, lenovoVC2, lenovoMem2);
c2.doWork();
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/292386.html
標籤:其他
