主頁 > 後端開發 > C++ 模板常見特性(函式模板、類模板)

C++ 模板常見特性(函式模板、類模板)

2020-09-18 11:24:04 後端開發

微信公眾號:「小林coding」
用簡潔的方式,分享編程小知識,

背景

C++ 是很強大,有各種特性來提高代碼的可重用性,有助于減少開發的代碼量和作業量,

C++ 提高代碼的可重用性主要有兩方面:

  • 繼承
  • 模板

繼承的特性我已在前面篇章寫過了,本篇主要是說明「模板」的特性,

使用「模板」的特性設計,實際上也就是「泛型」程式設計,

函式模板

01 變數交換函式模板

假設我們設計一個交換兩個整型變數的值的函式,代碼如下:

// 交換兩個整型變數的值的Swap函式:
void Swap(int & x,int & y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

如果是浮點型別的變數的值交換,則替換 int 型別為 double 即可,代碼如下:

// 交換兩個double型變數的值的Swap函式:
void Swap(double & x,double & y)
{
    double tmp = x;
    x = y;
    y = tmp;
}

那如果是其他變數型別的值交換,那不是每次都要重新寫一次 Swap 函式?是不是很繁瑣?且代碼后面會越來越冗余,

能否只寫一個 Swap 函式,就能交換各種型別的變數?

答案是肯定有的,就是用「函式模板」來解決,「函式模板」的形式:

template <class 型別引數1,class 型別引數2,...>
回傳值型別 模板名 (形參表)
{
    函式體
};

具體 Swap 「函式模板」代碼如下:

template 就是模板定義的關鍵詞,T 代表的是任意變數的型別,

template <class T>
void Swap(T & x,T & y)
{
    T tmp = x;
    x = y;
    y = tmp;
}

那么定義好「函式模板」后,在編譯的時候,編譯器會根據傳入 Swap 函式的引數變數型別,自動生成對應引數變數型別的 Swap 函式:

int main()
{
    int n = 1,m = 2;
    Swap(n,m); //編譯器自動生成 void Swap(int & ,int & )函式
    
    double f = 1.2,g = 2.3;
    Swap(f,g); //編譯器自動生成 void Swap(double & ,double & )函式
    
    return 0;
}

上面的實體化函式模板的例子,是讓編譯器自己來判斷傳入的變數型別,那么我們也可以自己指定函式模板的變數型別,具體代碼如下:

int main()
{
    int n = 1,m = 2;
    Swap<int>(n,m);     // 指定模板函式的變數型別為int
    
    double f = 1.2,g = 2.3;
    Swap<double>(f,g); // 指定模板函式的變數型別為double
    
    return 0;
}

02 查詢陣列最大值函式模板

在舉一個例子,下面的 MaxElement 函式定義成了函式模板,這樣不管是 int、double、char 等型別的陣列,都可以使用該函式來查陣列最大的值,代碼如下:

// 求陣列最大元素的MaxElement函式模板
template <class T>
T MaxElement(T a[], int size) // size是陣列元素個數
{
    T tmpMax = a[0];
    for(int i = 1;i < size;++i)
    {
        if(tmpMax < a[i])
        {
            tmpMax = a[i];
        }
    }
    return tmpMax;
}

03 多個型別引數模板函式

函式模板中,可以不止一個型別的引數:

template <class T1, class T2>
T2 MyFun(T1 arg1, T2 arg2)
{
    cout<< arg1 << " "<< arg2<<endl;
    return arg2;
}

T1 是傳入的第一種任意變數型別,T2 是傳入的第二種任意變數型別,


04 函式模板的多載

函式模板可以多載,只要它們的形參表或型別引數表不同即可,

// 模板函式 1
template<class T1, class T2>
void print(T1 arg1, T2 arg2) 
{
    cout<< arg1 << " "<< arg2<<endl;
}

// 模板函式 2
template<class T>
void print(T arg1, T arg2) 
{
    cout<< arg1 << " "<< arg2<<endl;
}

// 模板函式 3
template<class T,class T2>
void print(T arg1, T arg2) 
{
    cout<< arg1 << " "<< arg2<<endl;
}

上面都是 print(引數1, 引數2) 模板函式的多載,因為「形參表」或「型別引數表」名字不同,

05 函式模板和函式的次序

在有多個函式和函式模板名字相同的情況下,編譯器如下規則處理一條函式呼叫陳述句:

  1. 先找引數完全匹配的普通函式(非由模板實體化而得的函式);
  2. 再找引數完全匹配的模板函式;
  3. 再找實引數經過自動型別轉換后能夠匹配的普通函式;
  4. 上面的都找不到,則報錯,

代碼例子如下:

// 模板函式 - 1個引數型別
template <class T>
T Max(T a, T b) 
{
    cout << "TemplateMax" <<endl; return 0;
}

// 模板函式 - 2個引數型別
template <class T, class T2>
T Max(T a, T2 b) 
{
    cout << "TemplateMax2" <<endl; return 0;
}

// 普通函式
double Max(double a, double b)
{
    cout << "MyMax" << endl;
    return 0;
}

int main() 
{
    int i=4, j=5;
    
    // 輸出MyMax - 匹配普通函式
    Max( 1.2, 3.4 ); 
    
    //輸出TemplateMax - 匹配引數一樣的模板函
    Max( i, j );
    
    //輸出TemplateMax2 - 匹配引數型別不同的模板函式
    Max( 1.2, 3 );   
    
    return 0;
}

匹配模板函式時,當模板函式只有一個引數型別時,傳入了不同的引數型別,是不進行型別自動轉換,具體例子如下:

// 模板函式 - 1個引數型別
template<class T>
T myFunction( T arg1, T arg2)
{ 
    cout<<arg1<<" "<<arg2<<"\n"; 
    return arg1;
}

...

// OK :替換 T 為 int 型別
myFunction( 5, 7); 

// OK :替換 T 為 double 型別  
myFunction(5.8, 8.4);

// error :沒有匹配到myFunction(int, double)函式
myFunction(5, 8.4); 

類模板

01 類模板的定義

為了多快好省地定義出一批相似的類,可以定義「類模板」,然后由類模板生成不同的類

類模板的定義形式如下:

template <class 型別引數1,class 型別引數2,...> //型別引數表
class 類模板名
{
   成員函式和成員變數
};

用類模板定義物件的寫法:

類模板名<真實型別引數表> 物件名(建構式實參表);

02 Pair類模板例子

接下來,用 Pair 類用類模板的方式的實作,Pair 是一對的意思,也就是實作一個鍵值對(key-value)的關系的類,

// 類模板
template <class T1, class T2>
class Pair
{
public:
    Pair(T1 k, T2 v):m_key(k),m_value(v) {};
    bool operator < (const Pair<T1,T2> & p) const;
private:
    T1 m_key;
    T2 m_value;
};

// 類模板里成員函式的寫法
template <class T1, class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> &p) const
{
    return m_value < p.m_value;
}

int main()
{
    Pair<string,int> Astudent("Jay",20); 
    Pair<string,int> Bstudent("Tom",21);
    
    cout << (Astudent < Bstudent) << endl;
    
    return 0;
}

輸出結果:

1

需要注意的是,同一個類模板的兩個模板類是不兼容的:

Pair<string,int> *p;
Pair<string,double> a;
p = & a; //錯誤!!

03 函式模板作為類模板成員

當函式模板作為類模板的成員函式時,是可以單獨寫成函式模板的形式,成員函式模板在使用的時候,編譯器才會把函式模板根據傳入的函式引數進行實體化,例子如下:

// 類模板
template <class T>
class A
{
public:
    template<class T2>
    void Func(T2 t) { cout << t; } // 成員函式模板
};

int main()
{
    A<int> a;
    a.Func('K');     //成員函式模板 Func被實體化
    a.Func("hello"); //成員函式模板 Func再次被實體化

    return 0;
} 

04 類模板與非型別引數

類模板的“<型別引數表>”中可以出現非型別引數:

template <class T, int size>
class CArray
{
public:
    void Print( )
    {
        for( int i = 0;i < size; ++i)
        cout << array[i] << endl;
    }
private:
    T array[size];
};

CArray<double,40> a2;
CArray<int,50> a3; //a2和a3屬于不同的類

類模板與派生

01 類模板從類模板派生

上圖的代碼例子如下:

// 基類 - 類模板
template <class T1,class T2>
class A 
{
    T1 v1; T2 v2;
};

// 派生類 - 類模板
template <class T1,class T2>
class B:public A<T2,T1> 
{
    T1 v3; T2 v4;
};

