类函数与函数模板
完全匹配的普通函数 > 完全匹配的模板函数 > 需要转换的普通函数 > 需要转换的模板函数
泛型程序设计(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

模板类
定义
类模板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; }
};
友元



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

//普通类静态成员初始化
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<>标识部分特化支持 ❌ 不支持,用函数重载替代 ✅ 支持,仅适用于多参数模板 调用优先级 特化版本 > 通用模板 全特化 > 部分特化 > 通用模板 适用场景 为特定类型定制函数逻辑 为特定类型/参数约束定制类的完整实现
四、补充注意事项
- 函数模板特化在工程中优先用函数重载:重载的可读性、可维护性远高于特化,且符合C++的设计习惯。
- 类模板特化的常见用途:为指针类型优化内存操作、为特定类型(如
std::string)定制成员函数、为特化版本单独初始化静态成员。- 部分特化仅针对模板参数的约束,而非具体类型:例如
MyClass<T*, T*>是针对所有指针类型的特化,而非某一个具体指针(如int*)。- 特化必须在通用模板声明之后定义,否则编译器无法识别特化关系。