程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++泛型編程

C++泛型編程

編輯:關於C++

寫在前面
0. 所謂泛型編程就是獨立於任何特定類型的方式編寫代碼,使用泛型程序時,需要提供具體陳旭實例所操作的類型或者值。我們經常用到STL容器、迭代器、和算法都是泛型編程的例子;

模板是C++支持參數化多態的工具,使用模板可以使用戶為類或者函數聲明一種一般模式,使得類中的某些數據成員或者成員函數的參數、返回值取得任意類型; 模板是一種對類型進行參數化的工具; 通常有兩種形式:函數模板和類模板; 函數模板針對僅參數類型不同的函數; 類模板針對僅數據成員和成員函數類型不同的類; 使用模板的目的就是能夠讓程序員編寫與類型無關的代碼。比如編寫了一個交換兩個整型int 類型的swap函數,這個函數就只能實現int 型,對double,字符這些類型無法實現,要實現這些類型的交換就要重新編寫另一個swap函數。使用模板的目的就是要讓這程序的實現與類型無關,比如一個swap模板函數,即可以實現int 型,又可以實現double型的交換。

模板函數

普通定義形式

template
int func(const T &a1, const T &a2)
{
    ...
}

template 
inline int func(const T &a1, const T&a2)
{
    ...
}

tempalte
T1 func(const T2 &t2, const T3 &t3)
{
    ...
}

//調用方法
func(i, log);

如果類型的定義順序與調用順序不一樣的話, 則需要在申明的時候制定類型順序;

tempalte
T3 func(const T1 &t1, T2 &t2)
{
    ...
}
//調用方法
func(12, 34);

模板類

//定義方式
template  
class Queue
{
    ...
    ...
}

//使用方法
Queue qi;
模板形參表不能為空; 由編譯器根據實參列表實例化模板, 模板在實例化的時候會對參數類型進行語法檢查; 作用域由聲明之後直到模板聲明或定義的末尾處使用; 模板形參的名字只能在同一模板形參表中使用一次; 聲明和定義的模板形參名稱可以不一樣; 模板類型形參可以作為類型說明符用在模板中任何地方,與內置類型說明符或類類型說明符的使用方式完全相同; 對模板的非類型形參而言, 求值結果相同的表達式將認為是等價的,調用相同的實例; 編寫模板代碼時,對實參類型的要求盡可能少;-

非類型模板形參

template  
void func(T (&parm)[N])
{
    //此時,N將直接用值代替
}

關於模板編譯

發現錯誤一般分為三個階段:
1. 編譯模板定義本身,可以檢測模板本身的語法問題,例如漏掉分號,拼寫錯誤等;
2. 編譯器見到模板的使用時,檢測參數個數、類型是否合法;
3. 模板實例化期間,檢測類型相關的錯誤;

模板實例化

類模板在引用實例類模板類類型時實例化,函數模板在調用它或者用它對函數指針進程初始化或者賦值時實例化,在使用函數模板時,編譯器通常會為我們推斷模板實參;
當編譯器看到模板定義時,不立即產生代碼, 只有在看到模板調用時,編譯器才會產生對應的實例,類型相關的錯誤才會被檢查出來。

模板編譯模型

通常情況下,實例化一個對象或者調用一個函數時,編譯器不需要看到函數或者類的定義,只有在連接的時候才會去關心類或者函數的定義。但是模板不一樣, 編譯器在實例化模板時,必須看到模板的定義才會編譯通過。

包含編譯模型

//header file
#ifndef xx_H_
#define XX_H_

template 
int func(T &t1, T&t2);

#include "oo.cpp" //模板定義文件
#endif

//oo.cpp
template
int func(T &t1, T&t2)
{
    ...
}

類模板成員函數

必須以關鍵字template開頭, 後接類的模板形參表; 必須指出它是哪個類的成員; 類名必須包含其模板形參; 類模板的成員函數本身也是模板函數,像任何其他函數模板一樣,需要使用類模板的成員函數產生改成員的 實例化,也就是說只有在使用的時候才會被實例化; 類模板的形參定義在實例化類的時候確定了,所以調用的時候用於進行參數的常規類型轉換;
template  ret-type Queue::member_func_name
{
    //define 
}

非類型形參的模板實參

非類型模板實參必須是編譯時常量表達式
template 
class Screen
{
public:
    Screen():{}

private:
    std::string screen;
    std::string::size_type cursor;
    std::string::size_type height, width;
}

