加载中

类函数与函数模板

类函数与函数模板

完全匹配的普通函数 > 完全匹配的模板函数 > 需要转换的普通函数 > 需要转换的模板函数

泛型程序设计(Generic Programming)

算法实现时不指定具体要操作的数据的类型

  • 泛型: 算法实现一遍 → 适用于多种数据结构→减少重复代码的编写

  • 大量编写模板, 使用模板的程序设计

    • 函数模板

    • 模板

函数模板

问题根源:排序或者更广地说,算法完全相同,但被排序数组元素的变量的类型声明不同

两种可能的解法

函数重载

  • 同名函数
  • 编译系统根据参数调用时实参类型,确定实际执行的函数

问题在于:要是类有无数,要重载无数

函数模板

template<class T>

由编译系统根据sort函数调用时实参的类型,自动生成相应的模板函数

范式

单类型参数

template<class T>
void print(const T array[],int size){ //此处T为函数模板的类型参数
    int i;
    for(int i=0;i<size;i++) cout<<array[i];
    return;
}
// 要能编译通过当然要有对<<的适当重载

多个类型参数

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

函数模板的参数类型可以是

  • 类型参数
  • 基本数据类型,例如...void print(T1 arg1,T2 arg2,string s){...}

类型参数可以用于函数模板的局部变量声明(就是在函数模板里调用类型参数),也可以用来作为返回值

二义性:

  • 对于Function(T arg1,T arg2)Function(1,1.0)——error:replace T with int or double?

  • 采用Function(T1 arg1,T2 arg2)避免二义性

  • ⚠️显式指定**避免二义性

  • (⚠️如果可以自动匹配可以不指定)

  • int c=10;
    func(c); //自动匹配
    func<double>(c);  //注意:此处会发生隐式转换
    func<int>(1,1.0); //ok,统一转化为int
    
      
    - ⚠️函数模板可以重载(同一个函数名,**参数数量不同**)
    
    - ⚠️:函数与模板匹配的顺序:
    
      - 先找参数**完全**匹配的**函数**
    
      - 再找参数**完全**匹配的**模板**
    
      - 函数无二义性前提下,找可**转换后匹配**的**函数**(⚠️函数)
    
        ```c++
        #include <iostream>
        using namespace std;
                    
        template <class T>
        void Max( T a, T b ) {
            cout << "TemplateMax" <<endl;
            return;
        }
                    
        void Max(double a, double b){
            cout << "MyMax" << endl;
            return;
        }
                    
        int main(){
            int i=4, j=5;
            Max(1.2, 3.4);      //输出MyMax
            Max(i, j);          //输出TemplateMax
            Max(1.2, 3);        //模板有二义性,强制类型转换3为double,调用Max函数
            return 0;
        }
相关延伸

数组或者函数名作为值传入:

  • 数组退化:退化为指向数组首元素的指针,Type[N]退化为Type*

  • 函数退化:退化为指向函数的指针,Ret(Args)退化为Ret(*)(Args)

    • 函数指针Ret(*)(Args)

    • 函数类型Ret(Args),实际传递通常退化

    • 举个例子 void foo(int)变成指针 void(*)(int), 具体写的指针是void(*p)(int),⬅️这个可以写在函数参数表里

    • 一个细节:

      auto p1 = foo; // 函数到指针转换,p1 是 void ()(int)
      auto p2 = &foo; // 显式取地址,结果也是 void (
      )(int)

    • //推导函数不隐式转化为指针
      decltype(foo) x;                // ✅ x 的类型是 void(int),不是指针
      
      
       **实例化:**
      
       即:函数模版的类型参数**实际转化**为**什么参数类型**
      
      ```c++
      #include <iostream>
      #include <typeinfo>
      
      /**
      * * 1. 数组退化 (Array Decay): 
      * 当数组名作为值传递给模板时,T[N] 会退化为 T*。
      * * 2. 函数退化 (Function Decay): 
      * 当函数名作为值传递给模板时,void(int) 会退化为函数指针 void(*)(int)。
      * * 3. 模板匹配规则:
      * - 若形参是 T*,而实参是 int*,则 T 推导为 int。
      * - 若形参是 T,而实参是 int*,则 T 推导为 int*。
      */
      
      template <class T1, class T2, class T3>
      T1 func(T1 * a, T2 b, T3 c) {
         // 我们可以利用 C++ 内部机制打印推导出的类型名(取决于编译器输出)
         // std::cout << typeid(T1).name() << std::endl; 
         return T1(); 
      }
      
      // 全局变量与函数声明
      int a[10];       // 类型:int [10]
      int b[10];       // 类型:int [10]
      void f(int n) {} // 类型:void(int)
      
      int main() {
         /* * 调用过程:func(a, b, f);
          * * 参数 [1]: T1 * a  <-- 传入实参 a (int [10])
          * --------------------------------------------------
          * 逻辑:数组名 a 在这里退化为指向首元素的指针 int*。
          * 匹配:T1* = int* =>  T1 被实例化为 【int】。
          * * 参数 [2]: T2 b    <-- 传入实参 b (int [10])
          * --------------------------------------------------
          * 逻辑:数组名 b 同样退化为 int*。
          * 匹配:T2 直接接收这个指针  =>  T2 被实例化为 【int *】。
          * * 参数 [3]: T3 c    <-- 传入实参 f (函数名)
          * --------------------------------------------------
          * 逻辑:函数名 f 在非引用传递时,自动退化为函数指针类型。
          * 匹配:void(int) 变为 void(*)(int)。
          * 结果:T3 被实例化为 【void(*)(int)】。
          */
      
         func(a, b, f);
      
         return 0;
      }
      
      
      // T1: int,    T2: int *,    T3: void(*)(int)  
void foo(int a, int b) { }

void (*p)(int, int) = foo;

foo(1, 2);      // 直接调用
p(1, 2);        // 函数指针直接调用,编译器自动解引用
(*p)(1, 2);     // 显式解引用后调用
(****p)(1, 2);  // 甚至这样也行,多层解引用编译器自动归一化

类模板

目的:快速地定义出一批相似的类,例如数组类(包元素、基本操作)

特点:除了元素类型之外,其他完全相同

概念

  • 在定义类的时候给它一个/多个参数, 这个/些参数表示不同的数据类型

  • 在调用类模板时, 指定参数, 由编译系统根据参数提供的数据类型自动产生相应的模板类(类模板+指定具体数据类型)

范式

template <class T> //关键就这一行,类模板的首部, 声明类模板的参数
class CArray{
	T *ptrElement;
	int size;
public:
	CArray(int length);
	~CArray();
	int len();
	void setElement(T arg, int index);
	T getElement(int index);
};

More detail:

template <class T1, class T2>
class Pair {
public:
    T1 key;    //关键字
    T2 value;  //值
    Pair(T1 k, T2 v):key(k), value(v) { }
    bool operator < ( const Pair<T1, T2> & p) const;
};

template <class T1, class T2>
bool Pair<T1, T2>::operator < ( const Pair<T1, T2> & p) const
//Pair的成员函数 operator <
{    return key < p.key;    }

int main(){
    Pair<string, int> student("Tom", 19); //实例化出一个类
    cout << student.key << " " << student.value;
    return 0;
}

// 输出:Tom 19

image-20260404161620247

模板类

定义

类模板A+指定具体类型数据1→模板类A1→生成该模板类的对象或指针

类模板A+指定具体类型数据2→模板类A2

类模板A+指定具体类型数据n→模板类An

类模板B+指定具体类型数据1→模板类B1

数据类型可以是类

同一个类模板的两个模板类不兼容的(类名字相同,但是类型参数显然不同)(不能赋值、不能传指针/引用)

定义类模板的成员函数

小注:

  • arg→arguement 参数
  • argc→argument count 参数数量
  • argv→argument vector 参数数组
数组类模板实例

类模板声明

