C++語言學習(十七)——模板

C++語言學習(十七)——模板

1、模板簡介

泛型(Generic Programming)便是指具備在多種數據類型上皆可操做的含意。 泛型編程的表明做品STL是一種高效、泛型、可交互操做的軟件組件。
泛型編程最初誕生於C++中,目的是爲了實現C++的STL(標準模板庫)。其語言支持機制就是模板(Templates)。模板的核心思想是參數化類型,即把一個本來特定於某個類型的算法或類當中的類型信息抽掉,抽出來作成模板參數T。ios

2、函數模板

一、宏實現交換函數

定義一個交換兩個數的宏算法

#define SWAP(t, a, b)   \
do                      \
{                       \
    t c = a;            \
    a = b;              \
    b = c;              \
}while(0);

宏代碼塊實現的優勢是代碼複用,適合全部類型;缺點是缺乏類型檢查。編程

二、函數重載實現

#include <iostream>
using namespace std;
void swap(int &a, int& b)
{
    int t = a;
    a = b;
    b = t;
}
void swap(double &a,double b)
{
    double t = a;
    a = b;
    b = t;
}
int main()
{
    int ia = 10; int ib = 20;
    swap(ia,ib);
    cout<<ia<<ib<<endl;
    double da = 10, db = 20;
    swap(da,db);
    cout<<da<<db<<endl;
    return 0;
}

函數重載實現的優勢是真正進行函數調用,C++編譯器進行類型檢查;缺點是根據類型重複定義函數,沒法代碼複用。數組

三、函數模板

函數模板是可用不一樣類型進行調用的特殊函數,關鍵在於類型參數化。
函數模板的語法格式以下:數據結構

template<typename/class 類型參數表>
返回類型 函數模板名(函數參數列表)
{
    函數模板定義體
}

template關鍵字用於聲明開始進行泛型編程。
typename關鍵字用於聲明泛指類型。
函數模板能夠自動推導類型進行調用,也能夠顯示指定具體類型進行調用。ide

四、函數模板實現

#include <iostream>
using namespace std;
template <typename T>
void Swap(T& a,T &b )
{
    T t = a;
    a = b;
    b = t;
}
int main()
{
    int ia = 10; int ib = 20;
    Swap(ia,ib); //Swap<int>(ia,ib);
    cout<<ia<<ib<<endl;
    double da = 10, db = 20;
    Swap(da,db); //Swap<double>(da,db);
    cout<<da<<db<<endl;
    string sa ="china"; string sb = "America";
    Swap(sa,sb);
    cout<<sa<<sb<<endl;
    return 0;
}

判斷一個變量是否是指針類型示例:函數

template 
<typename T>
bool isPtr(T *p) 
{
    return true;
}

template 
<typename T>
bool isPtr(T t)
{
    return false;
}

函數模板,只適用於函數的參數個數相同而類型不一樣,且函數體相同的狀況。若是個數不一樣,則不能用函數模板。學習

五、函數模板分析

C++編譯器從函數模板經過具體類型產生不一樣的函數,C++編譯器會對函數模板進行兩次編譯,一次是函數模板代碼進行編譯,一次是參數替換後的函數代碼進行編譯。this

#include <iostream>

using namespace std;

template <typename T>
void Swap(T& a, T& b)
{
    T c = a;
    a = b;
    b = c;
}

class Test
{

};

typedef void (*pFuncInt)(int&, int&);
typedef void (*pFuncDouble)(double&, double&);
typedef void (*pFuncTest)(Test&, Test&);

int main(int argc, char *argv[])
{
    pFuncInt pi = Swap;//Swap<int>
    printf("0x%x\n", pi);
    pFuncDouble pd = Swap;//Swap<double>
    printf("0x%x\n", pd);
    pFuncTest pt = Swap;//Swap<Test>
    printf("0x%x\n", pt);

    return 0;
}

