跳转至

11 Template

说明

本文档仅涉及部分内容,仅可用于复习重点知识

模板(Template)是一种通用编程工具,用于编写与类型无关的代码。模板允许开发者编写可以处理多种数据类型的函数或类,而无需为每种类型重复编写代码。C++ 支持两种主要的模板:函数模板类模板

模板的核心思想是将类型参数化。通过模板,类型可以作为参数传递给函数或类,从而实现代码的复用和泛型编程(generic programming)

模板的语法:

1
2
3
template <typename T>
// 或
template <class T>
  1. typenameclass 在模板中是等价的,表示类型参数
  2. T 是类型参数的占位符,可以是任何合法的标识符

模板的注意事项

  1. 模板的实例化:

    1. 模板代码只有在使用时才会被实例化为具体的类型
    2. 编译器会为每种使用的类型生成对应的代码
  2. 模板的代码膨胀:如果模板被用于多种类型,可能会导致代码膨胀(生成大量重复代码)

  3. 模板的定义与实现:模板的定义和实现通常放在同一个头文件中,因为模板的实例化需要在编译时完成
  4. 模板的调试:模板代码的错误信息可能较难理解,尤其是涉及复杂的模板嵌套时

模板类的实现文件分离问题

在实际开发中,模板类的声明和定义通常放在同一个头文件中,而不是将定义放在 .cpp 文件中。这是因为模板的实例化需要在编译时完成,而模板的定义必须在使用时可见

解决方法:

  1. 将模板类的声明和定义都放在头文件中
  2. 或者,将模板类的定义放在一个 .hpp 文件中,并在头文件中包含它
#ifndef MYCLASS_H
#define MYCLASS_H

#include <iostream>
using namespace std;

template <typename T>
class MyClass {
private:
    T value;
public:
    MyClass(T v);
    void display() const;
};

#include "MyClass.hpp" // 包含实现文件
#endif
1
2
3
4
5
6
7
template <typename T>
MyClass<T>::MyClass(T v) : value(v) {}

template <typename T>
void MyClass<T>::display() const {
    cout << "Value: " << value << endl;
}
1
2
3
4
5
6
7
#include "MyClass.h"

int main() {
    MyClass<int> obj(42);
    obj.display(); // 输出: Value: 42
    return 0;
}

1 Function Template

函数模板用于定义可以处理多种类型的函数。编译器会根据调用时的参数类型生成具体的函数

#include <iostream>
using namespace std;

template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    cout << add(3, 5) << endl;       // 输出: 8 (int 类型)
    cout << add(2.5, 3.5) << endl;  // 输出: 6.0 (double 类型)
    return 0;
}

如果需要为特定类型提供不同的实现,可以使用 模板特化

#include <iostream>
using namespace std;

template <typename T>
T add(T a, T b) {
    return a + b;
}

// 针对 const char* 类型的特化
template <>
const char* add(const char* a, const char* b) {
    return "String concatenation not supported";
}

int main() {
    cout << add(3, 5) << endl;       // 输出: 8
    cout << add("Hello", "World") << endl; // 输出: String concatenation not supported
    return 0;
}

2 Class Template

类模板用于定义可以处理多种类型的类。编译器会根据使用时的类型生成具体的类

#include <iostream>
using namespace std;

template <typename T>
class Box {
private:
    T value;
public:
    Box(T v) : value(v) {}
    T getValue() const { return value; }
};

int main() {
    Box<int> intBox(42);       // 使用 int 类型
    Box<double> doubleBox(3.14); // 使用 double 类型

    cout << intBox.getValue() << endl;    // 输出: 42
    cout << doubleBox.getValue() << endl; // 输出: 3.14
    return 0;
}

与函数模板类似,类模板也可以为特定类型提供特化实现

#include <iostream>
using namespace std;

template <typename T>
class Box {
private:
    T value;
public:
    Box(T v) : value(v) {}
    T getValue() const { return value; }
};

// 针对 const char* 类型的特化
template <>
class Box<const char*> {
private:
    const char* value;
public:
    Box(const char* v) : value(v) {}
    const char* getValue() const { return value; }
};

int main() {
    Box<int> intBox(42);
    Box<const char*> strBox("Hello");

    cout << intBox.getValue() << endl;    // 输出: 42
    cout << strBox.getValue() << endl;   // 输出: Hello
    return 0;
}