//类模板的首部,声明类模板的参数
template <class T>
class CArray{
    T *ptrElement;
    int size;
public:
    CArray(int length);
    ~CArray();
    int len();
    void setElement(T arg, int index);
    T getElement(int index);
};

构造函数与析构函数实现

//模板类CArray<T>的构造函数
template <class T>
CArray<T>:: CArray(int length) {
    ptrElement = new T[length];
    size = length;
}

//模板类CArray<T>的析构函数
template <class T>
CArray<T>:: ~ CArray(){
    delete [] ptrElement;
}

定义类模板的成员函数

//模板类CArray<T>的成员函数 len()
template <class T>
int CArray<T>:: len(){
    return size;
}

template <class T>
void CArray<T>::setElement(T arg, int index) {
    *(ptr+index) = arg;
    return;
}

template <class T>
T CArray<T>::getElement(int index)
    return *(ptrElement+index);
}
函数模板作为类模板成员

成员函数模板只有调用时才会实例化

template <class T>
class A{
public:
    template <class T2>
	void Func(T2 t){cout<<t;}
};

int main(){
    A<int> a;
    a.Func(‘K’);
    a.Func("Hello");
    return 0;
}
非类型参数

⚠️类模板的<类型参数表>可以包含非类型参数

  • 用来说明属性,常用于定义模板内常量例如数组大小
  • 类型参数则用来说明属性类型例如成员函数参数类型和返回值
template <class T,int size>
class CArray{
    T array[size];
public:
    void print(){
        for(int i=0;i<size;i++){
            cout<<array[i]<<endl;
        }
    }
};
CArray<double,40> a2;
CArray<int,50> a3; //编译后size直接替换

⚠️ 类中并没有条件添加一个int size成员变量

类模板参数声明中的非类型参数→编译链接即确定→可以提高程序执行效率

继承与派生

flowchart LR
    A[类模板]
    B[模板类]
    C[普通类]
    A1[类模板raw]
    
    A-->|实例化|B
    B-->|派生|A
    A1-->|派生|A
    C-->|派生|A
    B-->|派生|C
1. 类模板派生类模板
  • 核心逻辑:父类是类模板,子类也保持模板特性,继承时必须传递父类的模板参数。
// 父类:类模板
template <class T1,class T2>
class Base {
public:
    T1 num;
    T2 hh;
};

// 子类:类模板(继承类模板)
template <class T1,class T2>
class Derived : public Base<T2,T1> {
public:
    T1 getNum() { return num; }
};
//想清楚上面就迎刃而解
2. 模板类派生类模板
  • 核心逻辑:父类是已经实例化的模板类(类型固定),子类可以是新的类模板。
// 父类:模板类(已实例化,T=int固定)
class BaseInt : public Base<int> {};

// 子类:类模板,继承固定的模板类
template <class T>
class Derived : public BaseInt {
public:
    T add(T a, T b) { return a + b; }
};
3. 普通类派生类模板
  • 核心逻辑:父类是普通类,子类是类模板,继承时父类的类型需匹配子类模板参数。
// 父类:普通类
class Base {
public:
    int id = 10;
};

// 子类:类模板,继承普通类
template <class T>
class Derived : public Base {
public:
    T value;
    void setValue(T v) { value = v; }
};
4. 模板类派生普通类
  • 核心逻辑:父类是模板类(类型固定),子类是普通类,直接继承固定的模板类实例。
// 父类:模板类(已实例化)
template <class T>
class Base {
public:
    T data;
};

// 子类:普通类,继承固定的模板类(Base<string>)
class Derived : public Base<string> {
public:
    void printData() { cout << data; }
};

友元

54d2126467529235b4d85a77647cbda5

image-20260404182527844

image-20260404182442452

static成员

  • 类模板中可以定义静态成员, 那么从该类模板同一个实例化得到的模板类的所有对象, 都包含同样的静态成员

image-20260404182834272