函數模板自己不容許隱式類型轉換,所以,自動推導類型時須要嚴格匹配,但當顯示指定類型參數時能夠進行隱式類型轉換。
函數模板中的返回值類型必須顯示指定。spa

add<int>(ia,ib);

六、多類型參數函數模板

函數模板能夠定義多個不一樣的類型參數,但沒法自動推導返回值類型,能夠從左向右部分指定類型參數,實際工程中將返回值做爲第一個類型參數,必須顯式指定。

#include <iostream>
#include <string>

using namespace std;

template 
< typename T1, typename T2, typename T3 >
T1 Add(T2 a, T3 b)
{
    return static_cast<T1>(a + b);
}

int main()
{
    // T1 = int, T2 = double, T3 = double
    int r1 = Add<int>(0.5, 0.8);

    // T1 = double, T2 = float, T3 = double
    double r2 = Add<double, float>(0.5, 0.8);

    // T1 = float, T2 = float, T3 = float
    float r3 = Add<float, float, float>(0.5, 0.8);

    cout << "r1 = " << r1 << endl;     // r1 = 1
    cout << "r2 = " << r2 << endl;     // r2 = 1.3
    cout << "r3 = " << r3 << endl;     // r3 = 1.3

    return 0;
}

七、普通函數與函數模板的關係

函數模板能夠被重載,C++編譯器優先考慮普通函數,但若是函數模板能夠產生更好的匹配,則使用函數模板,能夠經過空模板實參列表限定只能使用函數模板。

#include <iostream>
#include <string>

using namespace std;

template < typename T >
T Max(T a, T b)
{
    cout << "T Max(T a, T b)" << endl;

    return a > b ? a : b;
}

int Max(int a, int b)
{
    cout << "int Max(int a, int b)" << endl;

    return a > b ? a : b;
}

template < typename T >
T Max(T a, T b, T c)
{
    cout << "T Max(T a, T b, T c)" << endl;

    return Max(Max(a, b), c);
}

int main()
{
    int a = 1;
    int b = 2;

    cout << Max(a, b) << endl;                   // 普通函數 Max(int, int)

    cout << Max<>(a, b) << endl;                 // 函數模板 Max<int>(int, int)

    cout << Max(3.0, 4.0) << endl;               // 函數模板 Max<double>(double, double)

    cout << Max(5.0, 6.0, 7.0) << endl;          // 函數模板 Max<double>(double, double, double)

    cout << Max('a', 100) << endl;               // 普通函數 Max(int, int)

    return 0;
}

3、類模板

一、類模板的定義

C++語言中將模板的思想應用於類,使得類的實現不關注數據元素的具體類型,只關注類須要實現的功能。
類模板的定義語法以下:

template <typename T>
class classname
{

};

在類聲明前使用template進行標識,&lt;typename T&gt;用於說明類中使用泛指類型T。
類內定義成員函數

template<typename T>
class classname
{
public:
    void push(int size)
    {
}
}

類外定義函數

template<typename T>
void classname<T>::push(T data)
{
}

類模板實例化爲模板類:

classname<double> object;

類模板是類的抽象,類是類模板的實例。

二、類模板應用

類模板只能顯示指定類型參數,沒法自動推導。聲明的泛型類型參數能夠出如今類模板的任意地方。
類模板必須在頭文件中實現,不能分開實如今不一樣文件中。類模板的成員函數須要定義在外部定義時,每一個成員函數須要加上類模板template&lt;typename T&gt;聲明。
類模板適合以相同的邏輯處理不一樣的數據類型的數據,所以很是適合編寫數據結構相關代碼。

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
template<typename T>
class Stack
{
public:
    Stack(int size)
    {
        space = new T[size];
        top = 0;
    }
    ~Stack();
    bool isEmpty();
    bool isFull();
    void push(T data);
    T pop();
private:
    T* space;
    int top;
};
template<typename T>
Stack<T>::~Stack()
{
    delete []space;
}
template<typename T>
bool Stack<T>::isEmpty()
{
    return top == 0;
}
template<typename T>
bool Stack<T>::isFull()
{
    return top == 1024;
}
template<typename T>
void Stack<T>::push(T data)
{
    space[top++] = data;
}
template<typename T>
T Stack<T>::pop()
{
    return space[--top];
}
int main()
{
    Stack<double> s(100); //Stack<string> s(100);
    if(!s.isFull())
        s.push(10.3);
    if(!s.isFull())
        s.push(20);
    if(!s.isFull())
        s.push(30);
    if(!s.isFull())
        s.push(40);
    if(!s.isFull())
        s.push(50);
    while(!s.isEmpty())
        cout<<s.pop()<<endl;
    return 0;
}

