【C++模板与泛型编程】模板定义

目录

一、函数模板的定义与使用

1.1 基本概念

1.2 模板参数推导

1.3 显式指定模板参数

1.4 函数模板示例:通用交换函数

二、类模板的定义与使用

2.1 基本概念

2.2 实例化类模板

2.3 类模板的成员函数

2.4 类模板示例:动态数组

三、模板形参详解

3.1 模板类型形参

3.2 非类型模板形参

3.3 模板模板形参

3.4 模板参数默认值

四、重载操作符与模板结合

4.1 操作符重载基础

4.2 模板与操作符重载结合

4.3 友元函数与模板

五、类型转换与模板

5.1 隐式类型转换

5.2 类型转换操作符

六、编写泛型程序的最佳实践

6.1 遵循最小特权原则

6.2 使用概念(Concepts)约束模板

6.3 避免模板代码膨胀

七、实战案例:实现一个通用矩阵类

八、总结


在 C++ 编程中,模板是实现泛型编程的核心机制。通过模板,我们可以创建通用的函数、类和算法,而不必预先指定具体的数据类型。通过模板,我们可以编写更加灵活、复用性更高的代码,极大地提升开发效率。

一、函数模板的定义与使用

1.1 基本概念

函数模板允许我们定义一个通用的函数,其参数类型和返回值类型可以是泛型的。语法格式如下:

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

其中,typename 关键字也可以用 class 替代,二者在模板定义中没有区别。 template <typename T> 声明了一个模板参数 T,它可以是任何类型。函数 max 可以接受两个类型为 T 的参数,并返回类型为 T 的结果。

1.2 模板参数推导

当调用函数模板时,编译器会根据传入的实参自动推导模板参数的类型:

int a = 5, b = 10;
int result = max(a, b);  // 自动推导 T 为 int

double x = 3.14, y = 2.71;
double d_result = max(x, y);  // 自动推导 T 为 double

1.3 显式指定模板参数

在某些情况下,编译器无法自动推导模板参数,此时需要显式指定: 

// 显式指定 T 为 double
auto mixed_result = max<double>(a, x);  // int 和 double 的混合比较

1.4 函数模板示例:通用交换函数

下面是一个通用的交换函数模板,它可以交换任意类型的两个变量:

template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

// 使用示例
int main() {
    int x = 5, y = 10;
    swap(x, y);  // 交换两个 int

    double a = 3.14, b = 2.71;
    swap(a, b);  // 交换两个 double

    std::string s1 = "hello", s2 = "world";
    swap(s1, s2);  // 交换两个 string

    return 0;
}

二、类模板的定义与使用

2.1 基本概念

类模板允许我们定义一个通用的类,其中的成员变量和成员函数可以使用泛型类型。语法格式如下:

template <typename T>
class Container {
private:
    T value;
public:
    Container(T val) : value(val) {}
    T getValue() const { return value; }
    void setValue(T val) { value = val; }
};

2.2 实例化类模板

使用类模板时,必须显式指定模板参数的类型: 

Container<int> intContainer(42);  // T 被实例化为 int
Container<double> doubleContainer(3.14);  // T 被实例化为 double

2.3 类模板的成员函数

类模板的成员函数可以在类内部定义,也可以在类外部定义。在类外部定义时,需要使用完整的模板声明: 

template <typename T>
class Container {
private:
    T value;
public:
    Container(T val);
    T getValue() const;
    void setValue(T val);
};

// 类外定义构造函数
template <typename T>
Container<T>::Container(T val) : value(val) {}

// 类外定义成员函数
template <typename T>
T Container<T>::getValue() const {
    return value;
}

template <typename T>
void Container<T>::setValue(T val) {
    value = val;
}

2.4 类模板示例:动态数组

下面是一个简单的动态数组类模板实现:

template <typename T>
class DynamicArray {
private:
    T* data;
    size_t size;
    size_t capacity;

public:
    DynamicArray() : data(nullptr), size(0), capacity(0) {}
    ~DynamicArray() { delete[] data; }

    void push_back(const T& value) {
        if (size >= capacity) {
            resize(capacity == 0 ? 1 : capacity * 2);
        }
        data[size++] = value;
    }

    T& operator[](size_t index) { return data[index]; }
    const T& operator[](size_t index) const { return data[index]; }

    size_t getSize() const { return size; }

private:
    void resize(size_t newCapacity) {
        T* newData = new T[newCapacity];
        for (size_t i = 0; i < size; ++i) {
            newData[i] = data[i];
        }
        delete[] data;
        data = newData;
        capacity = newCapacity;
    }
};

// 使用示例
int main() {
    DynamicArray<int> intArray;
    intArray.push_back(10);
    intArray.push_back(20);
    std::cout << "Array size: " << intArray.getSize() << std::endl;
    std::cout << "Element at index 0: " << intArray[0] << std::endl;

    DynamicArray<std::string> stringArray;
    stringArray.push_back("Hello");
    stringArray.push_back("World");
    std::cout << "Element at index 1: " << stringArray[1] << std::endl;

    return 0;
}

 

三、模板形参详解

3.1 模板类型形参

模板类型形参是最常见的模板参数,使用 typename 或 class 关键字声明:

template <typename T>  // typename 和 class 在这里等价
class MyClass {};

3.2 非类型模板形参

非类型模板形参允许我们传递一个常量值作为模板参数。常见的非类型参数包括整数、指针和引用:

template <int N>
class FixedArray {
private:
    int data[N];
public:
    int& operator[](int index) { return data[index]; }
    const int& operator[](int index) const { return data[index]; }
    int size() const { return N; }
};

// 使用示例
FixedArray<10> arr;  // 创建一个大小为 10 的数组

3.3 模板模板形参

模板模板形参允许将一个模板作为另一个模板的参数: 

template <template <typename> class Container, typename T>
class Wrapper {
private:
    Container<T> container;
public:
    void add(const T& value) { container.push_back(value); }
};

// 使用示例
#include <vector>
#include <list>

Wrapper<std::vector, int> vecWrapper;  // 使用 vector 作为容器
Wrapper<std::list, double> listWrapper;  // 使用 list 作为容器

3.4 模板参数默认值

模板参数可以有默认值,类似于函数参数的默认值: 

template <typename T = int, int N = 10>
class DefaultArray {
private:
    T data[N];
public:
    // ...
};

// 使用示例
DefaultArray<> defaultArray;  // 使用默认参数:int, 10
DefaultArray<double, 20> customArray;  // 自定义参数

四、重载操作符与模板结合

4.1 操作符重载基础

操作符重载允许我们为自定义类型重新定义操作符的行为。例如,为自定义类重载 + 操作符:

class Complex {
private:
    double real, imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // 重载 + 操作符
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }
};

4.2 模板与操作符重载结合

当操作符重载与模板结合时,可以为泛型类提供通用的操作符行为:

template <typename T>
class Pair {
private:
    T first, second;
public:
    Pair(const T& a, const T& b) : first(a), second(b) {}

    // 重载 + 操作符
    Pair operator+(const Pair& other) const {
        return Pair(first + other.first, second + other.second);
    }
};

4.3 友元函数与模板

友元函数可以与模板结合,实现更灵活的操作符重载: 

template <typename T>
class Point {
private:
    T x, y;
public:
    Point(T x = 0, T y = 0) : x(x), y(y) {}

    // 声明友元函数
    friend Point operator+(const Point& a, const Point& b) {
        return Point(a.x + b.x, a.y + b.y);
    }
};

五、类型转换与模板

5.1 隐式类型转换

模板类可以定义转换构造函数,实现从其他类型到模板类型的隐式转换:

template <typename T>
class SmartPtr {
private:
    T* ptr;
public:
    // 转换构造函数
    template <typename U>
    SmartPtr(U* p) : ptr(p) {}

    // ...
};

// 使用示例
class Base {};
class Derived : public Base {};

SmartPtr<Base> ptr(new Derived);  // 隐式转换

5.2 类型转换操作符

模板类也可以定义类型转换操作符,实现从模板类型到其他类型的转换: 

template <typename T>
class Number {
private:
    T value;
public:
    Number(T val) : value(val) {}

    // 类型转换操作符
    template <typename U>
    operator U() const {
        return static_cast<U>(value);
    }
};

// 使用示例
Number<int> num(42);
double d = num;  // 隐式转换为 double

六、编写泛型程序的最佳实践

6.1 遵循最小特权原则

模板应该只依赖于完成任务所需的最小接口,提高代码的通用性:

// 不好的设计:依赖具体类型的接口
template <typename T>
void printAll(const std::vector<T>& vec) {
    for (const auto& elem : vec) {
        elem.print();  // 依赖 T 有 print() 方法
    }
}

