模板是范型編程的基礎,所謂范型編程就是用獨立與任何特定類型的方式編寫代碼
所以簡單地說,類是對象的抽象,而模板又是類的抽象,也就用模板能定義出具體類
再理解深刻點
在c++里,常說的多態一般分為兩種:
一種是運行時的多態,也就是虛函數體現的多態
另一種是編譯時的多態,也就是范型編程的多態,體現在參數的多態
在作用上是為了提高編程效率,其實用其他技術也能達到同樣的效果
另外貼一篇講虛函數的存在意義的知乎回答,寫的實在太清楚了:
鏈接:https://www.zhihu.com/question/23971699/answer/84332610
虛函數主要是用來實現多態和多重繼承的
沒有虛函數理論上也可以實現多態,但是太麻煩了,沒有虛函數清晰
主要是在多態上使用。
多態就是一個指針指向子類對象,那么他調用的函數就是子類的對象的。
這就實現了什么?
實現了一個函數會根據傳入參數的不同有不同的功能。
函數有多個狀態。 就是多態
void fuck (Animal *a);
如果沒有多態 那么 這個函數 只能fuck Animal
想要fuck多種 就要用重載,寫多個同名函數 參數不同
但是有了多態 他可以fuck所有Animal的子類 比如牛 羊 耗子 什么的
根據傳進來的參數不同,fuck不同的動物。
這樣這個函數就是fuckAnimalYouWant 函數了
這就多態了
如果現在你想fuck狗
要做的只是:
1.創建狗類繼承Animal
2.傳入到fuck函數中
此時 並不需要該代碼 即使傳入到fuck函數中 也可以是通過配置文件這種形式改變傳入參數,並沒有改變代碼
如果沒有多態呢?
1.創建狗對象繼承Animal
2.在類中增加一個fuck函數 參數是Dog
這就改變代碼了。
違反了設計模式開閉原則:對擴展開放,對修改關閉
實現多態的方式是什么
子類有和父類 同名同參數(重寫) 的函數
所以 對於多態的狀態來說,一定會有兩個同名同參數函數,分別定義在子類中和父類中
那么當用指針調用一個函數的時候,究竟是調用子類的還是父類的:
對於一個類成員函數的調用:
Animal *a = new Dog()
a->bark()
表面上看來是在一個對象內部去調用函數,好像是這個函數是存在於對象內部的感覺。。
但是實際上不是
實際上所有的函數都存放在單獨的一個代碼區,而函數對象里面只有成員變量。
也就是說
a->bark()
實際上被編譯器變成了
Animal::bark(a) 把對象a傳進去 才告訴了編譯器具體是哪個對象調用的這個函數
但是對於多態來說:
Animal::bark() 有
Dog::bark()也一定有
究竟選哪個函數執行呢?
此時還沒運行,還是在編譯階段,也就是內存中什么Dog啊 Animal 的對象還沒有存在。
所以只能根據a這個指針的類型 來決定使用哪個。也就是 Animal::bark()
所以
無論a指向什么子類,他調用的bark函數 都是Animal版本的父類版本的bark函數。
所以
現在沒有辦法達到多態的效果
這個在編譯時決定函數是哪個類的函數的方式就叫做靜態聯編
a->bark()
現在希望調用的是Dog::bark(a)
調用的是Dog類的
所以就不能使用靜態聯編,靜態聯編在編譯的時候決定函數是哪個。
為什么知道調用的是Dog::bark(a) 而不是 Cat::bark(a)呢?
因為a指向的對象是一個Dog 而不是一個Cat
所以必須在a指向的對象創建出來后才能決定究竟調用哪個函數
這就是動態聯編
怎么把靜態聯編改成動態聯編呢??
編譯器在靜態聯編時做了以下轉換
a->bark ---> Animal::bark(a)
當bark函數被設置為虛函數時,就不會進行那個轉換了,而是轉化為
a->bark ----》 (a->vtpl[1])(a)
先通過a指針找到對象,再找到對象中的虛表,再在虛表里面找到該調用的那個函數的函數指針
因為必須要在a指向的對象里面找,所以 必須等到a被創建出來,所以必須是運行時
所以這就是動態聯編 運行時決定調用哪個函數
而這個vtpl就是虛表指針,
當類中出現一個virtual指針時編譯器就自動在類的成員變量里加入一個指針成員。
這個指針成員指向這個類的虛表指針。
當子類重寫父類方法時 同時也是把繼承自父類的虛表中對應函數的索引的函數指針從父類函數改成了自己的函數。。
這就造成了子類和父類對象里面的虛表內容不同。
所以動態聯編時 去子類 或者父類里面找虛表,
調用的函數不同。
就形成了多態的效果
這個過程其實自己寫邏輯也能寫出來。
但是官方作出了virtual就是為了釋放這個工作量
當你寫了virtual這個關鍵字 ,以上的創建虛表 繼承 等一系列工作就都自動執行了。
但是降低了執行效率。