如果类模板的成员函数的声明在类内,而定义在类外,需要在定义时使用模板的完整语法,包括 template 关键字和模板参数列表。此外,还需要在类名后加上模板参数列表来表明这是一个模板类的成员函数

#include <iostream>
using namespace std;

// 声明类模板
template <typename T1, typename T2>
class MyPair {
private:
    T1 first;
    T2 second;
public:
    MyPair(T1 f, T2 s); // 构造函数声明
    void display() const; // 成员函数声明
};

// 定义构造函数
template <typename T1, typename T2>
MyPair<T1, T2>::MyPair(T1 f, T2 s) : first(f), second(s) {}

// 定义成员函数
template <typename T1, typename T2>
void MyPair<T1, T2>::display() const {
    cout << "First: " << first << ", Second: " << second << endl;
}

int main() {
    MyPair<int, double> pair(42, 3.14);
    pair.display(); // 输出: First: 42, Second: 3.14

    MyPair<string, int> pair2("Age", 25);
    pair2.display(); // 输出: First: Age, Second: 25

    return 0;
}
  1. 在类外定义成员函数时,必须再次使用 template <typename T> 来表明这是一个模板
  2. 在类名后使用 MyClass<T>,其中 <T> 表示模板参数

3 模板的高级用法

3.1 非类型模板参数

模板参数不仅可以是类型,还可以是常量(如整数、指针等)

#include <iostream>
using namespace std;

template <typename T, int size>
class Array {
private:
    T arr[size];
public:
    void set(int index, T value) {
        if (index >= 0 && index < size) {
            arr[index] = value;
        }
    }
    T get(int index) const {
        return arr[index];
    }
};

int main() {
    Array<int, 5> intArray;
    intArray.set(0, 42);
    cout << intArray.get(0) << endl; // 输出: 42
    return 0;
}

3.2 模板的默认参数

模板参数可以有默认值

#include <iostream>
using namespace std;

template <typename T = int>
class Box {
private:
    T value;
public:
    Box(T v) : value(v) {}
    T getValue() const { return value; }
};

int main() {
    Box<> intBox(42); // 使用默认类型 int
    Box<double> doubleBox(3.14);

    cout << intBox.getValue() << endl;    // 输出: 42
    cout << doubleBox.getValue() << endl; // 输出: 3.14
    return 0;
}

3.3 模板的嵌套

模板可以嵌套使用,例如类模板中包含另一个模板

#include <iostream>
#include <vector>
using namespace std;

template <typename T>
class Container {
private:
    vector<T> elements;
public:
    void add(T element) {
        elements.push_back(element);
    }
    void display() const {
        for (const auto& elem : elements) {
            cout << elem << " ";
        }
        cout << endl;
    }
};

int main() {
    Container<int> intContainer;
    intContainer.add(1);
    intContainer.add(2);
    intContainer.add(3);
    intContainer.display(); // 输出: 1 2 3
    return 0;
}

4 Template and Inheritance

4.1 模板类作为基类

模板类可以作为基类,派生类可以是普通类或模板类

普通类继承模板类
#include <iostream>
using namespace std;

// 定义模板基类
template <typename T>
class Base {
protected:
    T value;
public:
    Base(T v) : value(v) {}
    void display() const {
        cout << "Base value: " << value << endl;
    }
};

// 定义普通派生类
class Derived : public Base<int> {
public:
    Derived(int v) : Base<int>(v) {}
    void show() const {
        // 派生类 Derived 可以直接使用基类的成员
        cout << "Derived value: " << value << endl;
    }
};

int main() {
    Derived d(42);
    d.display(); // 调用基类方法
    d.show();    // 调用派生类方法
    return 0;
}
模板类继承模板类
#include <iostream>
using namespace std;

// 定义模板基类
template <typename T>
class Base {
protected:
    T value;
public:
    Base(T v) : value(v) {}
    void display() const {
        cout << "Base value: " << value << endl;
    }
};

// 定义模板派生类
template <typename T>
class Derived : public Base<T> {
public:
    Derived(T v) : Base<T>(v) {}
    void show() const {
        // 在派生类中,必须通过 Base<T>:: 或 this-> 来访问基类的成员
        cout << "Derived value: " << this->value << endl;
    }
};

int main() {
    Derived<int> d(42);
    d.display(); // 调用基类方法
    d.show();    // 调用派生类方法
    return 0;
}

4.2 模板类作为派生类

模板类可以继承普通类或模板类

