1. 程式人生 > 實用技巧 >模板template用法

模板template用法

模板(Template)指C++程式設計設計語言中採用型別作為引數的程式設計,支援通用程式設計。C++ 的標準庫提供許多有用的函式大多結合了模板的觀念,如STL以及IO Stream。 參考:https://www.jianshu.com/p/31d7e18372e2

函式模板定義一族函式。

//template1.cpp #include <iostream>

template<typename T> void swap(T &a, T &b) {

T tmp{a}; a = b;

b = tmp;

}

int main(int argc, char* argv[])

{

int a = 2; int b = 3;

swap(a, b); // 使用函式模板

std::cout << "a=" << a << ", b=" << b << std::endl;

double c = 1.1; double d = 2.2; swap(c, d);std::cout << "c=" << c << ", d=" << d << std::endl;return 0;

}

函式模板的格式:

template<parameter-list> function-declaration

函式模板在形式上分為兩部分:模板、函式。在函式前面加上 template<...>就成為函式模板,因此對函式的各種修飾(inline、constexpr 等)需要加在 function-declaration 上,而不是 template 前。如

template

inline T min(const T &, const T &);

parameter-list 是由英文逗號(,)分隔的列表,每項可以是下列之一:

上面 swap 函式模板,使用了型別形參。函式模板就像是一種契約,任何滿足該契約的型別都可以做為模板實參。而契約就是函式實現中,模板實參需要支援的各種操作。上面

swap 中 T 需要滿足的契約為:支援拷貝構造和賦值。

C++ 的函式模板本質上函式的過載,泛型只是簡化了程式設計師的工作,讓這些過載通過編譯器來完成。我們可以用 c++flit 來觀察這一現象。

通過 objdump 可以把 template1.out 中程式碼段的與 swap 相關的符號提出來,有兩個, 分別是_Z4swapIdEvRT_S1_和_Z4swapIiEvRT_S1_

再用 f++filt 將 Name Mangling 後的名字翻轉過來,就對應了兩個函式原型:

void swap(double&, double&)

void swap(int&, int&)

1.2 函式模板不是函式

剛才我們提到函式模板用來定義一族函式,而不是一個函式。C++是一種強型別的語

言,在不知道 T 的具體型別前,無法確定 swap 需要佔用的棧大小(引數棧,區域性變數), 同時也不知道函式體中 T 的各種操作如何實現,無法生成具體的函式。只有當用具體

型別去替換 T 時,才會生成具體函式,該過程叫做函式模板的例項化。當在 main 函式中呼叫 swap(a,b)時,編譯器推斷出此時 T 為 int,然後編譯器會生成 int 版的 swap 函式供呼叫。所以相較普通函式,函式模板多了生成具體函式這一步。如果我們只是編

寫了函式模板,但不在任何地方使用它(也不顯式例項化),則編譯器不會為該函式模板生成任何程式碼。函式模板例項化分為隱式例項化和顯式例項化。

1.3 隱式例項化 implicit instantiation

仍以 swap 為例,我們在main 中呼叫 swap(a,b)時,就發生了隱式例項化。當函式模板被呼叫,且在之前沒有顯式例項化時,即發生函式模板的隱式例項化。如果模板實參能從呼叫的語境中推導,則不需要提供。效率較低。

//template2.cpp #include

template void print(const T &r) {

std::cout << r << std::endl;

}

int main() {

// 隱式例項化print(int) print(1);

// 例項化 print(char) print<>('c');

// 仍然是隱式例項化,我們希望編譯器生成print(double) print(1);

return 0;

}

1.4 顯式例項化 explicit instantiation

隱式例項化可能影響效率,所以需要提高效率的顯式例項化,顯式例項化在編譯期間就會生成例項(增加了編譯時間)。在函式模板定義後,我們可以通過顯式例項化的方式告訴編譯器生成指定實參的函式。顯式例項化宣告會阻止隱式例項化。如果我們在顯式例項化時,只指定部分模板實參,則指定順序必須自左至右依次指定,不能越過前參模板形參,直接指定後面的。

1.5. 模板函式的隱式/顯示化與特化辨析