//實例化方法,參數必須是編譯時常量表達式
Screen<24, 80> hp2621;

類模板中友元聲明

普通友元,將由原關系授予制定的類或函數
template  
class Bar
{
    friend class FooBar;
    ...
}

FooBar 的成員可以訪問Bar類任意實例的private &protected 成員。

一般模板友元關系
template 
class Bar
{
    template  friend class Fool;
    template  friend void templ_fcnt(const T&);
    ...
}

表示Fool和templ_fcnt的任意實例都可以訪問Bar的任意實例的private和protected成員。

特定模板友元關系

模板類只授權對特定友元實例的訪問權

template  class Foo2;
template 
class Bar 
{
    friend class Foo2;
    friend void templ_fcnt(char *const &);
}

更通用的形式

template  class Foo2;
template  void templ_fcnt(const T&);
template 
class Bar 
{
    friend class Foo2;
    friend void templ_fcnt(const Type &);
    ...

}

這樣每個類型的類模板實例與對應的類型友元建立了一一映射關系。
- 聲明依賴性

如果模板類授權給所有友元實例訪問private和protected成員時, 編譯器將友元聲明當做類或者函數的聲明對待;但是如果指定到特定類型時,必須在前面聲明類或者函數。參考上面特定模板友元關系一般友元關系 的聲明。
同時,如果沒有提前告訴編譯器該友元是一個模板,編譯器則認為友元是一個普通非模板函數或者非模板類。

模板類的成員模板

這個名字確實有點繞, 其本質意思就是模板類的成員函數也希望有自己的參數類型,看如下例子:

template 
class Queue
{
public:
    template 
    Queue(It begin, It end):
        head(0), tail(0)
        {
            copy_elems(beg, end);
        }

    template 
    void assign(Iter , Iter);
private:
    template 
    void copy_elems(Iter, Iter);
}

在類模板的外部定義模板成員,必須包含類模板的形參和模板成員的模板形參:

template  //類模板的形參
tmeplate   //成員模板形參
void Queue::assign(Iter begin, Iter end)
{
    ...
}

與其他成員一樣,成員模板也只有在被使用的時候才會實例化。

類模板的static成員

template 
class Bar
{
public:
    static std::size_t count(){return ctr};

private:
    static std::size_t ctr;
}

實例化原則是:相同類型的實例共享一個static成員,例如Bar 類型的實例共享一個static 成員ctr,Bar 類型的實例共享一個static成員ctr;
- 使用方法

Bar bar1, bar2;
size_t ct = Bar::count();
初始化方法
template 
size_t Foo::ctr = 0;

模板特化

由於模板的定義中,其操作都是依賴實例化的類型是否支持該操作或者操作的結果與預期是否相匹配,例如:

template 
int compare(const Type& t1, const Type &t2)
{
    if(t1 > t2) return 1;

    if(t1 < t2> return -1;

    return 0;
}

在上面的例子中,如果用char* 去實例化模板時,函數將比較兩個指針,很明顯與預期的記過不相吻合。此時可以通過模板特例話來解決。

函數模板特化

函數模板特例化形式如下:
- 關鍵字template 後面接一對空的尖括號(<>);
- 在接末班嗎和一堆尖括號,尖括號中制定這個特化定義的模板形參;
- 函數形參表;
- 函數體。

例如:

template <>
int compare(const char *t1, const char *t2)
{
    return strcmp(t1, t2);
}

如果有多個模板形參,則依次排列即可。

類模板特化

定義類特化
template <>
class Queue
{
    ...
}

需要在類特化的外部定義普通成員函數時,成員之前不能加 template<>標記:

void Queue::push(const char* val)
{
    ...
}
特化成員而不特化類
template <>
void Queue::push(const char* const &val)
{
    ...
}

template<>
void Queue::pop()
{
    ...
}

現在,類類型Queue

template 
class tem
{
    ...
};

//partial specialization :fixes T2 as int and allows T2 to vary.
template 
class tem
{

}

使用方法:

tem foo; //調用普通的類模板
tem bar; //調用偏例化版本

重載與函數模板

函數模板可以重載:可以定義有相同名字但形參數據或類型不同的多個函數模板, 也可以定義與函數模板有相同名字的普通非模板函數。

不過從實踐來看,設計既包含函數模板又包含非模板函數的重載函數集合是困難的,因為坑你會使函數的用戶感覺到奇怪,定義函數模板特化幾乎總是比使用非模板版本更好。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved