首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 泛型編程核心:模板函數、類模板、特化與編譯模型深度解析

     編輯:關於C++

泛型編程是 C++ 庫開發的基石,STL 容器、迭代器、算法皆建於此。本文系統梳理模板函數、類模板、非類型參數、模板特化、編譯模型等核心知識點,並釐清常見誤區。

前言:何為泛型編程

泛型編程旨在編寫與類型無關的代碼。以 swap 為例,無模板時需為 int、double、string 分別重寫;模板化後,一份代碼服務所有支持拷貝賦值的類型。

  • 模板:C++ 實現參數化多態的工具,對類型/值進行參數化
  • 兩種形式:函數模板(針對參數類型不同的函數)、類模板(針對數據成員/成員函數類型不同的類)

一、模板函數

基本語法

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

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

template <typename T1, typename T2, typename T3>
T1 func(const T2& t2, const T3& t3) { ... }

// 顯式指定模板實參
func<long>(i, log);

模板參數順序與調用順序不一致時

template <typename T1, typename T2, typename T3>
T3 func(const T1& t1, T2& t2) { ... }

// 調用時必須按聲明順序提供所有前置參數
func<long, int, long>(12, 34);

二、類模板

定義與使用

template <class Type>
class Queue { ... };

Queue<int> qi; // 顯式提供模板實參

關鍵規則

  • 模板形參表不能為空
  • 編譯器根據實參列表實例化,實例化時進行語法檢查
  • 作用域:聲明後至模板定義末尾
  • 模板形參名在同一形參表中只能出現一次
  • 聲明與定義的形參名可不同
  • 模板類型參數可作類型說明符用於模板內任何位置
  • 非類型參數:求值結果相同的表達式視為等價,調用同一實例
  • 編寫模板時,對實參類型要求盡可能少

三、非類型模板參數

template <class T, size_t N>
void func(T (&parm)[N]) { // N 直接用值替換
    // 可獲取數組長度 N
}

四、模板編譯三階段錯誤檢測

  1. 編譯模板定義:語法錯誤(漏分號、拼寫錯誤)
  2. 編譯器見到模板使用:實參個數、類型合法性
  3. 模板實例化期間:類型相關錯誤(如操作符不支持、成員不存在)

五、模板實例化機制

  • 類模板:引用實例類類型時實例化
  • 函數模板:調用、函數指針初始化/賦值時實例化
  • 編譯器通常自動推斷函數模板實參
  • 關鍵點:看到模板定義不產生代碼,只有見到調用才產生實例,類型錯誤才被檢出

六、模板編譯模型:為什麼定義要放頭文件

普通函數/類:聲明在頭文件,定義在源文件,鏈接時解析。

模板不同:編譯器實例化時必須看到定義。因此模板頭文件通常同時包含聲明與定義。

包含編譯模型

// header.h
#ifndef XX_H_
#define XX_H_
template <typename T>
int func(T& t1, T& t2);
#include "impl.cpp" // 直接包含定義文件
#endif

// impl.cpp
template <typename T>
int func(T& t1, T& t2) { ... }

七、類模板成員函數

  • 定義在類外必須以 template 開頭,接類模板形參表
  • 必須指明所屬類,類名含模板實參:Queue<T>::member_func
  • 類模板成員函數本身也是模板,只有被使用時才實例化
  • 類模板形參在實例化類時確定,調用時進行常規類型轉換
template <class T>
ret_type Queue<T>::member_func_name() { ... }

八、非類型參數的模板實參

必須是編譯時常量表達式:

template <int H, int W>
class Screen { ... };

Screen<24, 80> hp2621; // 正確
// Screen<h, w> hp; // 錯誤:非常量

九、類模板友元聲明

1. 普通友元(授予特定類/函數)

template <class Type>
class Bar {
    friend class FooBar; // FooBar 所有成員可訪問 Bar 任意實例私有成員
};

2. 一般模板友元(授予所有實例)

template <class Type>
class Bar {
    template <class T> friend class Fool;
    template <class T> friend void templ_fcnt(const T&);
};

3. 特定模板友元(授予特定實例)

template <class T> class Foo2;
template <class T> void templ_fcnt(const T&);

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

4. 更通用的一一映射形式

template <class T> class Foo2;
template <class T> void templ_fcnt(const T&);

template <class Type>
class Bar {
    friend class Foo2<Type>;
    friend void templ_fcnt<Type>(const Type&);
};

聲明依賴性:特定友元需前置聲明模板;一般友元編譯器視為聲明。

十、類模板的成員模板

成員函數擁有自己的模板參數:

template <class Type>
class Queue {
public:
    template <class It>
    Queue(It begin, It end) : head(0), tail(0) { copy_elems(begin, end); }
    
    template <class Iter>
    void assign(Iter, Iter);
private:
    template <class Iter>
    void copy_elems(Iter, Iter);
};

類外定義需包含兩層模板參數:

template <class Type>      // 類模板參數
template <class Iter>      // 成員模板參數
void Queue<Type>::assign(Iter begin, Iter end) { ... }

十一、類模板靜態成員

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

// 相同類型實例共享一個 static 成員
Bar<int> bar1, bar2;
size_t ct = Bar<int>::count();

// 初始化
template <class Type>
std::size_t Bar<Type>::ctr = 0;

十二、模板特化

當通用模板對特定類型行為不符預期時(如 char* 比較地址而非內容),需特化。

函數模板特化

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

類模板全特化

template <>
class Queue<const char*> { ... };

// 類外定義成員不加 template<>
void Queue<const char*>::push(const char* val) { ... }

類模板偏特化(部分特化)

// 原始模板
template <class T1, class T2>
class Tem { ... };

// 偏特化:固定 T2 為 int,T1 可變
template <class T1>
class Tem<T1, int> { ... };

// 使用
Tem<int, string> foo;  // 原始模板
Tem<string, int> bar; // 偏特化版本

偏特化更多形式

  • 指針類型:template <typename T1, typename T2> class Data<T1*, T2*>
  • 引用類型:template <typename T1, typename T2> class Data<T1&, T2&>

十三、重載與函數模板

  • 函數模板可重載(同名不同參數列表)
  • 可與非模板函數重載
  • 最佳實踐:優先用模板特化,避免非模板重載導致調用規則複雜難懂

十四、模板分離編譯問題與解決

模板分離編譯問題示意

解決方案:
1. 頭文件顯式實例化 template class SeqList<int>;(不推薦:老編譯器不支持、實例化依賴調用者)
2. 推薦:聲明與定義放入同一文件 .hpp 或 .h 中

總結

優點缺點
代碼復用、節省資源、加速迭代、催生 STL代碼複雜難維護、編譯時間長
增強靈活性、類型安全錯誤信息冗長難定位
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved