C++11新特性之泛型編程與模板

  • 模板
    • 泛型編程
    • 函數模板
      • 普通函數模板
      • 成員函數模板
      • 函數模板重載
      • 模板函數的特化
    • 類模板
      • 類模板中的成員函數模板
      • 類模板的特化與偏特化
      • 類模板成員特化

模板

Template所代表的泛型編程是C++語言中的重要組成部分。

泛型編程

泛型編程(Generic Programming)是一種語言機制,通過它可以實現一個標準的容器庫。
像類一樣,泛型也是一種抽象數據類型,但是泛型不屬于面向對象,它是面向對象的補充和發展。
在面向對象編程中,當算法與數據類型有關時,面向對象在對算法的抽象描述方面存在一些缺陷。

首先我們先來了解什么是泛型編程,看下面的例子。

比如對棧的描述:

class stack
{push(參數類型)  //入棧算法pop(參數類型)   //出棧算法}

如果把上面的偽代碼看作算法描述,沒問題,因為算法與參數類型無關。但是如果把它寫成可編譯的源代碼,
就必須指明是什么類型,否則是無法通過編譯的。使用重載來解決這個問題,即對N種不同的參數類型寫N個
push和pop算法,這樣是很麻煩的,代碼也無法通用。

若對上面的描述進行改造如下:
首先指定一種通用類型T,不具體指明是哪一種類型。

class stack<參數模板 T>
{push(T)  //入棧算法pop(T)   //出棧算法}

這里的參數模板T相當于一個占位符,當我們實例化類stack時,T會被具體的數據類型替換掉。
若定義對象S為statc類型,在實例化S時若我們將T指定int型則:
這時候類S就成為:

class S
{push(int)  //入棧算法pop(int)   //出棧算法
}

這時我可以稱class stack<參數模板 T>是類的類,通過它可以生成具體參數類型不同的類。

泛型在C++中的應用:
==泛型在C++中的主要實現為模板函數和模板類。==

函數模板

把處理不同類型的公共邏輯抽象成函數,就得到了函數模板。

函數模板的格式:

template <class 形參名,class 形參名,......> 返回類型 函數名(參數列表)
{函數體
}

其中templateclass是關鍵字,當然class可以使用typename關鍵字代替,兩者之間一點區別都沒有,這個是真的。
<>括號中的參數叫模板形參,模板形參和函數形參很相像,模板形參不能為空。

普通函數模板

template<typename T>
int compare(const T& left, const T& right) {if (left < right) {return -1; }if (right < left) {return 1; }return 0;
}template<class T=double>
void processValue( T& value )
{std::cout << value << std::endl;
}int main()
{int a=0;processValue(a);std::cout<< compare<int>(3,5) << std::endl;std::cout<< compare(3,5) << std::endl;return 0;
}輸出結果為:
0
-1
-1

由上面的例子我們可以看出,除了直接為函數模板指定類型參數之外,我們還可以讓編譯器從傳遞給函數的實參推斷類型參數,這一功能被稱為模板實參推斷,形參T可以自動推到出類型,當我們傳入的是int類型時,T此時會被替換成int。函數模板支持默認的形參類型,如上面的template。

成員函數模板

不僅普通函數可以定義為模板,類的成員函數也可以定義為模板。

class Printer {
public:template<typename T>void print(const T& t) {cout << t <<endl;}
};int main()
{Printer p;p.print<const char*>("abc");p.print(1);return 0;
}輸出結果:  
abc
1

使用的方式和普通函數模板沒有什么兩樣。

總結:
1) 函數模板并不是真正的函數,它只是C++編譯生成具體函數的一個模子。
2) 函數模板本身并不生成函數,實際生成的函數是替換函數模板的那個函數。這種替換是編譯期就綁定的。
3) 函數模板不是只編譯一份滿足多重需要,而是為每一種替換它的函數編譯一份。
4) 函數模板不允許自動類型轉換。
5) 函數模板不可以設置默認模板實參。比如template 不可以。
6) 函數模板的模板形參不能為空。

補充:

為什么成員函數模板不能是虛函數(virtual)?

