【C++ 】 如何去认识模板

引言:

C++模板是泛型编程的基石,允许程序员定义可与任何数据类型协作的函数和类。这种机制极大地增加了代码的灵活性和复用性,是C++最强大的特性之一。本文将深入探讨C++模板的概念、优势以及使用方法,帮助读者掌握这一重要的编程工具。


模板简介

在软件工程中,“不要重复自己”(DRY)原则鼓励代码的复用。C++模板正是实现此原则的一种强大工具,它使得程序员能够通过编写一套代码,就能够处理多种数据类型。模板可以应用于函数和类,分别称为函数模板和类模板。

模板的优势

使用模板,可以创建通用的算法和数据结构,无需关心具体的数据类型。这样不仅减少了代码的重复,也提高了代码的清晰度和可维护性。例如,下面的代码展示了一个简单的函数模板,用于交换两个变量的值:

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

这个函数模板可以应用于任何支持赋值操作的数据类型,无需为每种类型编写单独的交换函数。

1、模板基础

1.1 模板的概念

模板是一种对类型进行参数化的工具,通过模板可以使用不同的类型来实例化类或函数,从而达到代码复用的目的。C++提供了两种模板:函数模板和类模板。

1.2 函数模板

函数模板是一种特殊的函数,它使用模板类型参数来定义函数,从而使函数能够处理不同类型的数据。函数模板的一般形式如下:

template <typename T>
返回类型 函数名(参数列表) {
    // 函数体
}

其中,typename可以替换为class,它们在这里是等价的。T是一个类型参数,表示函数可以接受的类型。在函数体内,可以使用T来声明变量、参数,或者作为返回类型。

举一个简单的例子,下面的代码定义了一个函数模板,用于交换两个值:

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

这个函数模板可以用于交换任意类型的两个值,例如:

int i = 1, j = 2; 
swap(i, j);  // 交换两个int string s1 = "hello", s2 = "world"; swap(s1, s2);  // 交换两个string

1.3 类模板

类模板是一种特殊的类,它使用模板类型参数来定义类,从而使类能够处理不同类型的数据。类模板的一般形式如下:

template <typename T> class 类名 {    
	// 类的定义 
};

在类模板内,可以使用类型参数T来定义成员变量和成员函数。

举一个简单的例子,下面的代码定义了一个类模板Stack,用于表示一个栈:

template <typename T>
class Stack {
private:
    T* elements;
    int size;
    int capacity;
public:
    Stack(int cap = 10) : elements(new T[cap]), size(0), capacity(cap) {}
    ~Stack() { delete[] elements; }
    
    void push(const T& value) { 
        // 实现压栈操作
    }
    void pop() {
        // 实现出栈操作
    }
    const T& top() const {
        // 返回栈顶元素
    }
    bool empty() const { return size == 0; }
};

这个类模板可以用于创建任意类型的栈,例如:

Stack<int> intStack;  // 创建一个int型的栈 Stack<string> stringStack;  // 创建一个string型的栈

2、模板进阶

2.1 模板的实例化

当编译器遇到一个模板的使用时,它会根据提供的模板参数,自动生成一个特定类型的函数或类。这个过程称为模板的实例化。

对于函数模板,编译器会在调用点自动推导模板参数的类型。例如:

int a = 1, b = 2; 
swap(a, b);  // 编译器自动推导T为int

对于类模板,必须显式地指定模板参数的类型。例如:

Stack<int> intStack;  // 显式指定T为int

2.2 模板的特化

有时候,我们可能需要为某些特定的类型提供一个特殊的实现。这时,我们可以使用模板的特化。

例如,对于上面的Stack类模板,我们可以为bool类型提供一个特化版本:

template <> class Stack<bool> {    
    // 为bool类型提供特殊的实现 
};

2.3 模板的默认参数

我们可以为模板参数提供默认值,就像为函数参数提供默认值一样。例如:

template <typename T, int SIZE = 10> class Array {    
    T elements[SIZE];   
    // ... 
};

这样,在使用Array类模板时,如果不指定SIZE,则默认为10。

2.4 模板的嵌套

模板可以嵌套使用,即一个模板的定义中可以使用另一个模板。例如:

template <typename T>
class Node {
    T value;
    Node<T>* next;
    // ...
};

template <typename T>
class List {
    Node<T>* head;
    // ...
};

这里,List类模板中使用了Node类模板。

3、模板的特化

3.1 函数模板特化

使用模板对于一些特殊类型的可能得不到想要的结果,比如:

template <class T>
bool less(T l, T r) {
	return l < r;
}
int main()
{
    date d1(2020, 1, 2);
    date d2(2020, 3, 3);
    less(d1, d2); // 结果不确定
    return 0;
}

实例化为基本类型时能够完成任务,实例化为指针类型时,我们的需求并不是比较指针大小而是比较指针所指向的对象的大小。

此时该模板就不能起作用,需要特化该函数专门针对指针类型的比较函数。

template <class T>
bool less(T l, T r) {
	return l < r;
}

template <>
bool less<date*>(date* l, date* r) { //针对指针类型
	return *l < *r;
}

