文章

C++ 泛型编程 函数模板

C++ 泛型编程 函数模板

如果要编写一个函数, 用于交换两个变量的值, 传统的方法是使用指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void funcSwap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}
void funcSwap(double& a, double& b) {
    double temp = a;
    a = b;
    b = temp;
}
void funcSwap(char& a, char& b) {
    char temp = a;
    a = b;
    b = temp;
}
......

这种方法的缺点是, 每次交换不同类型的变量时, 都需要重新编写一个函数, 代码冗余, 且不易维护

函数模板可以解决这个问题, 通过函数模板, 可以编写一个通用的函数, 用于交换任意类型的变量

基本概念

函数模板就是通用的函数描述, 使用任意类型(泛型)来描述函数

编译的时候, 编译器推导实参的数据类型, 根据实参的数据类型和函数模板, 生成该类型的函数定义

生成函数定义的过程叫做函数模板的实例化

实际应用

创建交换两个变量的函数模板:

C++98添加了关键字typename, 用于声明函数模板:

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
void funcSwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}
int main() {
    int a = 10, b = 20;
    funcSwap(a, b); // 根据参数类型生成函数定义: funcSwap(int &a, int &b)
    cout << "a=" << a << ", b=" << b << endl;
}

如果声明了函数模板之后, 需要在代码中手动指定模板参数类型, 也可以使用<>来指定:

1
2
3
4
5
int main() {
    double a = 10.5, b = 20.5;
    funcSwap<double>(a, b); // 根据手动指定的参数类型生成函数定义: funcSwap(double &a, double &b)
    cout << "a=" << a << ", b=" << b << endl;
}

这里如果传入的参数是不同类型的变量(如int类型), 编译器会报错, 因为函数模板的参数类型已经被手动指定了, 无法推导出实参的类型

注意事项

  1. 定义函数模板时, 必须使用template关键字声明, 并在template后面使用<typename T>声明模板参数, 每次定义函数模板时, 都需要声明模板参数
1
2
3
4
5
6
7
8
9
10
11
12
template <typename T>
void funcA(T a) {
    cout << a << endl;
}
template <typename T>
void funcB(T a, T b) {
    cout << a+b << endl;
}
template <typename T>
void funcC(T a, T b, T c) {
    cout << a+b+c << endl;
}
  1. 可以为类的成员函数创建模板, 但不能是虚函数和析构函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test {
public:
    template <typename T>
    void func(T a) {
        cout << a << endl;
    }
    template <typename T>
    virtual void func(T a) { // 错误: 不能为虚函数创建模板
        cout << a << endl;
    }
    template <typename T>
    ~Test() { // 错误: 不能为析构函数创建模板
        cout << "析构函数" << endl;
    }
};
  1. 使用函数模板时, 必须明确数据类型, 确保实参与模板参数能够匹配(类似函数重载)
1
2
3
4
5
6
7
8
9
10
11
template <typename T>
void funcSwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}
int main() {
    int a = 10;
    double b = 20.5;
    funcSwap(a, b); // 错误: 无法推导出模板参数类型, 模板参数不明确
}
  1. 使用函数模板时, 推导的数据类型必须适应函数模板中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <typename T>
T funcAdd(T a, T b) {
    return a + b;
}
class Test {};
int main() {
    int a = 10;
    int b = 20;
    cout << funcAdd(a, b) << endl; // 输出: 30

    string c = "hello";
    string d = "world";
    cout << funcAdd(e, f) << endl; // 输出: helloworld

    Test e;
    Test f;
    cout << funcAdd(g, h) << endl; // 错误: 无法推导出模板参数类型, 模板参数不明确

}
  1. 使用函数模板时, 如果是自动类型推导, 不会发生隐式类型转换, 如果显示指定了函数模板的参数类型, 则会发生隐式类型转换
1
2
3
4
5
6
7
8
9
10
template <typename T>
T funcAdd(T a, T b) {
    return a + b;
}
int main() {
    int a = 10;
    double b = 20.5;
    cout << funcAdd(a, b) << endl; // 错误: 无法推导出模板参数类型, 模板参数不明确
    cout << funcAdd<int>(a, b) << endl; // 输出: 30
}
  1. 函数模板支持多个通用数据类型的参数
1
2
3
4
5
6
7
8
9
10
11
12
template <typename T1, typename T2>
void funcSwap(T1 &a, T2 &b) {
    T1 temp = a;
    a = b;
    b = temp;
}
int main(){
    int a = 10;
    double b = 20.5;
    funcSwap(a, b); // 根据参数类型生成函数定义: funcSwap(int &a, double &b)
    cout << "a=" << a << ", b=" << b << endl;
}
  1. 函数模板支持重载, 可以有非通用数据类型的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
void func(T a) {
    cout << a << endl;
}
template <typename T1, typename T2>
void func(T1 a, T2 b) {
    cout << a << " " << b << endl;
}
template <typename T1, typename T2>
void func(T1 a, T2 b, int c) {
    cout << a << " " << b << " " << c << endl;
}
int main() {
    func(10); // 输出: 10
    func(10, 20.5); // 输出: 10 20.5
    func(10, 20.5, 30); // 输出: 10 20.5 30
}

函数模板的具体化

为了满足特殊需求, 可以提供一个具体化的函数定义, 当编译器找到与函数调用匹配的具体化定义时, 将使用该定义, 不在寻找通用的函数模板

这种方法叫做函数模板的具体化, 也叫特化特例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Student {
public:
    string name;
    int id;
    int rank;
};
template <typename T>
void funcSwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}
template <> void funcSwap<Student>(Student &a, Student &b) {
// template <> void funcSwap(Student &a, Student &b) { // 也可以省略模板参数
    int temp = a.id;
    a.id = b.id;
    b.id = temp;
}
int main() {
    int a - 10, b = 20;
    funcSwap(a, b); // 根据参数类型生成函数定义: funcSwap(int &a, int &b)
    cout << "a=" << a << ", b=" << b << endl;
    Student s1, s2;
    s1.id = 1;
    s2.id = 2;
    funcSwap(s1, s2); // 根据参数类型生成函数定义: funcSwap(Student &a, Student &b)
    cout << "s1.id=" << s1.id << ", s2.id=" << s2.id << endl;
}

优先级

  1. 具体化优先级高于常规模板, 普通函数优先于具体化常规模板
  2. 如果希望使用常规模板, 但是传入的参数类型与具体化匹配, 可以用空模板参数强制使用常规模板(空模板参数: <>)
  3. 如果函数模板能产生更好的匹配, 则将优先于非模板函数
本文由作者按照 CC BY 4.0 进行授权