這是因為c++ compiler在parse一個類的時候就要確定vtable的大小,如果允許一個虛函數是模板函數,那么compiler就需要在parse這個類之前掃描所有的代碼,找出這個模板成員函數的調用(實例化),然后才能確定vtable的大小,而顯然這是不可行的,除非改變當前compiler的工作機制。

函數模板和模板函數是什么?
函數模板的重點是模板。表示的是一個模板,專門用來生產函數。
模板函數的重點是函數。表示的是由一個模板生成而來的函數。

當返回值類型也是參數時
當一個模板函數的返回值類型需要用另外一個模板參數表示時,你無法利用實參推斷獲取全部的類型參數,這時有兩種解決辦法:

  • 返回值類型與參數類型完全無關,那么就需要顯示的指定返回值類型,其他的類型交給實參推斷。
    注意:此行為與函數的默認實參相同,我們必須從左向右逐一指定。
template<typename T1, typename T2, typename T3>
T1 sum(T2 v2, T3 v3) {return static_cast<T1>(v2 + v3);
}auto ret = sum<long>(1L, 23); //指定T1, T2和T3交由編譯器來推斷template<typename T1, typename T2, typename T3>
T3 sum_alternative(T1 v1, T2 v2) {return static_cast<T1>(v1 + v2);
}
auto ret = sum_alternative<long>(1L, 23); //error,只能從左向右逐一指定
auto ret = sum_alternative<long,int,long>(1L,23); //ok, 誰叫你把最后一個T3作為返回類型的呢?int main()
{int a  = 3;auto ret = sum(1.3443, 23); //指定T1, T2和T3交由編譯器來推斷,編譯錯誤,必須指定返回類型T1auto ret = sum<double>(1.3443, 23); //編譯通過std::cout<< ret << std::endl; //結果為24.3443auto ret1 = sum_alternative<double>(1.3443, 23); //error,只能從左向右逐一指定std::cout<< ret1 << std::endl;auto ret2 = sum_alternative<double,int,double>(1.3443,23); //ok, 誰叫你把最后一個T3作為返回類型的呢?std::cout<< ret2 << std::endl; //結果24.3443return 0;
}
  • 返回值類型可以從參數類型中獲得,那么把函數寫成尾置返回類型的形式,就可以愉快的使用實參推斷了。
template<typename T>
auto sum(T beg, T end) -> decltype(*beg) {decltype(*beg) ret = *beg;for (T it = beg+1; it != end; it++) {ret  = ret + *it;}return ret;
}int main()
{std::vector<int> v = {1, 2, 3, 4};auto s = sum(v.begin(), v.end()); //s = 10std::cout << s << std::endl; //結果為10return 0;
}

函數模板重載

函數模板之間,函數模板與普通函數之間可以重載。編譯器會根據調用時提供的函數參數,調用能夠處理這一類型的最特殊的版本。在特殊性上,一般按照如下順序考慮:
1. 普通函數
2. 特殊模板(限定了T的形式的,指針、引用、容器等)
3. 普通模板(對T沒有任何限制的)

template<typename T>
void func(T& t) { //通用模板函數cout << "In generic version template " << t << endl;
}template<typename T>
void func(T* t) { //指針版本cout << "In pointer version template "<< *t << endl;
}void func(string* s) { //普通函數cout << "In normal function " << *s << endl;
}int i = 10;
func(i); //調用通用版本,其他函數或者無法實例化或者不匹配
func(&i); //調用指針版本,通用版本雖然也可以用,但是編譯器選擇最特殊的版本
string s = "abc";
func(&s); //調用普通函數,通用版本和特殊版本雖然也都可以用,但是編譯器選擇最特化的版本
func<>(&s); //調用指針版本,通過<>告訴編譯器我們需要用template而不是普通函數

模板函數的特化

有時通用的函數模板不能解決個別類型的問題,我們必須對此進行定制,這就是函數模板的特化。函數模板的特化必須把所有的模版參數全部指定。

template<>
void func(int i) {cout << "In special version for int "<< i << endl; 
}int main()
{int i = 10;func(i); //調用特化版本return 0;
}

