C++ 和 Java 不同的是,C++ 沒有 interface 關鍵字。對於很多新手來說,C++ 當中接口的概念不容易像 Java 當中那樣被理解。
然而接口是面向對象編程當中的重要組成部分,也是新手需要學習的重要思維,雖然 C++ 並不那么面向對象。
首先,要明確接口的概念:
接口的存在意義是為不同的派生類提供統一的標准,繼而實現面向對象編程當中的多態概念。
對象是對客觀事物的抽象,類是對對象的抽象。
那么,C++ 當中既然不存在 interface 關鍵字,那么接口是通過什么方式來實現的呢?
首先要講兩個概念:
一、純虛函數 (Pure Virtual Function):
1,純虛函數只有函數名、參數、返回值類型。
2,純虛函數的定義是在函數句首使用 virtual 關鍵字修飾,並且在句末增加 "= 0"。
virtual void funtion() = 0;
3,純虛函數不能包含實現,只有聲明,所以純虛函數不能被調用。
4,定義純虛函數的目的在於,使每一個派生類都擁有相同的函數規范。
👆
承上啟下:包含純虛函數的類就是抽象類。
👇
二、抽象類 (Abstract Class):
1,抽象類必須包含一個純虛函數,存在純虛函數的類就一定是一個抽象類。
2,抽象類不能被實例化,只能被繼承派生,因為純虛函數沒有函數體,不是完整的函數,無法調用,也無法為其分配內存空間。
3,派生出來的子類必須實現所有抽象類當中的純虛函數。
4,抽象類默認存在隱式的構造函數,但是不能將構造函數定義為純虛函數。(因為無意義)
5,析構函數可以定義成純虛函數,相應的派生類也要給出析構函數的實現。
6,如果派生出來的子類沒有實現父抽象類當中的所有純虛函數,那子類仍然是抽象類。
所以,特點就很顯而易見了,有這么一個“類”,它不能被實例化,只能被繼承,而繼承它的派生類必須要重寫它聲明的所有函數。
這就是接口的概念,為所有派生類提供了一個統一的規范可以實現多態。
====================================
演示講解部分
這里的 Demo 不像高校老師或培訓機構那樣隨便寫幾個類來演示了,而是選擇一個更貼合實際開發的場景:
定義一個接口(抽象類),兩個動態庫去繼承並分別采用不同實現,最后在 main 函數中執行通過多態獲得不同效果。
演示使用 Visual Studio 2022,其中會涉及智能指針、動態庫類的導出、動態庫鏈接等額外知識點,自行 MSDN 補充。
Visual Studio 是一個解決方案包含N個項目,這個 Demo 的結構就是接口作為一個項目、兩個動態庫兩個項目、演示執行的EXE一個項目。
------------------------------------
新建第一個項目,創建接口頭文件:BrainToolBoxInterface.h
文件中有一個叫“大腦工具箱”的抽象類,它擁有兩個純虛函數作為統一的接口。
#pragma once #include <vector> using std::vector; // 定義統一的 DLL 導出宏 #define BrainToolBoxDLL_EXPORTS // 定義接口 class BrainToolBoxInterface { public: // 對 Vector 進行排序的接口 virtual void SortVector(std::vector<int>& vec) = 0; // 說自己是人腦還是電腦 virtual void SelfIntroduction() = 0; };
------------------------------------
新建第二和第三個項目:兩個動態庫
分別叫 “人力工具箱” 和 “電腦工具箱”
其中兩個項目的 dllmain、phc、framework完全一致,並且VS會自動幫你創建。
但是要注意,兩個項目新建后,要將接口頭文件所在的路徑添加到 “附加包含目錄”。
補充 DLL 導出相關知識(重要):
dllexport 與 dllimport 屬性官方解釋:https://docs.microsoft.com/zh-cn/cpp/cpp/dllexport-dllimport?view=msvc-170
注意兩個屬性必須搭配 _declspec() 關鍵字使用。
我們的兩個動態庫是給外部程序調用的,所以應使用 _declspec(dllexport),表明類可以被外部所使用。
注意:[摘自MSDN] 不使用 __declspec(dllimport) 也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在於 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。
“人力工具箱”動態庫:
HumanToolBox.h
#pragma once #include "BrainToolBoxInterface.h" // 配置類的導出 #ifdef BrainToolBoxDLL_EXPORTS #define BrainToolBoxDLL __declspec(dllexport) #else #define BrainToolBoxDLL __declspec(dllimport) #endif class BrainToolBoxDLL HumanToolBox : public BrainToolBoxInterface { public: // 構造 HumanToolBox(); // 析構 ~HumanToolBox(); // 對 Vector 進行排序 void SortVector(std::vector<int>& vec); // 自我介紹 void SelfIntroduction(); };
HumanToolBox.cpp
#include "pch.h" #include "HumanToolBox.h" #include <iostream> HumanToolBox::HumanToolBox() { } HumanToolBox::~HumanToolBox() { } void HumanToolBox::SortVector(std::vector<int>& vec) { int vec_size = vec.size(); for (size_t i = 0; i < vec_size; i++) { int trend_size = vec_size - i - 1; for (size_t j = 0; j < trend_size; j++) { if (vec[j] > vec[j + 1]) { int k = vec[j]; vec[j] = vec[j + 1]; vec[j + 1] = k; } } } return; } void HumanToolBox::SelfIntroduction() { std::cout << "I am super man!" << std::endl; return; }
“電腦工具箱”動態庫:
ComputerToolBox.h
#pragma once #include "BrainToolBoxInterface.h" // 配置類的導出 #ifdef BrainToolBoxDLL_EXPORTS #define BrainToolBoxDLL __declspec(dllexport) #else #define BrainToolBoxDLL __declspec(dllimport) #endif class BrainToolBoxDLL ComputerToolBox : public BrainToolBoxInterface { public: // 構造 ComputerToolBox(); // 析構 ~ComputerToolBox(); // 對 Vector 進行排序 void SortVector(std::vector<int>& vec); // 自我介紹 void SelfIntroduction(); };
ComputerToolBox.cpp
#include "pch.h" #include "ComputerToolBox.h" #include <iostream> #include <algorithm> ComputerToolBox::ComputerToolBox() { } ComputerToolBox::~ComputerToolBox() { } void ComputerToolBox::SortVector(std::vector<int>& vec) { std::sort(vec.begin(), vec.end()); return; } void ComputerToolBox::SelfIntroduction() { std::cout << "I am byte!" << std::endl; return; }
可以看到,“人力工具箱” 和 “電腦工具箱” 使用了共同的 BrainToolBoxInterface 接口,但是實現完全不同。
------------------------------------
新建第四個項目:調用兩個DLL的可執行文件
在項目引用當中添加 ComputerToolBox 和 HumanToolBox。
將 ComputerToolBox 和 HumanToolBox 頭文件所在的路徑添加到 “附加包含目錄”。【實際開發中建議使用 pIMPL 隱藏 DLL 實現】
CMD_Main.cpp
// CMD_Main.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。 // #include <iostream> #include <memory> using std::shared_ptr; using std::make_shared; #include "BrainToolBoxInterface.h" #include "ComputerToolBox.h" #include "HumanToolBox.h" int main() { shared_ptr<BrainToolBoxInterface> cr = make_shared<ComputerToolBox>(); shared_ptr<BrainToolBoxInterface> hr = make_shared<HumanToolBox>(); cr->SelfIntroduction(); hr->SelfIntroduction(); return EXIT_SUCCESS; }
------------------------------------
最終運行效果:
I am byte! I am super man! D:\Repos\AirChip\Demo\CPP_Interface\x64\Debug\CMD_Main.exe (進程 9516)已退出,代碼為 0。