类函数与函数模板

类函数与函数模板

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

目录

[TOC]

泛型程序设计(Generic Programming)

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

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

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

    • 函数模板

    • 模板

函数模板

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

两种可能的解法

函数重载

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

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

函数模板

1
template<class T>

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

范式

单类型参数

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

多个类型参数

1
2
3
4
5
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
    <!--code3-->
    
相关延伸

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

  • 数组退化:退化为指向数组首元素的指针,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),不是指针
      <!--code4-->
      
1
2
3
4
5
6
7
8
void foo(int a, int b) { }

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

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

类模板

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

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

概念

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

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

范式

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 参数数组
数组类模板实例

类模板声明

1
2
3
4
5
6
7
8
9
10
11
12
//类模板的首部,声明类模板的参数
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);
};

构造函数与析构函数实现

1
2
3
4
5
6
7
8
9
10
11
12
//模板类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;
}

定义类模板的成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//模板类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);
}
函数模板作为类模板成员

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}
非类型参数

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

  • 用来说明属性,常用于定义模板内常量例如数组大小
  • 类型参数则用来说明属性类型例如成员函数参数类型和返回值
1
2
3
4
5
6
7
8
9
10
11
12
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成员变量

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

继承与派生

1
2
3
4
5
6
7
8
9
10
11
12
13
flowchart LR
A[类模板]
B[模板类]
C[普通类]
A1[类模板raw]

A-->|实例化|B
B-->|派生|A
A1-->|派生|A
C-->|派生|A
B-->|派生|C


1. 类模板派生类模板
  • 核心逻辑:父类是类模板,子类也保持模板特性,继承时必须传递父类的模板参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 父类:类模板
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. 模板类派生类模板
  • 核心逻辑:父类是已经实例化的模板类(类型固定),子类可以是新的类模板。
1
2
3
4
5
6
7
8
9
// 父类:模板类(已实例化,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. 普通类派生类模板
  • 核心逻辑:父类是普通类,子类是类模板,继承时父类的类型需匹配子类模板参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 父类:普通类
class Base {
public:
int id = 10;
};

// 子类:类模板,继承普通类
template <class T>
class Derived : public Base {
public:
T value;
void setValue(T v) { value = v; }
};
4. 模板类派生普通类
  • 核心逻辑:父类是模板类(类型固定),子类是普通类,直接继承固定的模板类实例。
1
2
3
4
5
6
7
8
9
10
11
12
// 父类:模板类(已实例化)
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

1
2
3
4
5
6
7
8
9
10
//普通类静态成员初始化
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. 核心语法与示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 通用函数模板(默认实现,适用于所有类型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<>标识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 通用类模板(默认实现)
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. 类模板部分特化(仅指定部分/约束模板参数)

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

  • 通用模板定义:
1
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> | 两个参数必须是完全相同的类型 |

  • 全特化(完全指定所有参数):

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

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

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