三、類模板分析

類模板經過具體類型產生不一樣的類,C++編譯器在類模板聲明的地方對類模板代碼自己進行編譯,在使用的地方對類模板參數替換後產生的代碼進行編譯。
類模板能夠定義多個不一樣類型參數。

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class Operator
{
public:
    Operator()
    {
        cout << "Operator()" << endl;
    }
    T add(T a, T b)
    {
        cout << "T add(T a, T b)" << endl;
        return a + b;
    }
    T minus(T a, T b)
    {
        return a - b;
    }
    T multiply(T a, T b)
    {
        return a * b;
    }
    T divide(T a, T b)
    {
        return a / b;
    }
};

int main(int argc, char *argv[])
{
    Operator<int> op1;
    cout << op1.add(1, 2) << endl;
    cout << op1.add(1, 2) << endl;

    Operator<string> op2;

    cout << op2.add("D.T.", "Software") << endl;

    return 0;
}
// output:
// Operator()
// T add(T a, T b)
// 3
// Operator()
// T add(T a, T b)
// 3
// Operator()
// T add(T a, T b)
// Hello World

上述代碼中,類模板中的函數代碼在使用的時候纔會被分別編譯。

4、模板的特化

一、類模板的特化

類模板能夠被特化,如下狀況須要特化類模板:
A、指定特定類型的實現
B、部分參數類型必須顯示指定
C、根據類型參數分開實現類模板
類模板的特化分爲部分特化和徹底特化。部分特化是指用特定規則約束類型參數,徹底特化是指徹底顯示指定類型參數。
類模板的特化是模板的分開實現,本質上是同一個類模板,特化類模板必須顯示指定每個類型參數。編譯器會自動優先選擇特化類模板。

#include <iostream>

using namespace std;

template
<typename T1, typename T2>
class Test
{
public:
    void add(T1 a, T2 b)
    {
        cout << "void add(T1 a, T2 b)" << endl;
        cout << a + b << endl;
    }
};

//部分特化
template
<typename T>
class Test<T,T>
{
public:
    void add(T a, T b)
    {
        cout << "void add(T a, T b)" << endl;
        cout << a + b << endl;
    }
    void print()
    {
        cout << "class Test <T,T>" << endl;
    }
};

//徹底特化
template
<>
class Test<int,int>
{
public:
    void add(int a, int b)
    {
        cout << "void add(int a, int b)" << endl;
        cout << a + b << endl;
    }
    void print()
    {
        cout << "class Test<int,int>" << endl;
    }
};

int main(int argc, char *argv[])
{
    Test<int, int> t1;//徹底特化
    t1.add(1,2);
    t1.print();

    Test<double, double> t2;//部分特化
    t2.add(3.14,2.0);
    t2.print();

    Test<float, double> t3;//類模板
    t3.add(3.14,2.0);

    return 0;
}

// output:
// void add(int a, int b)
// 3
// class Test<int,int>
// void add(T a, T b)
// 5.14
// class Test <T,T>
// void add(T1 a, T2 b)
// 5.14

二、函數模板的特化

函數模板只支持模板的徹底特化。

#include <iostream>

using namespace std;

//函數模板
template
<typename T>
bool Equal(T a, T b)
{
    cout << "bool Equal(T a, T b)" << endl;
    return a == b;
}

//函數特化模板
template
< >
bool Equal<double>(double a, double b)
{
    const double delta = 0.00000000000001;
    double r = a - b;
    cout << "bool Equal<double>(double a, double b)" << endl;
    return (-delta < r) && (r < delta);
}

//函數重載
bool Equal(double a, double b)
{
    const double delta = 0.00000000000001;
    double r = a - b;
    cout << "bool Equal(double a, double b)" << endl;
    return (-delta < r) && (r < delta);
}

int main(int argc, char *argv[])
{
    Equal<double>(0.1,0.1);//函數特化模板
    Equal<int>(10,10);//函數模板
    Equal(0.1,0.1);//函數重載

    return 0;
}

// output:
// bool Equal<double>(double a, double b)
// bool Equal(T a, T b)
// bool Equal(double a, double b)

工程實踐中當須要重載函數模板時,優先使用函數模板特化,當函數模板特化沒法知足需求時,使用函數重載。

5、數組類模板

一、數值型模板

模板參數能夠是數值型參數,數值型模板參數存在限制:
A、變量不能做爲模板參數
B、浮點數不能做爲模板參數
C、類對象不能做爲模板參數
模板參數是在編譯階段處理的,所以在編譯階段須要惟一肯定。
使用最高效方式求1+2+3+4......+100

#include <iostream>
using namespace std;
template
<int N>
class Sum
{
public:
    static const int value = Sum<N-1>::value + N;
};

template
<>
class Sum<1>
{
public:
    static const int value = 1;
};

int main(int argc, char *argv[])
{
    cout<<Sum<100>::value<<endl;
    return 0;
}

二、數組模板

#ifndef _ARRAY_H_
#define _ARRAY_H_

template
< typename T, int N >
class Array
{
    T m_array[N];
public:
    int length();
    bool set(int index, T value);
    bool get(int index, T& value);
    T& operator[] (int index);
    T operator[] (int index) const;
    virtual ~Array();
};

template
< typename T, int N >
int Array<T, N>::length()
{
    return N;
}

template
< typename T, int N >
bool Array<T, N>::set(int index, T value)
{
    bool ret = (0 <= index) && (index < N);
    if( ret )
    {
        m_array[index] = value;
    }
    return ret;
}

template
< typename T, int N >
bool Array<T, N>::get(int index, T& value)
{
    bool ret = (0 <= index) && (index < N);
    if( ret )
    {
        value = m_array[index];
    }
    return ret;
}

template
< typename T, int N >
T& Array<T, N>::operator[] (int index)
{
    return m_array[index];
}

template
< typename T, int N >
T Array<T, N>::operator[] (int index) const
{
    return m_array[index];
}

template
< typename T, int N >
Array<T, N>::~Array()
{

}

#endif

6、智能指針類模板

智能指針是C++開發庫的重要類模板之一,是自動內存管理的主要手段,能夠避開內存的相關問題。

一、STL的智能指針

STL中的智能指針分爲auto_ptr、shared_ptr、weak_ptr、unique_ptr四類。
auto_ptr智能指針的特性:
A、生命週期結束時,銷燬指向的內存空間
B、不能指向堆數組,只能指向堆對象
C、一塊堆空間只能屬於一個智能指針
D、多個智能指針對象不能指向同一塊空間
shared_ptr智能指針的特性:
帶有引用計數機制,支持多個指針指向同一對象內存空間。
weak_ptr智能指針的特性:
weak_ptr是一種弱引用,指向shared_ptr所管理的對象。
unique_ptr智能指針的特性:
一個指針對象指向一片內存空間,不能拷貝構造和賦值

STL智能指針使用實例:

#include <iostream>
#include <memory>

using namespace std;

class Test
{
    string m_name;
public:
    Test(const char* name)
    {
        cout << "Hello, " << name << "." << endl;
        m_name = name;
    }

    void print()
    {
        cout << "I'm " << m_name << "." << endl;
    }