//普通类静态成员初始化
int MyClass::count = 0;
//类模板静态成员初始化(默认值)
template<class T>
int A<T>::count = 0;
//为特定类型的类模板静态成员初始化
template<> //标准写法
int A<int>::count = 0;
template<>
int A<double>::count = 1;

模板特化(考试不要求)

一、函数模板的特化(考试不要求)
1. 核心语法与示例
// 通用函数模板(默认实现,适用于所有类型T)
template<class T>
void print(T x) { 
    cout << "Default Print: " << x << endl; 
}

// 对bool类型的全特化(为特定类型提供专属实现)
template<>
void print(bool x) { 
    cout << (x ? "Good" : "Bad"); 
}

int main() {
    print(5);        // 匹配通用模板,输出:Default Print: 5
    print("hello");  // 匹配通用模板,输出:Default Print: hello
    print(5 == 1);   // 匹配bool特化版本,输出:Bad
    return 0;
}
2. 关键说明
  • 全特化:用template<>标识,为完全确定的类型提供独立实现,调用优先级远高于通用模板。
  • 函数模板不支持部分特化:若需要针对某一类类型(如所有指针、所有容器)做定制,直接使用函数重载是更简洁、易维护的方案。
  • 补充:函数模板特化本质是为通用模板的特定实例提供专属实现,而非重载(重载是不同的函数签名)。

二、类模板的特化(考试不要求)

类模板特化分为全特化部分特化(偏特化),优先级规则:全特化 > 部分特化 > 通用模板

1. 类模板全特化(完全指定所有模板参数)

完全确定的模板参数提供专属实现,用template<>标识。

// 通用类模板(默认实现)
template<class T>
class Printer{
public:
    void print(T x) { 
        cout << "Default Print: " << x << endl; 
    }
};

// 对bool类型的全特化
template<>
class Printer<bool>{
public:
    void print(bool x) { 
        cout << (x ? "Good" : "Bad"); 
    }
};

// 使用示例
Printer<int> p; 
Printer<bool> p2;
p.print(5);      // 调用通用模板,输出:Default Print: 5
p2.print(5);     // 调用bool特化版本,输出:Good(5隐式转换为true)
2. 类模板部分特化(仅指定部分/约束模板参数)

仅对多参数类模板生效,通过约束部分参数生成更具体的模板版本,适用于通用模板无法覆盖的场景。

  • 通用模板定义:
template<class T1, class T2> 
class MyClass {};
  • 常见部分特化场景:

    特化类型 语法 说明
    固定第二个参数 template<class T> class MyClass<T, int> 第二个参数固定为int,第一个参数仍为泛型
    两个参数均为同类型指针 template<class T> class MyClass<T*, T*> 两个参数必须是同类型的指针
    两个参数类型完全相同 template<class T> class MyClass<T, T> 两个参数必须是完全相同的类型
  • 全特化(完全指定所有参数):

template<> 
class MyClass<int, double> {};

三、核心对比与总结
特性 函数模板 类模板
全特化支持 ✅ 支持,用template<>标识 ✅ 支持,用template<>标识
部分特化支持 ❌ 不支持,用函数重载替代 ✅ 支持,仅适用于多参数模板
调用优先级 特化版本 > 通用模板 全特化 > 部分特化 > 通用模板
适用场景 为特定类型定制函数逻辑 为特定类型/参数约束定制类的完整实现

四、补充注意事项
  1. 函数模板特化在工程中优先用函数重载:重载的可读性、可维护性远高于特化,且符合C++的设计习惯。
  2. 类模板特化的常见用途:为指针类型优化内存操作、为特定类型(如std::string)定制成员函数、为特化版本单独初始化静态成员。
  3. 部分特化仅针对模板参数的约束,而非具体类型:例如MyClass<T*, T*>是针对所有指针类型的特化,而非某一个具体指针(如int*)。
  4. 特化必须在通用模板声明之后定义,否则编译器无法识别特化关系。

本文作者:flowwalker之码艺Blog

本文链接:/coding-notes-blog/posts/d56d/

版权声明:本文采用 CC BY-NC-SA 4.0 许可协议