// 派生類 - 類模板
template <class T>
class C:public B<T,T> 
{
    T v5;
};

int main() 
{
    B<int,double> obj1; 
    C<int> obj2;
    return 0;
}

02 類模板從模板類派生

上圖的代碼例子如下:

template <class T1,class T2>
class A 
{
    T1 v1; T2 v2;
};

template <class T>
class B:public A<int,double>  // A<int,double> 模板類
{
    T v;
};

int main() 
{
    //自動生成兩個模板類 :A<int,double> 和 B<char>
    B<char> obj1;
    return 0;
}

03 類模板從普通類派生

上圖的代碼例子如下:

// 基類 - 普通類
class A 
{
    int v1;
};

// 派生類 - 類模板
template <class T>
class B:public A  // 所有從B實體化得到的類 ,都以A為基類
{ 
    T v;
};

int main() 
{
    B<char> obj1;
    return 0;
}

04 普通類從模板類派生

上圖的代碼例子如下:

template <class T>
class A 
{
    T v1;
};

class B:public A<int> 
{
    double v;
};

int main() 
{
    B obj1;
    return 0;
}

類模板與友元

01 函式、類、類的成員函式作為類模板的友元

代碼例子如下:

// 普通函式
void Func1() { } 

// 普通類
class A { }; 

// 普通類
class B 
{
    public:
    void Func() { } // 成員函式
};

// 類模板
template <class T>
class Tmp
{
    friend void Func1();    // 友元函式
    friend class A;         // 友元類
    friend void B::Func();  // 友元類的成員函式
}; // 任何從 Tmp 實體化來的類 ,都有以上三個友元

02 函式模板作為類模板的友元

// 類模板
template <class T1,class T2>
class Pair
{
private:
    T1 key;   //關鍵字
    T2 value; //值
public:
    Pair(T1 k,T2 v):key(k),value(v) { };
    
    // 友元函式模板
    template <class T3,class T4>
    friend ostream & operator<< (ostream & o, const Pair<T3,T4> & p);
};

// 函式模板
template <class T3,class T4>
ostream & operator<< (ostream & o, const Pair<T3,T4> & p)
{
    o << "(" << p.key << "," << p.value << ")" ;
    return o;
}

int main()
{
    Pair<string,int> student("Tom",29);
    Pair<int,double> obj(12,3.14);
    
    cout << student << " " << obj;
    return 0;
}

輸出結果:

(Tom,29) (12,3.14)

03 函式模板作為類的友元

// 普通類
class A
{
private:
    int v;
public:
    A(int n):v(n) { }
    
    template <class T>
    friend void Print(const T & p); // 函式模板
};

// 函式模板
template <class T>
void Print(const T & p)
{
    cout << p.v;
}

int main() 
{
    A a(4);
    Print(a);
    return 0;
}

輸出結果:

4

04 類模板作為類模板的友元

// 類模板
template <class T>
class B 
{
private:
    T v;
public:
    B(T n):v(n) { }
    
    template <class T2>
    friend class A; // 友元類模板
};

// 類模板
template <class T>
class A 
{
public:
    void Func( )  
    {
        B<int> o(10); // 實體化B模板類
        cout << o.v << endl;
    }
};

int main()
{
    A<double> a;
    a.Func ();
    return 0;
}

輸出結果:

10

類模板與靜態成員變數

類模板中可以定義靜態成員,那么從該類模板實體化得到的所有類,都包含同樣的靜態成員,

template <class T>
class A
{
private:
    static int count; // 靜態成員
public:
    A() { count ++; }
    ~A() { count -- ; };
    A( A & ) { count ++ ; }
    
    static void PrintCount() { cout << count << endl; } // 靜態函式
};

template<> int A<int>::count = 0;    // 初始化
template<> int A<double>::count = 0; // 初始化

int main()
{
    A<int> ia;  
    A<double> da; // da和ia不是相同模板類
    ia.PrintCount();
    da.PrintCount();
    return 0;
}

輸出:

1
1

上面的代碼需要注意的點:

  • 類模板里的靜態成員初始化的時候,最前面要加template<>
  • ia 和 da 物件是不同的模板類,因為型別引數是不一致,所以也就是不同的模板類,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/73331.html

標籤:C++

上一篇:使用技巧 --- 與VS Code相關

下一篇:關于非旋轉Treap

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more