// 好的设计:只依赖必要的操作符
template <typename T>
void printAll(const std::vector<T>& vec) {
    for (const auto& elem : vec) {
        std::cout << elem << std::endl;  // 只依赖 << 操作符
    }
}

6.2 使用概念(Concepts)约束模板

C++20 引入的概念(Concepts)可以约束模板参数必须满足的条件:

// C++20 概念示例
template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;
};

template <Addable T>
T sum(T a, T b) {
    return a + b;
}

6.3 避免模板代码膨胀

过度使用模板可能导致代码膨胀,可以通过显式实例化和模板复用减少代码体积: 

// 显式实例化
template class std::vector<int>;  // 只在一处实例化 vector<int>

七、实战案例:实现一个通用矩阵类

下面是一个使用模板实现的通用矩阵类,展示了模板、操作符重载和类型转换的综合应用: 

#include <iostream>

template <typename T, size_t Rows, size_t Cols>
class Matrix {
private:
    T data[Rows][Cols];
    
public:
    // 默认构造函数
    Matrix() {
        for (size_t i = 0; i < Rows; ++i) {
            for (size_t j = 0; j < Cols; ++j) {
                data[i][j] = T();
            }
        }
    }
    
    // 元素访问
    T& operator()(size_t i, size_t j) {
        return data[i][j];
    }
    
    const T& operator()(size_t i, size_t j) const {
        return data[i][j];
    }
    
    // 矩阵加法
    template <typename U>
    Matrix<T, Rows, Cols> operator+(const Matrix<U, Rows, Cols>& other) const {
        Matrix<T, Rows, Cols> result;
        for (size_t i = 0; i < Rows; ++i) {
            for (size_t j = 0; j < Cols; ++j) {
                result(i, j) = data[i][j] + other(i, j);
            }
        }
        return result;
    }
    
    // 矩阵乘法(需要满足列数等于另一个矩阵的行数)
    template <size_t OtherCols>
    Matrix<T, Rows, OtherCols> operator*(const Matrix<T, Cols, OtherCols>& other) const {
        Matrix<T, Rows, OtherCols> result;
        for (size_t i = 0; i < Rows; ++i) {
            for (size_t j = 0; j < OtherCols; ++j) {
                for (size_t k = 0; k < Cols; ++k) {
                    result(i, j) += data[i][k] * other(k, j);
                }
            }
        }
        return result;
    }
    
    // 添加打印函数
    void print() const {
        for (size_t i = 0; i < Rows; ++i) {
            for (size_t j = 0; j < Cols; ++j) {
                std::cout << data[i][j] << "\t";
            }
            std::cout << std::endl;
        }
    }
};

使用示例:

int main() {
    Matrix<int, 2, 2> m1;
    m1(0, 0) = 1; m1(0, 1) = 2;
    m1(1, 0) = 3; m1(1, 1) = 4;
    
    Matrix<int, 2, 2> m2;
    m2(0, 0) = 5; m2(0, 1) = 6;
    m2(1, 0) = 7; m2(1, 1) = 8;
    
    std::cout << "Matrix m1:" << std::endl;
    m1.print();
    
    std::cout << "\nMatrix m2:" << std::endl;
    m2.print();
    
    Matrix<int, 2, 2> sum = m1 + m2;
    std::cout << "\nm1 + m2:" << std::endl;
    sum.print();
    
    Matrix<int, 2, 2> product = m1 * m2;
    std::cout << "\nm1 * m2:" << std::endl;
    product.print();
    
    return 0;
}

八、总结

模板是 C++ 中最强大的特性之一,它使得我们能够编写高度通用、灵活的代码。通过函数模板和类模板,我们可以创建不依赖于具体类型的算法和数据结构。模板参数可以是类型参数,也可以是非类型参数,甚至可以是模板本身。

重载操作符与模板结合,可以为泛型类提供直观的操作符语义,增强代码的可读性。类型转换机制则使得模板类能够与其他类型无缝协作。

在编写泛型程序时,我们应该遵循最小特权原则,使用 C++20 概念约束模板参数,并注意避免代码膨胀问题。通过合理使用模板、重载操作符和类型转换,我们可以创建出既通用又高效的 C++ 代码。

模板的实例化机制是连接模板定义与具体类型的桥梁,理解隐式实例化、显式实例化和特化的区别,对于编写高效、可维护的模板代码至关重要。随着 C++ 标准的不断发展,模板功能也在不断完善,如 C++20 引入的概念机制,使得模板更加易用和安全。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byte轻骑兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值