模板类继承普通类
#include <iostream>
using namespace std;

// 定义普通基类
class Base {
protected:
    int value;
public:
    Base(int v) : value(v) {}
    void display() const {
        cout << "Base value: " << value << endl;
    }
};

// 定义模板派生类
template <typename T>
class Derived : public Base {
private:
    T extraValue;
public:
    Derived(int v, T ev) : Base(v), extraValue(ev) {}
    void show() const {
        cout << "Base value: " << value << ", Extra value: " << extraValue << endl;
    }
};

int main() {
    Derived<double> d(42, 3.14);
    d.display(); // 调用基类方法
    d.show();    // 调用派生类方法
    return 0;
}

5 Template and Friend

5.1 类模板的普通友元(非模板友元)

如果友元是一个 非模板函数或类,可以直接在类模板中声明它为友元。此时,该友元可以访问类模板 所有实例化版本 的私有成员

普通友元函数
#include <iostream>

// 类模板
template <typename T>
class Box {
private:
    T content;
public:
    Box(T val) : content(val) {}

    // 声明普通友元函数(非模板)
    friend void printBoxContent(const Box<T>& box);
};

// 普通友元函数的定义(必须针对每个类型单独实例化)
void printBoxContent(const Box<int>& box) {
    std::cout << "Box<int>: " << box.content << std::endl;
}

void printBoxContent(const Box<double>& box) {
    std::cout << "Box<double>: " << box.content << std::endl;
}

int main() {
    Box<int> intBox(42);
    printBoxContent(intBox); // 输出: Box<int>: 42

    Box<double> doubleBox(3.14);
    printBoxContent(doubleBox); // 输出: Box<double>: 3.14

    // Box<std::string> stringBox("hello");
    // printBoxContent(stringBox); // 错误!没有定义 Box<std::string> 的友元函数
}
普通友元类
#include <iostream>

// 前置声明
class BoxFriend;

// 类模板
template <typename T>
class Box {
private:
    T content;
public:
    Box(T val) : content(val) {}

    // 声明普通友元类(非模板)
    friend class BoxFriend;
};

// 普通友元类(非模板)
class BoxFriend {
public:
    void peek(const Box<int>& box) {
        std::cout << "Accessing Box<int>: " << box.content << std::endl;
    }

    void peek(const Box<double>& box) {
        std::cout << "Accessing Box<double>: " << box.content << std::endl;
    }
};

int main() {
    Box<int> intBox(42);
    BoxFriend friendObj;
    friendObj.peek(intBox); // 输出: Accessing Box<int>: 42

    Box<double> doubleBox(3.14);
    friendObj.peek(doubleBox); // 输出: Accessing Box<double>: 3.14
}

5.2 类模板的模板友元

如果友元本身是一个 函数模板或类模板,需要在友元声明中指定模板参数

5.2.1 一对一友元(绑定到相同模板参数)

友元模板的参数与类模板的参数 一一对应

一对一绑定:友元模板的每个实例化版本仅与类模板的 对应实例化版本 成为友元。例如,Friend<Int> 只能访问 MyClass<Int> 的私有成员,不能访问 MyClass<Double>

一对一友元函数
#include <iostream>

// 前置声明
template <typename T>
class MyClass;

// 声明友元函数模板(一对一绑定)
template <typename T>
void printData(const MyClass<T>& obj);

// 类模板
template <typename T>
class MyClass {
private:
    T data;
public:
    MyClass(T val) : data(val) {}

    // 一对一友元函数声明
    friend void printData<T>(const MyClass<T>& obj);
};

// 定义友元函数模板
template <typename T>
void printData(const MyClass<T>& obj) {
    std::cout << "Data: " << obj.data << std::endl; // 可访问私有成员
}

int main() {
    MyClass<int> a(42);
    printData<int>(a); // 合法:printData<int> 是 MyClass<int> 的友元

    MyClass<double> b(3.14);
    printData<double>(b); // 合法:printData<double> 是 MyClass<double> 的友元

    // printData<int>(b); // 错误!printData<int> 不能访问 MyClass<double>
}
一对一友元类
#include <iostream>

// 前置声明
template <typename T>
class MyClass;

// 声明友元类模板(一对一绑定)
template <typename T>
class Friend;

// 类模板
template <typename T>
class MyClass {
private:
    T data;
public:
    MyClass(T val) : data(val) {}

    // 一对一友元类声明
    friend class Friend<T>;
};

