1. 程式人生 > >6、【C++】模板

6、【C++】模板

C++ 模板

    模板是泛型程式設計的基礎泛型程式設計即以一種獨立於任何特定型別的方式編寫程式碼

1、函式模板
    int swap(int &a, int &b){int temp = a;a = b; b=temp;}
    float swap(float &a, float &b){float temp = a;a = b; b=temp;}
    char swap(char &a, char &b){char temp = a;a = b; b=temp;}

    在這上面三條語句當中,int float char 這三種資料型別,但是他們具體實現的函式是一樣的,所以我們就是想把函式作為引數傳入進去,然後讓計算機幫我們實現這三條語句的編寫。

函式模板關鍵字:template;typename;class

    函式模板可以用來建立一個通用的函式,以支援多種不同的形參,避免過載函式的函式體重複設計。它的最大特點是把函式使用的資料型別作為引數。

函式模板的宣告形式為

    template<typename 資料型別引數識別符號>
    <返回型別><函式名>(引數表)
    {
	  函式體
    }

【示例】

//method.h
template<typename T> void swap(T& t1, T& t2);

#include "method.cpp"
template<typename  T> 
void swap(T& t1, T& t2) {
    T tmpT;
    tmpT = t1;
    t1 = t2;
    t2 = tmpT;
}
//main.cpp
#include <stdio.h>
#include "method.h"
int main() {
    //模板方法 
    int num1 = 1, num2 = 2;
    swap<int>(num1, num2);
    printf("num1:%d, num2:%d\n", num1,
num2); return 0; }

    這裡使用swap函式,必須包含swap的定義,否則編譯會出錯,這個和一般的函式使用不一樣。所以必須在method.h檔案中加入#include “method.cpp”。

2、類模板

    正如我們定義函式模板一樣,我們也可以定義類模板。泛型類宣告的一般形式如下所示:

template <class type> class class-name 
{
	……
}

    在這裡,type 是佔位符型別名稱,可以在類被例項化的時候進行指定。您可以使用一個逗號分隔的列表來定義多個泛型資料型別。

    考慮我們寫一個簡單的棧的類,這個棧可以支援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;
};
//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;
}
//main.cpp
#include <stdio.h>
#include "stack.h"
int main() {
    Stack<int> intStack;
    intStack.push(1);
    intStack.push(2);
    intStack.push(3);
    
    while (!intStack.isEmpty()) {
        printf("num:%d\n", intStack.pop());
    }
    return 0;
}
3、模板引數

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

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

    在類模板的示例中,有一個限制,就是最多隻能支援100個元素,我們可以使用模板引數配置這個棧的最大元素數,如果不配置,就設定預設最大值為100,程式碼如下:

//statck.h
template <class T,int maxsize = 100> 
class Stack {
public:
    Stack();
    ~Stack();
    void push(T t);
    T pop();
    bool isEmpty();
private:
    T *m_pT;        
    int m_maxSize;
    int m_size;
};
//stack.cpp
template <class T,int maxsize> 
Stack<T, maxsize>::Stack(){
   m_maxSize = maxsize;      
   m_size = 0;
   m_pT = new T[m_maxSize];
}
template <class T,int maxsize>  
Stack<T, maxsize>::~Stack() {
   delete [] m_pT ;
}
        
template <class T,int maxsize> 
void Stack<T, maxsize>::push(T t) {
    m_size++;
    m_pT[m_size - 1] = t;
    
}
template <class T,int maxsize> 
T Stack<T, maxsize>::pop() {
    T t = m_pT[m_size - 1];
    m_size--;
    return t;
}
template <class T,int maxsize> 
bool Stack<T, maxsize>::isEmpty() {
    return m_size == 0;
}
//main.cpp
#include <stdio.h>
#include "stack.h"
int main() {
    int maxsize = 1024;
    Stack<int,1024> intStack;
    for (int i = 0; i < maxsize; i++) {
        intStack.push(i);
    }
    while (!intStack.isEmpty()) {
        printf("num:%d\n", intStack.pop());
    }
    return 0;
}
4、模板專門化

    當我們要定義模板的不同實現,我們可以使用模板的專門化。例如我們定義的stack類模板,如果是char*型別的棧,我們希望可以複製char的所有資料到stack類中,因為只是儲存char指標,char指標指向的記憶體有可能會失效,stack彈出的堆疊元素char指標,指向的記憶體可能已經無效了。還有我們定義的swap函式模板,在vector或者list等容器型別時,如果容器儲存的物件很大,會佔用大量記憶體,效能下降,因為要產生一個臨時的大物件儲存a,這些都需要模板的專門化才能解決。