如果你真的想要例項化(而不是特殊化或某物)的功能,請執行以下操作:

template void func(T param) {} // definition

template void func(int param); // explicit instantiation.

template <> void func(int param) {} // specialization

總結一下,C++只有模板顯式例項化(explicit instantiation),隱式例項化(implicit instantiation),特化(specialization,也譯作具體化,偏特化)。首先考慮如下模板函式程式碼:

template

void swap(T &a, T &b){

...

}

1.隱式例項化

我們知道,模板函式不是真正的函式定義,他只是如其名提供一個模板,模板只有在執行時才會生成相應的例項,隱式例項化就是這種情況:

int main(){

....

swap(a,b);

....

}

它會在執行到這裡的時候才生成相應的例項,很顯然的影響效率這裡順便提一下 swap<int>(a,b);中的<int>是可選的,因為編譯器可以根據函式引數型別自動進行判斷,也就是說如果編譯器不不能自動判斷的時候這個就是必要的;

2.顯式例項化

前面已經提到隱式例項化可能影響效率,所以需要提高效率的顯式例項化,顯式例項化在編譯期間就會生成例項,方法如下:

template void swap(int &a,int &b);

這樣就不會影響執行時的效率,但編譯時間隨之增加。

3.特化

這個 swap 可以處理一些基本型別如 long int double,但是如果想處理使用者自定義的型別就不行了,特化就是為了解決這個問題而出現的:

template <> void swap(job a,job b){...}

其中 job 是使用者定義的型別.

2. 函式模板的使用

2.1 使用非型別形參

//template3.cpp

#include

// N 必須是編譯時的常量表達式

template

void printArray(const T (&a)[N]) {

std::cout << "[";

const char *sep = "";

for (int i = 0; i < N; i++, (sep = ", ")) {

std::cout << sep << a[i];

}

std::cout << "]" << std::endl;

}

int main() {

// T: int, N: 3

int a[]={1, 2, 3};

printArray(a);

float b[] = {1.1, 2.2, 3.3};

printArray(b);

return 0;

}

2.2返回值為 auto

有些時候我們會碰到這樣一種情況,函式的返回值型別取決於函式引數某種運算後的型別。對於這種情況可以採用auto關鍵字作為返回值佔位符。 decltype 操作符用於查詢表示式的資料型別,也是 C++11 標準引入的新的運算子,其目的是解決泛型程式設計中有些型別由模板引數決定,而難以表示的問題。為何要將返回值後置呢?

// 這樣是編譯不過去的,因為 decltype(a*b)中,a 和 b 還未宣告,編譯器不知道 a 和 b 是什麼。

template

decltype(a*b) multi(T a, T b) {

return a*+ b;

}

//編譯時會產生如下錯誤:error: use of undeclared identifier 'a'

//template4.cpp

#include

using namespace std;

template

auto multi(T1 a, T2 b) -> decltype(a * b) {

return a * b;

}

int main(int argc, char* argv[])

{

cout << multi(2, 3) << endl;

cout << multi(2.2, 3.0) << endl;

cout << multi(2.2, 4) << endl;

cout << multi(3, 4.4) << endl;

return 0;

}

2.3 類成員函式模板

函式模板可以做為類的成員函式。

需要注意的是:函式模板不能用作虛擬函式。這是因為 C++編譯器在解析類的時候就要確定虛擬函式表(vtable)的大小,如果允許一個虛擬函式是函式模板,那麼就需要在解析這個類之前掃描所有的程式碼,找出這個模板成員函式的呼叫或顯式例項化操作,然後才能確定虛擬函式表的大小,而顯然這是不可行的。

//template5.cpp

#include <iostream>

class object {

public:

template<typename T>

void print(const char *name, const T &v){

std::cout << name << ": " << v << std::endl;

}

};

int main() {

object o;

o.print("name", "Crystal");

o.print("age", 18);

return 0;

}

2.4 函式模板過載

函式模板之間、普通函式和模板函式之間可以過載。編譯器會根據呼叫時提供的函式引數,呼叫能夠處理這一型別的最佳匹配版本。在匹配度上,一般按照如下順序考慮:

可以通過空模板實參列表來限定編譯器只匹配函式模板,比如 main 函式中的最後一條語句。

//template6.cpp

#include

#include

template

const T &max(const T &a, const T &b)

{ std::cout << "max(&, &) = ";

return a > b ? a : b;

}

// 函式模板過載

template

const T *max(T *a, T *b) {

std::cout << "max(*, *) = ";

return *a > *b ? a : b;

}

// 函式模板過載

template

const T &max(const T &a, const T &b, const T &c)

{ std::cout << "max(&, &, &) = ";

const T &t = (a > b ? a : b);

return t > c ? t : c;

}

// 普通函式

const char *max(const char *a, const char *b)

{ std::cout << "max(const char *, const char *) = ";

return strcmp(a, b) > 0 ? a : b;

}

int main() {

int a = 1, b = 2;

std::cout << max(a, b) << std::endl;

std::cout << *max(&a, &b) << std::endl;

std::cout << max(a, b, 3) << std::endl;

std::cout << max("en", "ch") << std::endl;

// 可以通過空模板實參列表來限定編譯器只匹配函式模板

std::cout << max<>("en", "ch") << std::endl;

std::cout << max(100, 200) << std::endl;

return 0;

}

2.5 函式模板特化 specialization

當函式模板需要對某些型別進行特別處理,這稱為函式模板的特化。當我們定義一個特化版本時,函式引數型別必須與一個先前宣告的模板中對應的型別匹配。函式模板特化的本質是例項化一個模板,而非過載它。因此,特化不影響編譯器函式匹配。

//template7.cpp

#include

#include

using namespace std;

template

int compare(const T1 &a, const T2 b) {

return a - b;

}

// 對 const char *進行特化

template<>

int compare(const char * const &a, const char * const &b) {

return strcmp(a, b);

}

int main(int argc, char* argv[])

{

cout << compare(100, 200) << endl;

cout << compare("abc", "xyz") << endl;

return 0;

}

上面的例子中針對 const char *的特化,我們其實可以通過函式過載達到相同效果。因此對於函式模板特化,目前公認的觀點是沒什麼用,並且最好別用。Why Not Specialize Function Templates?

但函式模板特化和過載在過載決議時有些細微的差別。這些差別中比較有用的一個是阻止某些隱式轉換。如當你只有 void foo(int)時,以浮點型別呼叫會發生隱式轉換,這可以通過特化來阻止:

template <class T>void foo(T);

template <> void foo(int) {}

foo(3.0); // link error,阻止 float 隱式轉換為 int

雖然模板配過載也可以達到同樣的效果,但特化版的意圖更加明確。

函式模板及其特化版本應該宣告在同一個標頭檔案中。所有同名模板的宣告應該放在前面,然後是這些模板的特化版本。

程式執行結果和使用函式模板特化相同。但是,使用普通函式過載和使用模板特化還是有不同之處,主要表現在如下兩個方面:

(1)如果使用普通過載函式,那麼不管是否發生實際的函式呼叫,都會在目標檔案中生成該函式的二進位制程式碼。而如果使用模板的特化版本,除非發生函式呼叫,否則不會在目標檔案中包含特化模板函式的二進位制程式碼。這符合函式模板的“惰性例項化”準則。

(2)如果使用普通過載函式,那麼在分離編譯模式下,應該在各個原始檔中包含過載函式的申明,否則在某些原始檔中就會使用模板函式,而不是過載函式。

2.6 變參函式模板(模板引數包)

這是 C++11 引入的新特性,用來表示任意數量的模板形參。其語法樣式如下:

template<typename ...Args> // Args: 模板引數包

void foo(Args ... args);// args: 函式引數包

在模板形參 Args 的左邊出現三個英文點號"...",表示 Args 是零個或多個型別的列表,是一個模板引數包(template parameter pack)。正如其名稱一樣,編譯器會將 Args 所表示的型別列表打成一個包,將其當做一個特殊型別處理。相應的函式引數列表中也有一個函式引數包。與普通模板函式一樣,編譯器從函式的實參推斷模板引數型別,與此同時還會推斷包中引數的數量。

// sizeof...() 是 C++11 引入的引數包的操作函式,用來取引數的數量

template

int length(Args ... args) {

return sizeof...(Args);

}

// 以下語句將在螢幕打印出:2

std::cout << length(1, "hello") << std::endl;

變參函式模板主要用來處理既不知道要處理的實參的數目也不知道它們的型別時的場景。既然我們對實引數量以及型別都一無所知,那麼我們怎麼使用它呢?最常用的方法是遞迴。

//template8.cpp

#include

using namespace std;

// sizeof...() 是 C++11 引入的引數包的操作函式,用來取引數的數量

template

int length(Args ... args) {

return sizeof...(Args);

}

int main(int argc, char* argv[])

{

// 以下語句將在螢幕打印出:2

cout << length(1, "hello") << endl; // 以下語句將在螢幕打印出:3

cout << length(1, "hello", 2) << endl; // 以下語句將在螢幕打印出:4

cout << length(1, "hello", 2, 3) << endl; // 以下語句將在螢幕打印出:5

cout << length(1, "hello", 2, 3, 4) << endl;

return 0;

}

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <vector>
#include <map>

// 在C++11之前,類模板和函式模板只能含有固定數量的模板引數。C++11增強了模板功能,允許模板定義中包含0到任意個模板引數,這就是可變引數模板。

// 可變引數模板類 繼承方式展開引數包
// 可變引數模板類的展開一般需要定義2 ~ 3個類,包含類宣告和特化的模板類
template<typename... A> class BMW{};  // 變長模板的宣告

template<typename Head, typename... Tail>  // 遞迴的偏特化定義
class BMW<Head, Tail...> : public BMW<Tail...>
{//當例項化物件時,則會引起基類的遞迴構造
public:
    BMW()
    {
        printf("type: %s\n", typeid(Head).name());
    }

    Head head;
};

template<> class BMW<>{};  // 邊界條件

// 模板遞迴和特化方式展開引數包
template <long... nums> struct Multiply;// 變長模板的宣告

template <long first, long... last>
struct Multiply<first, last...> // 變長模板類
{
    static const long val = first * Multiply<last...>::val;
};

template<>
struct Multiply<> // 邊界條件
{
    static const long val = 1;
};


void mytest()
{
    BMW<int, char, float> car;
    /*
    執行結果:
        type: f
        type: c
        type: i
    */

    std::cout << Multiply<2, 3, 4, 5>::val << std::endl; // 120


    return;
}


int main()
{
    mytest();

    system("pause");
    return 0;
}

類模板https://blog.csdn.net/qq_35637562/article/details/55194097

考慮我們寫一個簡單的棧的類,這個棧可以支援int型別,long型別,string型別等等,不利用類模板,我們就要寫三個以上的stack類,其中程式碼基本一樣,通過類模板,我們可以定義一個簡單的棧模板,再根據需要例項化為int棧,long棧,string棧。

statck.h
template <class T> class Stack {
    public:
        Stack();
        ~Stack();
        void push(T t);
        T pop();
        bool isEmpty();
    private:
        T *m_pT;        
        int m_maxSize;
        int m_size;
};

#include "stack.cpp"
//stack.cpp
template <class  T>  Stack<T>::Stack(){
   m_maxSize = 100;      
   m_size = 0;
   m_pT = new T[m_maxSize];
}
template <class T>  Stack<T>::~Stack() {
   delete [] m_pT ;
}
        
template <class T> void Stack<T>::push(T t) {
    m_size++;
    m_pT[m_size - 1] = t;
    
}
template <class T> T Stack<T>::pop() {
    T t = m_pT[m_size - 1];
    m_size--;
    return t;
}
template <class T> bool Stack<T>::isEmpty() {
    return m_size == 0;
}

模板引數
模板可以有型別引數,也可以有常規的型別引數int,也可以有預設模板引數,例如

template<class T, T def_val> class Stack{...}

上述類模板的棧有一個限制,就是最多隻能支援100個元素,我們可以使用模板引數配置這個棧的最大元素數,如果不配置,就設定預設最大值為100