// 定义友元类模板
template <typename T>
class Friend {
public:
    void peek(const MyClass<T>& obj) {
        std::cout << "Peek: " << obj.data << std::endl; // 可访问私有成员
    }
};

int main() {
    MyClass<int> a(42);
    Friend<int> f1;
    f1.peek(a); // 合法:Friend<int> 是 MyClass<int> 的友元

    MyClass<double> b(3.14);
    Friend<double> f2;
    f2.peek(b); // 合法:Friend<double> 是 MyClass<double> 的友元

    // Friend<int> f3;
    // f3.peek(b); // 错误!Friend<int> 不能访问 MyClass<double>
}

5.2.2 通用友元(友元模板独立于类模板)

友元模板的参数与类模板无关,可以访问类模板 所有实例化版本 的私有成员

一对多访问:友元模板的任一实例化版本可以访问类模板 所有实例化版本 的私有成员。例如,Friend<Int> 既能访问 MyClass<Int>,也能访问 MyClass<Double>

通用友元函数
#include <iostream>

// 前置声明
template <typename T>
class Box;

// 声明通用友元函数模板
template <typename U>
void peekBox(const Box<U>& box);

// 类模板
template <typename T>
class Box {
private:
    T content;
public:
    Box(T val) : content(val) {}

    // 声明通用友元函数(所有实例化版本均可访问)
    template <typename U>
    friend void peekBox(const Box<U>& box);
};

// 定义通用友元函数
template <typename U>
void peekBox(const Box<U>& box) {
    std::cout << "Box<" << typeid(U).name() << ">: " << box.content << std::endl;
}

int main() {
    Box<int> intBox(42);
    Box<double> doubleBox(3.14);
    Box<std::string> stringBox("Hello");

    peekBox(intBox);     // 输出: Box<int>: 42
    peekBox(doubleBox);  // 输出: Box<double>: 3.14
    peekBox(stringBox);  // 输出: Box<string>: Hello
}
通用友元类
#include <iostream>

// 前置声明
template <typename T>
class Box;

// 声明通用友元类模板
template <typename U>
class BoxInspector;

// 类模板
template <typename T>
class Box {
private:
    T content;
public:
    Box(T val) : content(val) {}

    // 声明通用友元类(所有实例化版本均可访问)
    template <typename U>
    friend class BoxInspector;
};

// 定义通用友元类
template <typename U>
class BoxInspector {
public:
    void inspect(const Box<U>& box) {
        std::cout << "Box<" << typeid(U).name() << ">: " << box.content << std::endl;
    }

    // 甚至可以访问其他类型的 Box
    void inspectOther(const Box<int>& box) {
        std::cout << "Accessing Box<int>: " << box.content << std::endl;
    }
};

int main() {
    Box<int> intBox(42);
    Box<double> doubleBox(3.14);

    BoxInspector<int> inspector1;
    inspector1.inspect(intBox);      // 输出: Box<int>: 42
    inspector1.inspectOther(intBox); // 输出: Accessing Box<int>: 42

    BoxInspector<double> inspector2;
    inspector2.inspect(doubleBox);   // 输出: Box<double>: 3.14
    inspector2.inspectOther(intBox); // 输出: Accessing Box<int>: 42
}

6 Template and Static

6.1 静态成员变量

类模板中的 static 成员变量有以下特点:

  1. 每个模板实例化都会有自己的 static 成员变量副本
  2. 需要在类外进行定义(初始化)
  3. 不同模板参数实例化的类拥有不同的 static 成员
通用友元类
template <typename T>
class MyClass {
public:
    static int count;  // 声明static成员变量
};

// 定义并初始化static成员变量
template <typename T>
int MyClass<T>::count = 0;

// 使用
MyClass<int>::count++;      // MyClass<int>的count
MyClass<double>::count++;   // MyClass<double>的count (与上面的不同)

6.2 静态成员函数

类模板中的 static 成员函数有以下特点:

  1. 不依赖于任何特定对象实例
  2. 可以直接通过类名调用
  3. 只能访问 static 成员变量和其他 static 成员函数
通用友元类
template <typename T>
class MyClass {
public:
    static void printInfo() {
        std::cout << "This is a static member function." << std::endl;
    }
};

// 使用
MyClass<int>::printInfo();
MyClass<double>::printInfo();

评论区

欢迎在评论区指出文档错误,为文档提供宝贵意见,或写下你的疑问