類模板

類模板也是公共邏輯的抽象,通常用來作為容器(例如:vector)或者行為的封裝。

類模板的格式:
template

#include <iostream>
#include <vector>
#include <string>
#include <sstream>template<typename T>
class Printer {
public:explicit Printer(const T& param):t(param){}//右值引用string&& to_string(){std::stringstream ss;ss << t;return std::move(string(ss.str()));}   void print() {cout << t << endl;}
private:T t;
};int main()
{Printer p(1); //errorPrinter<int> p(3); //okstd::string str = p.to_string();std::cout << str << std::endl; //結果為3return 0;
}

與函數模板不同,類模板不能推斷實例化。所以你只能顯示指定類型參數使用Printer p(3),而不能讓編譯器自行推斷。

類模板的成員函數既可以定義在內部,也可以定義在外部。定義在內部的被隱式聲明為inline,定義在外部的類名之前必須加上template的相關聲明。

類模板中的成員函數模板

我們還可以把類模板和函數模板結合起來,定義一個含有成員函數模板的類模板。

template<typename T>
class Printer {
public:explicit Printer(const T& param):t(param){}//成員函數模板template<typename U>void add_and_print(const U& u);
private:T t;
};//注意這里要有兩層template的說明
template<typename T>
template<typename U>
void Printer<T>::add_and_print(const U& u) {cout << t + u << endl;
}Printer<int> p(42);
p.add_and_print(1.1); //自動推斷U為double,打印出43.1

類模板成員函數實例化

為了節省資源,類模板實例化時并不是每個成員函數都實例化了,而是使用到了哪個成員函數,那個成員函數才實例化。

template<typename T>
class Printer {
public:explicit Printer(const T& param):t(param){}void print() {cout << t << endl;}private:T t;};class empty{};empty e;
Printer<empty> p(e); //ok

雖然成員函數print無法通過編譯,但是因為沒有使用到,也就沒有實例化print,所以沒有觸發編譯錯誤。

類模板的特化與偏特化

就像函數模板重載那樣,你可以通過特化(偏特化)類模板來為特定的類型指定你想要的行為。類模板的特化(偏特化)只需要模板名稱相同并且特化列表<>中的參數個數與原始模板對應上即可,模板參數列表不必與原始模板相同模板名稱相同。一個類模板可以有多個特化,與函數模板相同,編譯器會自動實例化那個最特殊的版本。

#include <typeinfo>template<typename T> //基本模板
class S {
public:void info() {   printf("In base template\n"); }
};template<> //特化
class S<int> { 
public:void info() {printf("In int specialization\n");}
};template<typename T> //偏特化
class S<T*> {
public:void info() {printf("In pointer specialization\n");}
};template<typename T, typename U> //另外一個偏特化
class S<T(U)> {
public:void info() {std::cout << typeid(T).name() << std::endl; std::cout << typeid(U).name() << std::endl;printf("In function specialization\n");}
};int func(int i) {return 2 * i;
}S<float> s1;
s1.info();     //調用base模板                
S<int> s2;
s2.info();     //調用int特化版本
S<float*> s3;
s3.info();     //調用T*特化版本 
S<decltype(func)> s4;
s4.info();     //調用函數特化版本

提供了所有類型實參的特化是完全特化,只提供了部分類型實參或者T的類型受限(例如:T)的特化被認為是不完整的,所以也被稱為偏特化。完全特化的結果是一個實際的class,而偏特化的結果是另外一個同名的模板。*

類模板成員特化

除了可以特化類模板之外,還可以對類模板中的成員函數和普通靜態成員變量進行特化。

template<typename T>  
class S {
public:void info() {printf("In base template\n");}static int code;
};template<typename T>
int S<T>::code = 10;template<>
int S<int>::code = 100;    //普通靜態成員變量的int特化template<>
void S<int>::info() {    //成員函數的int特化printf("In int specialization\n");
} S<float> s1;
s1.info();    //普通版本
printf("Code is: %d\n", s1.code);    //code = 10S<int> s2; 
s2.info();   //int特化版本
printf("Code is: %d\n", s2.code);   //code = 100