(1)函式模板專門化

    假設我們swap函式要處理一個情況,我們有兩個很多元素的vector,在使用原來的swap函式,執行tmpT = t1要拷貝t1的全部元素,佔用大量記憶體,造成效能下降,於是我們系統通過vector.swap函式解決這個問題,程式碼如下:

//method.h
#include "method.cpp"

template<class T> void swap(T& t1, T& t2);
#include <vector>
using namespace std;
template<class T> 
void swap(T& t1, T& t2) {
    T tmpT;
    tmpT = t1;
    t1 = t2;
    t2 = tmpT;
}
//模板專門化
template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
    t1.swap(t2);
}

    template<>字首表示這是一個專門化,描述時不用模板引數,使用示例如下:

//main.cpp
#include <stdio.h>
#include <vector>
#include <string>
#include "method.h"
int main() {
    using namespace std;
    //模板方法 
    string str1 = "1", str2 = "2";
    swap(str1, str2);
    printf("str1:%s, str2:%s\n", str1.c_str(), str2.c_str());  
    
    vector<int> v1, v2;
    v1.push_back(1);
    v2.push_back(2);
    swap(v1, v2);
    for (int i = 0; i < v1.size(); i++) {
        printf("v1[%d]:%d\n", i, v1[i]);
    }
    for (int i = 0; i < v2.size(); i++) {
        printf("v2[%d]:%d\n", i, v2[i]);
    }
    return 0;
}

    vector的swap程式碼還是比較侷限,如果要用模板專門化解決所有vector的swap,該如何做呢,只需要把下面程式碼

template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
    t1.swap(t2);
}

    改為

template<class V> void swap(std::vector<V>& t1, std::vector<V>& t2) {
    t1.swap(t2);
}

    就可以了,其他程式碼不變。

(2)類模板專門化

//compare.h
template <class T>
class compare
{
public:
  bool equal(T t1, T t2)
  {
       return t1 == t2;
  }
};
#include <iostream>
#include "compare.h"
int main()
{
  using namespace std;
  char str1[] = "Hello";
  char str2[] = "Hello";
  compare<int> c1;
  compare<char *> c2;   
  cout << c1.equal(1, 1) << endl;        //比較兩個int型別的引數
  cout << c2.equal(str1, str2) << endl;   //比較兩個char *型別的引數
  return 0;
}

    在比較兩個整數,compare的equal方法是正確的,但是compare的模板引數是char*時,這個模板就不能工作了,於是修改如下:

//compare.h
#include <string.h>
template <class T>
class compare
{
public:
  bool equal(T t1, T t2)
  {
       return t1 == t2;
  }
};
   
template<>class compare<char *>  
{
public:
    bool equal(char* t1, char* t2)
    {
        return strcmp(t1, t2) == 0;
    }
};

    main.cpp檔案不變,此程式碼可以正常工作。

5、模板型別轉換

    還記得我們自定義的Stack模板嗎,在我們的程式中,假設我們定義了Shape和Circle類,程式碼如下:

//shape.h
class Shape {

};
class Circle : public Shape {
};

    然後我們希望可以這麼使用:

//main.cpp
#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
    Stack<Circle*> pcircleStack;
    Stack<Shape*> pshapeStack;
    pcircleStack.push(new Circle);
    pshapeStack = pcircleStack;
    return 0;
}

    這裡是無法編譯的,因為Stack<Shape*>不是Stack<Circle*>的父類,然而我們卻希望程式碼可以這麼工作,那我們就要定義轉換運算子了,Stack程式碼如下:

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

#include "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;
}

template <class T> template <class T2>  
Stack<T>::operator Stack<T2>() {
    Stack<T2> StackT2;
    for (int i = 0; i < m_size; i++) {
        StackT2.push((T2)m_pT[m_size - 1]);
    }
    return StackT2;
}
//main.cpp
#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
    Stack<Circle*> pcircleStack;
    Stack<Shape*> pshapeStack;
    pcircleStack.push(new Circle);
    pshapeStack = pcircleStack;
    return 0;
}

    這樣,Stack 或者Stack<Circle*>就可以自動轉換為Stack或者Stack<Shape*>,如果轉換的型別是Stack到Stack,編譯器會報錯。

6、其他

     一個類沒有模板引數,但是成員函式有模板引數,是可行的,程式碼如下:

class Util {
public:
    template <class T> bool equal(T t1, T t2) {
        return t1 == t2;
    }
};

int main() {
    Util util;
    int a = 1, b = 2;
    util.equal<int>(1, 2);
    return 0;
}

     甚至可以把Util的equal宣告為static,程式碼如下:

class Util {
public:
     template <class T> static bool equal(T t1, T t2) {
        return t1 == t2;
    }
};

int main() {
    int a = 1, b = 2;
    Util::equal<int>(1, 2);
    return 0;
}