前言
在上一則教程中,敘述了抽象類以及動態(tài)鏈接庫的相關(guān)內(nèi)容,本節(jié)來敘述一下抽象類界面的相關(guān)內(nèi)容,以及本節(jié)即將引入一個新的概念,模板。
抽象類界面
何為抽象類界面呢?要說清楚這個概念,需要回顧上一則教程中所述的類編程和應(yīng)用編程兩個概念,為了實現(xiàn)應(yīng)用編程和類編程,引入了動態(tài)鏈接庫的概念,要達到的效果就是當更改類的代碼的時候,而不更改應(yīng)用程序的代碼的時候,只需要重新生成動態(tài)鏈接庫,而不需要重新生成可執(zhí)行文件。那么我們回顧之前的代碼,看應(yīng)用編程里面的內(nèi)容,也就是主函數(shù)里面的內(nèi)容:
#include "Human.h"
#include "Englishman.h"
#include "Chinese.h"
void test_eating(Human *h)
{
h->eating();
}
int main(int argc, char **argv)
{
Englishman e;
Chinese c;
Human* h[2] = {&e, &c};
int i;
for (i = 0; i < 2; i++)
test_eating(h[i]);
return 0;
}
在上述代碼中,我們看到第一行代碼和第二行代碼包含了頭文件Englishman.h
和 Chinese.h
,那么這個時候,如果更改了類中代碼,比如說我們更改了Englishman.h
或者是Chinese.h
的代碼,這個時候在編譯的時候,如果只編譯動態(tài)鏈接庫,而不編譯應(yīng)用程序,那么必然會導致程序出現(xiàn)問題。那要如何解決這個問題呢,所采取的一種思路便是使用抽象類界面的思路來進行解決。
下面是抽象類界面的一個示意圖:
image-20210224101410895
通過這張示意圖也可以明白,這個時候,APP也就是應(yīng)用程序的代碼只和Human.h
相關(guān),而 Human.h
又和Englishman
和Chinese
有關(guān),這樣一來,如果改變的是Englishman
或者是Chinese
類的代碼,那么就不會影響到應(yīng)用程序,仍然只需要重新編譯動態(tài)鏈接庫就好。
說了那么多,該如何做呢,我們先從主函數(shù)看起,下面是更改之后的主函數(shù):
#include "Human.h"
void test_eating(Human *h)
{
h->eating();
}
int main(int argc, char **argv)
{
Human& e = CreateEnglishman("Bill", 10, "sfwqerfsdfas");
Human& c = CreateChinese("zhangsan", 11, "beijing");
Human* h[2] = {&e, &c};
int i;
for (i = 0; i < 2; i++)
test_eating(h[i]);
return 0;
}
看到上述代碼,第一,頭文件中,Englishman.h
和Chinese.h
不見了,只剩下一個Human.h
,正如上面所說,APP
的代碼只和Human.h
有關(guān)聯(lián);第二,之前有Englishman
和Chinese
的實例化對象,現(xiàn)在改為了使用函數(shù)調(diào)用生成Human
類的引用,來替代之前的實例化對象。
那自然,這兩個函數(shù)調(diào)用是在Human.h
中聲明的了,Human.h
的代碼如下所示:
#ifndef _HUMAN_H
#define _HUMAN_H
#include
#include
#include
using namespace std;
class Human {
private:
char *name;
public:
void setName(char *name);
char *getName(void);
virtual void eating(void) = 0;
virtual void wearing(void) = 0;
virtual void driving(void) = 0;
};
Human& CreateEnglishman(char *name, int age, char *address);
Human& CreateChinese(char *name, int age, char *address);
#endif
為了使得應(yīng)用編程和類編程相互分離,那么這兩個函數(shù)的定義自然分別為了Englishman
和Chinese
了,代碼分別如下所示:
/* 當前處于Englishman.cpp中 */
Human& CreateEnglishman(char *name, int age, char *address)
{
return *(new Englishman(name, age, address));
}
下面是CreateChinese
的函數(shù)定義,代碼如下所示:
Human& CreateChinese(char *name, int age, char *address)
{
return *(new Chinese(name, age, address));
}
這樣一來,就實現(xiàn)了抽象類界面,在更改Englishman
和Chinese
的代碼的時候,不需要重新生成可執(zhí)行文件,只需要重新生成動態(tài)鏈接庫就可以了。
模板
在C++
中的模板定義中,模板有兩類,一個是函數(shù)模板,一個是類模板,在本節(jié)的教程中,主要是講述函數(shù)模板的相關(guān)內(nèi)容。
函數(shù)模板的引入
為什么要引入函數(shù)模板呢,我們來看一下如下所示的代碼:
int& max(int& a, int& b)
{
return (a < b)? b : a;
}
double& max(double& a, double& b)
{
return (a < b)? b : a;
}
float& max(float& a, float& b)
{
return (a < b)? b : a;
}
上述的代碼是max
函數(shù)的一個重載,觀察這個重載函數(shù),可見,每個重載函數(shù)的兩個形參是相同的,并且形參和返回值一樣,基于此,我們也就可以定義一個函數(shù)模板來替代這些函數(shù)重載,函數(shù)模板定義如下:
template
T& max(T&a,t&B)
{
return (a < b)? b : a;
}
如何理解上述模板函數(shù)呢,實際上也就是說,把類型用T
來替換了。
基于模板函數(shù),我們再來實現(xiàn)上述使用重載而實現(xiàn)的功能,代碼如下所示:
#include
#include
#include
using namespace std;
template
T& mymax(T& a, T& b)
{
cout<<__PRETTY_FUNCTION__< return (a < b)? b : a;
}
int main(int argc, char **argv)
{
int ia = 1, ib = 2;
float fa = 1, fb = 2;
double da = 1, db = 2;
mymax(ia, ib);
mymax(fa, fb);
mymax(da, db);
return 0;
}
上述代碼執(zhí)行的結(jié)果如下所示:
image-20210224135710100
可見上述的運行結(jié)果顯示,雖然是使用的一個函數(shù)模板,但是在執(zhí)行的時候,mymax(ia, ib);
、mymax(fa, fb);
以及mymax(da, db);
實際上是執(zhí)行了三個不同的函數(shù),這也正是函數(shù)模板執(zhí)行的一個機制,函數(shù)模板其特點主要是以下兩點:
函數(shù)模板只是編譯指令,一般寫在頭文件中;
編譯程序的時候,編譯器根據(jù)函數(shù)的參數(shù)來“推導”模板的參數(shù);然后生成具體的模板函數(shù)
模板函數(shù)參數(shù)推導過程
模板函數(shù)參數(shù)的推導過程是一個重要的內(nèi)容,它主要可以分為如下幾個方面:
有限的類型轉(zhuǎn)換
函數(shù)模板只支持兩種隱式轉(zhuǎn)換
其他隱式轉(zhuǎn)換都不支持
苛刻的類型匹配
參數(shù)類型必須完全匹配;如果不能直接匹配,則可以進行”有限的類型轉(zhuǎn)換“,如果還是不匹配,那么就推導失敗
-
const 轉(zhuǎn)換:函數(shù)參數(shù)為非 const 引用/指針,它可以隱式地轉(zhuǎn)換為const引用/指針
數(shù)組或者函數(shù)指針轉(zhuǎn)換:
數(shù)組可以隱式的轉(zhuǎn)換為”指向第一個元素的指針“
參數(shù)為”函數(shù)的名字“,它隱式地轉(zhuǎn)化為函數(shù)指針
基于上述所述的這些特點,接下來通過實例進行闡述,現(xiàn)在基于剛才那個函數(shù)模板,我們來編寫下面的例子:
using namespace std;
int main(int argc,char **argv)
{
int a = 1;
double b = 2.1;
mymax(a,b);
return 0;
}
代碼編譯結(jié)果如下所示:
image-20210224142730144
通過上述錯誤信息,可以看到所給出的信息是沒有匹配的函數(shù),只是因為我們傳入的參數(shù)是int
和double
,傳入這兩個參數(shù)是函數(shù)模板是無法進行推導的,無法進行隱式轉(zhuǎn)換。
針對于上述來講,函數(shù)模板只支持兩種隱式轉(zhuǎn)換,那分別是哪兩種呢,我們來看具體的例子,我們將函數(shù)模板也進行一些更改,更改之后的代碼如下所示:
#include
#include
#include
using namespace std;
template
const T& mymax(const T& a, const T& b)
{
cout<<__PRETTY_FUNCTION__< return (a < b)? b : a;
}
int main(int argc, char **argv)
{
int ia = 1;
int ib = 2;
mymax(ia, ib);
return 0;
}
當前這個函數(shù)是可以執(zhí)行通過的,也就是說當函數(shù)模板中的形參和返回值帶有const
的時候,那么對于實參是可以不含const
修飾的,也就是說可變的參數(shù)可以傳入到形參不可變的函數(shù)里,但是反過來是不行的,除非兩個傳進去的變量都是const
的。比如如下所示的代碼:
#include
#include
#include
using namespace std;
template
T& mymax(T& a, T& b)
{
cout<<__PRETTY_FUNCTION__< return (a < b)? b : a;
}
int main(int argc,char** argv)
{
int ia = 1;
const int ib = 2;
mymax(ia,ib); /* 錯誤,const 不能隱式轉(zhuǎn)換為非 const */
const int isa = 1;
const int isb = 2;
mymax(isa,isb); /* 正確 */
return 0;
}
除了上述的 非const
轉(zhuǎn) const
的例子以外,還有一個是數(shù)組和指針的隱式轉(zhuǎn)換,數(shù)組可以隱式地轉(zhuǎn)換為“指向第一個元素的指針”,下面是一個關(guān)于數(shù)組和指針的代碼:
#include
#include
#include
using namespace std;
template
const T& mymax(const T& a, const T& b)
{
cout<<__PRETTY_FUNCTION__< return (a < b)? b : a;
}
template
const T* mymax2(const T* a, const T* b)
{
cout<<__PRETTY_FUNCTION__< return (a < b)? b : a;
}
基于上述編寫的兩個模板函數(shù),我們依次測試上述所說的指針和數(shù)組之間的隱式轉(zhuǎn)換,代碼如下所示:
int main(int argc, char** argv)
{
char a[] = "ab";
char b[] = "cd";
mymax(a,b);
mymax2(a,b);
return 0;
}
下面是代碼執(zhí)行的結(jié)果:
image-20210224145329671
注:
cout<<__PRETTY_FUNCTION__<
可以在函數(shù)模板內(nèi)打印匹配結(jié)果
根據(jù)打印出來的匹配結(jié)果,可以看到
mymax
匹配的T
是數(shù)組類型,而mymax2
匹配的T
是char
類型,這也證實了上述所說的指針和數(shù)組之間的隱式轉(zhuǎn)換。
基于上述的函數(shù)模板,我們繼續(xù)來看一個例子:
int main(int argc, char** argv)
{
char a2[] = "abc";
char b2[] = "cd";
mymax(a2, b2); /* mymax(char[4], char[3]),無法推導出T:mymax(char& [4], char& [3]),因為兩個參數(shù)不一樣*/
mymax(a2, b2); /* mymax2(char[4], char[3]),可以推導出T:mymax2(const char*,const char*) */
return 0;
}
通過上述的注釋我們可以知道,第6行代碼是不能編譯通過的,但是第七行代碼可以編譯通過,因為它使用的模板的參數(shù)是指針,而對于數(shù)組來說,可以隱式轉(zhuǎn)換為指針,數(shù)組名可以隱式轉(zhuǎn)換為指向第一個元素的指針。
上述的幾個例子已經(jīng)說了
const
和非const
的,以及數(shù)組和指針的,接下來就是說的是函數(shù)指針的,且看下面這個例子:
template
void test_func(T f)
{
cout<<__PRETTY_FUNCTION__<}
void f1(int a, int b)
{
return 0;
}
int main(int argc, char **argv)
{
test_func(f1);
test_func(&f1);
return 0;
}
代碼執(zhí)行結(jié)果如下所示:
image-20210224151130109
可見對于函數(shù)名稱來說,上述的兩種傳入方式都是將
T
推導為函數(shù)指針的形式。
小結(jié)
上述就是本期分享的內(nèi)容,涉及的代碼可以通過百度云鏈接的方式獲取到:
鏈接:https://pan.baidu.com/s/13_g0L9KBTSVJWDvOrksrfQ
提取碼:gfsb