*補充:*

類模板的重點是模板。表示的是一個模板,專門用于產生類的模

例如:

template   <typename   T> class   Vector { … }; 

模板類的重點是類。表示的是由一個模板生成而來的類
例如:

Vector <int> 、Vector <char> 、Vector <   Vector <int>   > 、Vector <Shape*> ……//全是模板類

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/536149.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/536149.shtml
英文地址,請注明出處:http://en.pswp.cn/news/536149.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

WordPress更改“固定鏈接”后 頁面404原因及解決方法(Nginx版)

網上盛傳的方法是&#xff1a; 在 /etc/nginx/nginx.conf文件的 loction / {} 中添加 if (-f $request_filename/index.html){rewrite (.*) $1/index.html break; }if (-f $request_filename/index.php){rewrite (.*) $1/index.php; }if (!-f $request_filename){rewrite (.*…

C++類型萃取之type_traits和type_info

類型萃取類型判斷typeiddecltype和declvalenable_if 類型萃取 通過type_traits可以實現在編譯期計算、查詢、判斷、轉換和選擇&#xff0c;增強了泛型編程的能力&#xff0c;也增強了我們程序的彈性&#xff0c;讓我們能夠在編譯期就能夠優化改進甚至排錯&#xff0c;進一步提…

使用Phpstorm實現遠程開發

Phpstorm除了能直接打開本地文件之外&#xff0c;還可以連接FTP&#xff0c;除了完成正常的數據傳遞任務之外&#xff0c;還可以進行本地文件與服務端文件的異同比較&#xff0c;同一文件自動匹配目錄上傳&#xff0c;下載&#xff0c;這些功能是平常IDE&#xff0c;FTP軟件中少…

什么是遞歸函數?

文章目錄遞歸函數遞歸例題特點效率優點遞歸函數 遞歸 遞歸就是一個函數在它的函數體內調用它自身。執行遞歸函數將反復調用其自身&#xff0c;每調用一次就進入新的一層。遞歸函數必須有結束條件。 當函數在一直遞推&#xff0c;直到遇到墻后返回&#xff0c;這個墻就是結束條…

apache ab壓力測試報錯

今天用apache 自帶的ab工具測試&#xff0c;當并發量達到1000多的時候報錯如下&#xff1a; [rootaa~]# This is ApacheBench, Version 2.3 <Revision:655654> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Sof…

ngOnInit與constructor的區別

前世今生 Angular會管理一個組件的生命周期,包括組件的創建、渲染、子組件創建與渲染、當數據綁定屬性變化時的校驗、DOM移除之前毀銷。 Angular提供組件生命周期鉤子便于我們在某些關鍵點發生時添加操作。 組件生命周期鉤子 指令和組件實例有個生命周期用于創建、更新和銷…

Nginx配置性能優化

大多數的Nginx安裝指南告訴你如下基礎知識——通過apt-get安裝&#xff0c;修改這里或那里的幾行配置&#xff0c;好了&#xff0c;你已經有了一個Web服務器了。而且&#xff0c;在大多數情況下&#xff0c;一個常規安裝的nginx對你的網站來說已經能很好地工作了。然而&#xf…

Angular的@Output與@Input理解

@Output與@Input理解 Output和Input是兩個裝飾器,是Angular2專門用來實現跨組件通訊,雙向綁定等操作所用的。 @Input Component本身是一種支持 nest 的結構,Child和Parent之間,如果Parent需要把數據傳輸給child并在child自己的頁面中顯示,則需要在Child的對應 directiv…

騰訊云CDN配置

第一步&#xff1a;先去領取騰訊云CDN免費包23333333 以下為正式步驟&#xff1a; 在這里體現大家&#xff0c;域名一定要備案&#xff0c;另外要明白域名如何解析 前邊問題不大&#xff0c;一切跟著騰訊云的套路來即可&#xff0c;需要注意的是網上后優化的配置大家可以自行…

Promise.all的深入理解