    ~Test()
    {
        cout << "Goodbye, " << m_name << "." << endl;
    }
};

int main(int argc, char *argv[])
{
    auto_ptr<Test> pt(new Test("D.T.Software"));
    cout << "pt = " << pt.get() << endl;
    pt->print();
    cout << endl;

    auto_ptr<Test> pt1(pt);
    cout << "pt = " << pt.get() << endl;//NULL
    cout << "pt1 = " << pt1.get() << endl;//

    return 0;
}

二、QT中的智能指針

QT中的主要智能指針有:QPointer、QSharedPointer、QWeakPointer、QScopedPointer、QSharedDataPoiner、QExplicitlySharedDataPointer。
QPointer智能指針特性:
A、當其所指向的對象被銷燬時會被自動置空
B、析構時不會自動銷燬所指向的對象
多個QPointer指針對象能夠指向同一內存空間,當所指向對象被銷燬時指針會被自動置空,可是指針對象析構時不會自動銷燬所指向的對象。
QSharedPointer智能指針特性:
A、引用計數型智能指針
B、能夠被自由的拷貝和賦值
C、當引用計數爲0時才刪除指向的對象
QT中智能指針使用實例:

#include <QPointer>
#include <QSharedPointer>
#include <QDebug>

class Test : public QObject
{
    QString m_name;
public:
    Test(const char* name)
    {
        qDebug() << "Hello, " << name << ".";

        m_name = name;
    }

    void print()
    {
        qDebug() << "I'm " << m_name << ".";
    }

    ~Test()
    {
        qDebug() << "Goodbye, " << m_name << ".";
    }
};

int main()
{
    QPointer<Test> pt(new Test("D.T.Software"));
    QPointer<Test> pt1(pt);
    QPointer<Test> pt2(pt);//多個QPointer指針對象能夠指向同一內存空間

    pt->print();
    pt1->print();
    pt2->print();

    delete pt;//QPointer智能指針指向的對象被銷燬時,指針對象被置空

    qDebug() << "pt = " << pt;//NULL
    qDebug() << "pt1 = " << pt1;//NULL
    qDebug() << "pt2 = " << pt2;//NULL

    qDebug() << endl;

    QSharedPointer<Test> spt(new Test("Delphi Tang"));
    QSharedPointer<Test> spt1(spt);
    QSharedPointer<Test> spt2(spt);

    spt->print();
    spt1->print();
    spt2->print();

    return 0;//指針對象都被銷燬時引用計數爲0,自動析構指針指向的對象
}

三、智能指針類模板

#ifndef _SMARTPOINTER_H_
#define _SMARTPOINTER_H_

template
< typename T >
class SmartPointer
{
    T* mp;
public:
    SmartPointer(T* p = NULL)
    {
        mp = p;
    }

    SmartPointer(const SmartPointer<T>& obj)
    {
        mp = obj.mp;
        const_cast<SmartPointer<T>&>(obj).mp = NULL;
    }

    SmartPointer<T>& operator = (const SmartPointer<T>& obj)
    {
        if( this != &obj )
        {
            delete mp;
            mp = obj.mp;
            const_cast<SmartPointer<T>&>(obj).mp = NULL;
        }

        return *this;
    }

    T* operator -> ()
    {
        return mp;
    }

    T& operator * ()
    {
        return *mp;
    }

    bool isNull()
    {
        return (mp == NULL);
    }

    T* get()
    {
        return mp;
    }

    ~SmartPointer()
    {
        delete mp;
    }
};

#endif

7、單例類模板

某些類在整個系統的生命週期中只能有一個對象存在,即單例模式。
要控制類的對象數目必須隱藏類的構造函數,即構造函數聲明爲private。
定義一個instance標識符,初始化爲NULL,當須要使用對象時查看instance的值,若是instance爲NULL則建立對象並用instance標識,若是instance非空則返回instance標識的值。

#ifndef _SINGLETON_H_
#define _SINGLETON_H_

template
< typename T >
class Singleton
{
    static T* c_instance;
public:
    static T* GetInstance();
};

template
< typename T >
T* Singleton<T>::c_instance = NULL;

template
< typename T >
T* Singleton<T>::GetInstance()
{
    if( c_instance == NULL )
    {
        c_instance = new T();
    }

    return c_instance;
}

#endif

使用代碼:

#include <iostream>
#include <string>
#include "Singleton.h"

using namespace std;

class SObject
{
    friend class Singleton<SObject>;    // 當前類須要使用單例模式

    SObject(const SObject&);
    SObject& operator= (const SObject&);

    SObject()
    {
    }
public:

    void print()
    {
        cout << "this = " << this << endl;
    }
};

int main()
{
    SObject* s = Singleton<SObject>::GetInstance();
    SObject* s1 = Singleton<SObject>::GetInstance();
    SObject* s2 = Singleton<SObject>::GetInstance();

    s->print();
    s1->print();
    s2->print();

    return 0;
}

8、模板應用示例

判斷一個變量是否是指針
C++編譯器匹配的調用優先級:
A、重載函數
B、函數模板
C、變參函數
能夠根據C++編譯器匹配的調用優先級,將函數模板匹配指針變量,返回true,變參函數匹配非指針變量,返回false。

template
<typename T>
bool IsPtr(T *pt)
{
    return true;
}

bool IsPtr(...)
{
    return false;
}

可是,因爲變參函數是C語言的內容,沒法解析C++自定義類型對象,可能形成程序崩潰。

template
<typename T>
char IsPtr(T* v) // match pointer
{
    return 'd';
}

int IsPtr(...)  // match non-pointer
{
    return 0;
}

#define ISPTR(p) (sizeof(IsPtr(p)) == sizeof(char))

上述代碼中,C++編譯器在編譯時會進行函數匹配,不會進行調用,避免了參數爲自定義對象時調用變參函數致使的程序崩潰。

9、class與typename關鍵字

C++語言在引入了面向對象編程思想時,使用class關鍵字定義類類型。C++語言發展過程當中引入了泛型編程,直接複用class關鍵字來定義模板。但泛型編程針對的不僅是類類型,直接複用class關鍵字會使代碼出現二義性。所以,C++直接引入了typename關鍵字,用於在模板定義中聲明泛指類型,明確告訴C++編譯器聲明的標識符爲類型。
C++語言中容許類定義中嵌套類型,所以當自定義類類型中嵌套類型的標識符與其它類類型中定義的成員變量標識符重名時將會形成二義性。不一樣類中的同名標識符表明可能致使二義性,所以C++編譯器沒法識別標識符的確切意義。

#include <iostream>

using namespace std;

class Test1
{
public:
    static const int NS = 1;
};

class Test2
{
public:
    struct NS
    {
        int value;
    };
};

int a = 0;

template <class T>
void func()
{
    T::NS* a;
}

int main(int argc, char *argv[])
{
    func<Test1>();
    //func<Test2>();//error
    //error: dependent-name 'T:: NS' is parsed as a non-type,
    //but instantiation yields a type
    //say 'typename T:: NS' if a type is meant

    return 0;
}

上述代碼中,C++編譯器不會將func函數模板中NS解析爲類型,所以使用Test2做爲參數時,C++編譯器會報錯。所以,爲了將NS明確聲明爲類型,須要使用typename關鍵字對NS標識符進行聲明。代碼以下:

#include <iostream>

using namespace std;

class Test1
{
public:
    static const int NS = 1;
};

class Test2
{
public:
    struct NS
    {
        int value;
    };
};

int a = 0;

template <class T>
void func()
{
    typename T::NS* a;
}

int main(int argc, char *argv[])
{
    //func<Test1>();//error
    func<Test2>();
    return 0;
}

上述代碼中,NS被明確聲明爲類型,所以若是使用Test1做爲參數,func函數模板將會報錯。

相關文章
相關標籤/搜索