bool less(date* l, date* r) { 
	return *l < *r;
}

函数模板特化是从原来的函数模板特化出一个类型专有的版本,相当于针对一个类型的特殊解法。

函数模板特化不如直接重载,模板特化在函数处显得比较鸡肋。模板特化主要是用于类模板特化。

3.2 类模板特化

类模板特化和函数模板特化差不多,都是针对某些特殊类型进行特殊处理。

template<class T>
struct greater
{
    bool operator()(const T& x, const T& y)
    {
        return x > y;
    }
};

template<>
struct greater<date*>
{
    bool operator()(const date* p1, const date* p2)
    {
        return *p1 > *p2;
    }
};
全特化

全特化是模板参数列表中所有模板参数都进行特化。

template <class T1, class T2>
struct Data {
	Data() { cout << "Data<T1, T2>" << endl; }
	T1 _a;
	T2 _c;
};
template <>
struct Data<int, char> { // 特化处理int,char类型
	Data() { cout << "Data<int, char>" << endl; }
	int _a;
	char _c;
};
template<class T>
struct less
{
    bool operator()(const T l, const T r)
    {
        return l < r;
    }
};
template<>
struct less<date*> {
    bool operator()(const date* l, const date* r) {
        return l < r;
    }
};
less<date>()(date(2020, 1, 2), date(2020, 3, 3));
less<date*>()(new date(2020, 1, 2), new date(2020, 3, 3));
偏特化

偏特化是只限制部分参数为固定类型,其他仍是模板参数类型。

template <class T1>
class Data<T1, char> //第二个模板参数为char
{
public:
	Data() {
		cout << "Data<T1, char>" << endl;
	}
private:
	T1 _a;
	char _c;
};

模板参数的指针和引用也算偏特化

template<class T>
struct less {
    bool operator()(const T l, const T r) {
        return l < r;
    }
};
template<class T>
struct less<T*> {
    bool operator()(const T* l, const T* r) {
        return l < r;
    }

};

less<int*>()(new int(1), new int(2));
less<date>()(date(2020, 1, 2), date(2020, 3, 3));
less<date*>()(new date(2020, 1, 2), new date(2020, 3, 3));

非类型模板参数也支持特化。

template <size_t N>
struct A {
	A() { cout << "A<N>" << endl; }
}

template <>
struct A<10> {
	A() { cout << "A<10>" << endl; }
}

A<20> a1;
A<10> a2;

4. 模板分离编译

编译单元和链接过程

首先,让我们回顾一下编译和链接的过程。在C++中,一个编译单元通常由一个.cpp文件以及它所包含的所有.h文件组成。编译器将这些文件编译成目标文件(在Windows环境下通常是.obj文件),然后连接器将这些目标文件链接成一个可执行文件。

模板的实例化和分离式编译的挑战

模板的定义通常包含在头文件中,而模板的实例化需要在编译期间进行。这意味着每个使用模板的.cpp文件都需要能够访问到模板的实现。如果模板的实现被分离到不同的.cpp文件中编译,那么在链接阶段就无法正确地找到模板的实例化代码,从而导致链接错误。

举个例子,假设有一个包含模板类的头文件template.h和两个使用该模板的.cpp文件file1.cppfile2.cpp。如果模板的实现被分离到各自的.cpp文件中,那么在file1.cpp中实例化的模板代码将无法在链接阶段被file2.cpp正确访问到,导致链接错误。

解决方案:模板的定义和实现一起编译

为了解决这个问题,C++编译器需要将模板的定义和实现一起编译,并在每个使用了该模板的编译单元中实例化模板。这样做可以确保在链接时能够找到正确的模板实现,从而避免链接错误的发生。

模板声明定义分离,会出现链接错误:
在这里插入图片描述

模版在编译阶段会根据调用生成对应的函数,但定义模版的文件中没有函数调用,就不会生成函数

当然我们可以显式实例化,在定义模版的文件里显式实例化需要的版本。

template<class T>
void func(T& x)
{
    cout << "func(T& x)" << endl;
}
//显式实例化
template void func<int>(int& x);
template void func<double>(double& x);

请添加图片描述

总结来说,全特化适用于对所有模板参数进行具体化的情况,而偏特化则适用于对部分模板参数进行具体化的情况。这使得模板能够根据不同的情况进行更灵活和定制化的处理。

5、模板总结

优点

  • 模板复用代码,更快的开发,增强了代码的灵活性

缺点

  • 导致代码膨胀,导致编译时间变长
  • 编译报错信息非常凌乱,不易定位错误

C++模板是一个非常强大的工具,它提供了一种抽象和复用代码的方式,使得我们可以编写出高度泛型的代码。通过对模板的深入理解和灵活运用,我们可以大大提高代码的质量和效率。当然,模板也不是万能的。过度使用模板可能会导致代码复杂度的增加,编译时间的延长。因此,在使用模板时,我们要权衡其利弊,选择合适的应用场景。总的来说,C++模板是每一个C++程序员必须掌握的重要工具,希望本文能够帮助您更好地理解和运用模板。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SuhyOvO

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

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

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

打赏作者

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

抵扣说明:

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

余额充值