異步之Promise Promise.all Promise.all接收的promise數組是按順序執行的還是一起執行的&#xff0c;也就是說返回的結果是順序固定的嗎&#xff1f; 目前有兩種答案&#xff1a; 應該是同步執行的&#xff0c;但是這樣就有效率問題了&#xff0c;如果想改成異步執行怎么辦…

wordpress后臺無法登錄問題

之前給自己的WordPress加了個標簽云&#xff0c;今天登錄的時候突然發現網站后臺進不去了&#xff0c;無奈各種找材料&#xff0c;這算是皇天不負有心人&#xff0c;總算是給我找到了&#xff0c;現在做一下記錄 登錄不上的原因在于&#xff1a;wp-admin和wp-admin/是不同的&a…

深入理解Angular訂閱者模式

深入理解Angular訂閱者模式 如果正在讀此篇文章的你學過java,c++等面向對象語言,知道兩個模式觀察者模式和訂閱者模式,分別為:Observer pattern,Pub-sub pattern(Subscriber) 接下來我們結合Angular來說明這兩個模式。 Observer pattern This is a pattern of developme…

Ubuntu中安裝python3

通過命令行安裝Python3.*&#xff0c;只需要在終端中通過命令行安裝即可&#xff1a; sudo apt-get install python3 Ubuntu的底層大多數采用的是Python2.*&#xff0c;Python3和Python2是互相不兼容的&#xff0c;完全沒法通用的&#xff08;也不知道他們怎么想的o(TヘTo)&a…

Angular深入理解之指令

Angular深入理解之指令 指令有什么功能 Attribute directives 屬性指令Structural directives 結構指令自定義屬性指令自定義結構指令Angular深入理解之指令 對于初學Angular的同學來說,指令無疑是最痛苦的,那么我們怎么使用自定義的指令呢?指令到底怎么實現呢?為什么要寫…

windows下Apache虛擬主機配置

找到host文件&#xff1a;C:\Windows\System32\drivers\etc\hosts 在hosts這么增加&#xff1a; 127.0.0.1 666.666.com 127.0.0.1 777.777.com 修改httpd.conf文件&#xff1a; 打開文件&#xff1a;xxx\xampp\apache\conf\httpd.conf 找到#LoadModule vhost_…

Angular深入理解基本組成

Angular深入理解基本組成 在講指令時,我們先來了解一下Angular的基本概念和結構。 Module 模塊 Angular 是模塊化的.Modules 導出 classes, function, values , 以便在其他模塊導入使用.angular應用由模塊組成,每個模塊都做此模塊相關的事情組件、方法、類、服務等,他們都…

1607: 字符棱形

1607: 字符棱形 根據讀入的字符和邊長&#xff0c;勾畫字符棱形。 Input 輸入數據含有不超過50組的數據&#xff0c;每組數據包括一個可見字符c和一個整數n&#xff08;1≤n≤30&#xff09;。 Output 輸出以c為填充字符&#xff0c;邊長為n的棱形&#xff0c;勾畫每個棱形…

Angular深入理解管道Pipe

Angular深入理解管道 純管道與非純管道區別的本質 Pure FunctionImpure Function內置Pipe pipe使用自定義Pipe 管道性能優化Angular深入理解管道 管道的鏈接 有學過linux shell的同學,應該知道管道,在shell中的管道是IPC,linux的進程間通訊有pipe,FIFO,signal。這里只是簡單…

1959: 圖案打印

1959: 圖案打印 Description 一年一度的植樹節就要到了&#xff0c;計算機學院學生準備在學院教學樓門前的空地上種植樹木。為使樹木排列得更加美觀&#xff0c;大家決定把樹木排列成菱形。現在告訴你我們所擁有的樹木能排列成邊長為N的菱形&#xff0c;請你編程輸出樹木所排…

JS事件的捕獲和冒泡階段

JS事件的捕獲和冒泡階段 這里介紹兩個事件模型&#xff1a;IE事件模型與DOM事件模型 IE內核瀏覽器的事件模型是冒泡型事件&#xff08;沒有捕獲事件過程&#xff09;&#xff0c;事件句柄的觸發順序是從ChildNode到ParentNode。 <div id"ancestor"> <butt…