侧边栏壁纸
博主头像
LittleAO的学习小站 博主等级

在知识的沙漠寻找绿洲

  • 累计撰写 125 篇文章
  • 累计创建 27 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

C++之模板

LittleAO
2023-12-03 / 0 评论 / 0 点赞 / 27 阅读 / 0 字
温馨提示:
本文最后更新于2023-12-03,若内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

C++之模板

模板的概念

在将模板之前,我们需要先讲一讲泛型编程。所谓泛型编程就是一种编程范式,其目标就是使代码能够独立于任何特定类型。而C++的模板就是泛型编程的一个重要工具。

下面来举两个泛型编程的例子:

Python是一种动态类型语言,其变量类型在运行是才能确定。

def max_element(lst):
    return max(lst)

上面的函数支持传入很多种类型的变量,准确来说,支持传入任何类型的参数,这满足泛型的概念。当然,Python支持更明确的泛型编程类型,这不在本文的讨论范围之内。

将话题扯回到C++,C++是一种静态类型的编程语言,也就是说我们在创建变量的时候必须指定变量的类型。想要其支持泛化的特征,模板是其重要的工具。

第二个例子就是C++的STL库,准确来说使其数据类型。就拿最常用的vector举例,在声明vector类型的变量时,我们需要指定其包含的数据类型是什么,用尖括号来表示。

std::vector<int> sample;

上述的例子中就用 来表示了 sample存储的数据类型。这就是模板的一个例子,准确来说,这是类模板的一个例子,

类模板的定义看起来像这样:

template <class T>
class vector {
    // ...
};

了解了以上的信息后,相信你已经对模板有了一个大致的概念,总而言之:

模板是C++中泛型编程的基础,一个模板是创建一个类或函数的蓝图或者是公式。

函数模板

先从最简单的函数讲起。在C++中使用 template关键字来定义模板,一个函数模板可以这样定义:

template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

模板定义以关键字 template开始,后面跟一个模板参数列表,这是一个由逗号分隔的一个或多个模板参数的列表,用尖括号包围起来。

在C++中,关键字 typename用在模板声明中,用于指定一个模板类型的参数,这意味着在模板实例化时,这个参数会被任意类型替换。

在 C++ 模板中,typenameclass 关键字在大多数情况下可以互换使用,它们都用来声明类型参数。然而,有一种情况需要注意。当你在模板中使用嵌套类型时,你必须使用 typename 关键字,而不能使用 class 关键字。

上面说到模板参数列表是一个可以有多个模板参数的列表,下面是一个多模板参数列表的例子:

template <typename T, class U>
void printPair(T a, U b) {
    std::cout << a << ", " << b << std::endl;
}

上面的例子中,可以用两种不同的类型来实例化这个模板,例如:printPair(3, "hello")

在定义模板的时候,我们隐式或显式的制定了模板实参,例如上面例子中的 T

编译器处理模板时,会生成不同类型的函数版本(没有用到的类型不会编译),编译器生成的版本通常称为模板的实例

我们创建的函数模板,也可以返回我们定义的类型参数的数据,例如:

template <typename T>
T fun1(T p)
{
    T tmp;
    tmp = p;
    // ...
    return tmp;
}

非类型模板参数

我们可以通过其他关键字来定义非类型模板参数,非类型模板参数表示一个值而非一个类型。例如:

template <typename T, int size>
class Array {
    T data[size];
public:
    T& operator[](int index) {
        return data[index];
    }
};

上面是实现一个array类的例子,我们可以通过 Array<int, 10> arr;来声明一个对象。上面的例子用到了重载运算符,之后的文章会详细讲解。

非类型模板参数必须是编译时常量。这意味着你不能用一个运行时确定的值来实例化模板。例如下面这样:

int size = 10;
Array<int, size> arr;  // 错误:size 必须是一个编译时常量

小心编译错误

template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

这个例子中,传进的变量类型必须能使用 <进行比较才能得出正确的结果,但是如果该类型无法使用 <进行运算,那么将会在实例化这个函数的时候报错。

如何避免这个问题可以在下面的模板特化一部分找到答案。

类模板

类模板是C++中的一种特性,它允许程序员为类定义一种模式,使得类可以处理任何类型的数据。你可以把类模板看作是一种对类型进行参数化的方式。

下面是一个简单的类模板的例子:

template <typename T>
class Box {
private:
    T data;
public:
    Box(T data) : data(data) {}
    T getData() { return data; }
};

在这个例子中,Box 是一个类模板,T 是一个模板参数,它代表了一个类型。Box 类有一个成员变量 data,它的类型是 T。这意味着,你可以用任何类型来创建一个 Box 对象:

Box<int> intBox(10);
Box<std::string> stringBox("Hello, world!");

在这个例子中,Box<int> 是一个具体的类,它的 data 成员的类型是 int。类似地,Box<std::string> 是另一个具体的类,它的 data 成员的类型是 std::string

类模板可以有多个模板参数。例如,你可以定义一个可以存储两种类型数据的类模板:

template <typename T1, typename T2>
class Pair {
private:
    T1 first;
    T2 second;
public:
    Pair(T1 first, T2 second) : first(first), second(second) {}
    T1 getFirst() { return first; }
    T2 getSecond() { return second; }
};

在这个例子中,Pair 类有两个模板参数 T1T2,它有两个成员变量,其类型分别是 T1T2

类模板的一个重要应用是在标准模板库(STL)中。STL 提供了许多模板类,例如 std::vectorstd::liststd::map 等等。这些模板类可以用来存储和操作任何类型的数据。

类模板的一个主要优点是它提供了代码复用。你只需要写一次模板代码,就可以用它来处理任何类型的数据。这可以减少代码冗余,并提高代码的可维护性和可读性。

模板特化

函数模板特化

拿之前的例子:

template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return 1;
    return 0;
}

如果我们给函数传入 char*类型的参数时,可能会导致此函数出现问题。这时候我们就需要模板特化,特别的为 char*类型构建一个函数,有点类似于函数的重载。模板特化的规则是:先写 template <>,后面的函数实参要指明参数类型,例如:

template <>
int compare(const char *const &v1, const char *const &v2)
{
    return std::strcmp(v1, v2);
}

就如上面这个例子,把函数中的参数 T改成了 char *const。当函数匹配到对应的类型时,会自动执行相匹配的函数,例如

std::cout << compare(2, 1) << std::endl; // 执行通用模板
const char *cp1 = "apple", *cp2 = "banana";
std::cout << compare(cp1, cp2) << std::endl; // 执行特化模板

类模板特化

下面是一个类模板特化的例子:

首先,我们定义一个通用的类模板 Box,这个模板有一个 print 成员函数,它打印一条通用的消息:

template <typename T>
class Box {
public:
    void print() const {
        std::cout << "This is a generic box.\n";
    }
};

然后,我们可以为特定类型(如 int)提供一个特化版本。这个特化版本的 print 成员函数打印一条特定的消息:

template <>
class Box<int> {
public:
    void print() const {
        std::cout << "This is a box of integers.\n";
    }
};

这样,当你创建一个 Box<int> 对象并调用其 print 成员函数时,C++ 将会使用特化版本,而不是通用版本。

注意在使用类模板特化时,模板名后面要用尖括号来指定该模板参数的类型,这是因为函数特化可以推导(你要是愿意的话也可以加上尖括号),类模板无法进行推导。

0

评论区