1.模板的基础内容
C++ 的模板是实现泛型编程的核心机制,它允许我们编写独立于具体数据类型的代码,从而实现代码复用并保证类型安全。
模板分为函数模板和类模板:
1.1函数模板
函数模板用于创建通用函数,可操作多种数据类型,而无需为每种类型重复编写函数。
cpp
template <typename T> // 声明模板参数T(T为类型占位符,typename可替换为class)
返回值类型 函数名(参数列表) {
// 函数体(使用T作为类型)
}
模板的实例化:
隐式实例化:编译器根据传入的参数自动推断模板类型
cpp
#include <iostream>
// 函数模板:交换两个同类型变量的值
template <typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
swap(x, y); // 隐式实例化:编译器自动推断T为int
std::cout << "x=" << x << ", y=" << y << "\n"; // 输出x=20, y=10
double a = 3.14, b = 6.28;
swap(a, b); // 隐式实例化:T为double
std::cout << "a=" << a << ", b=" << b << "\n"; // 输出a=6.28, b=3.14
return 0;
}
显示实例化:手动指定模板类型,格式为函数名<类型>(参数)
cpp
swap<int>(x, y); // 显式指定T为int
1.2类模板
类模板用于创建通用类(如容器类),支持多种数据类型的成员变量和成员函数。
cpp
template <typename T> // 声明模板参数
class 类名 {
public:
// 成员变量(可使用T作为类型)
// 成员函数(可使用T作为类型)
};
实例化类模板时,必须显式指定类型(编译器无法自动推断)。
2.模板的进阶
2.1需要加typename的情况
cpp
#include<iostream>
#include<vector>
#include<list>
using namespace std;
template<class Container>
void Print(const Container& v)
{
// 编译不确定Container::const_iterator是类型还是容器内的静态对象变量
// typename就是明确告诉编译器这里是类型,等模板实例化再去找
typename Container::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main()
{
vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
Print(v);
list<int>li;
li.push_back(1);
li.push_back(2);
li.push_back(3);
Print(li);
return 0;
}
2.2非类型模板参数
模板参数分类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类**(函数)模板的一个参数,在类(函数)**模板中可将该参数当成常量来使用。
cpp
namespace my
{
// 定义一个模板类型的静态数组,N就是一个非类型模板参数,用来确定静态数组的大小
template<class T, size_t N = 10>
class array
{
public:
T& operator[](size_t index){return _array[index];}
const T& operator[](size_t index)const{return _array[index];}
size_t size()const{return _size;}
bool empty()const{return 0 == _size;}
private:
T _array[N];
size_t _size;
};
}

C++11中新增的容器array中,后面的那一个参数也是和上述代码一样作为非类型模板参数。
cpp
#include<iostream>
#include<array>
using namespace std;
int main()
{
array<int, 10>a;
for (int i = 0; i < 10; i++)
{
a[i] = i;
}
for (auto e : a)
{
cout << e << " ";
}
cout << endl;
return 0;
}
注意:
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
3.非类型的模板参数是常量,必须是整型。
2.3模板的特化
当需要为特定类型提供自定义实现时,可以使用模板特化(即 "特殊化" 某个类型的模板)。
2.3.1全特化
全特化即是将模板参数列表中所有的参数都确定化。
cpp
template<class T1, class T2>
class Data
{
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
template<>
class Data<int, char>
{
public:
Data() {cout<<"Data<int, char>" <<endl;}
private:
int _d1;
char _d2;
};
void TestVector()
{
Data<int, int> d1;
Data<int, char> d2;
}
2.3.2偏特化
偏特化有两种表现形式:
1.将模板参数类表中的一部分参数特化。
cpp
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
2.参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
cpp
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
2.3.3示例
cpp
#include<vector>
#include <algorithm>
template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
// 对Less类模板按照指针方式特化 全特化
template<>
struct Less<Date*>
{
bool operator()(Date* x, Date* y) const
{
return *x < *y;
}
};
//或者 进行偏特化
template<class T>
struct Less<T*>
{
bool operator()(T* x, T* y) const
{
return *x < *y;
}
};
int main()
{
Date d1(2022, 7, 7);
Date d2(2022, 7, 6);
Date d3(2022, 7, 8);
vector<Date> v1;
v1.push_back(d1);
v1.push_back(d2);
v1.push_back(d3);
sort(v1.begin(), v1.end(), Less<Date>());
vector<Date*> v2;
v2.push_back(&d1);
v2.push_back(&d2);
v2.push_back(&d3);
//如果上面不进行特化,则按指针比较,结果将是错误的
sort(v2.begin(), v2.end(), Less<Date*>());
return 0;
}
3.模板分离编译
模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义,则会发生链接错误。
cpp
#include<deque>
//stack.h
namespace my
{
template<class T, class Container = std::deque<T>>
class stack
{
public:
void push(const T& x);
void pop();
T& top()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
}
//stack.cpp
#include"Stack.h"
namespace my
{
template<class T, class Container>
void stack<T, Container>::push(const T& x)
{
_con.push_back(x);
}
template<class T, class Container>
void stack<T, Container>::pop()
{
_con.pop_back();
}
}
//mian函数所在文件
int main()
{
my::stack<int> st; // call xxstackxx(0x324242)
st.push(1); // call xxpushi(?)
st.pop(); // call xxpopi(?)
st.size(); // call xxsizexx(0xdadada)
return 0;
}


预处理,编译,汇编是没有问题,链接时会出现问题,链接时找到了函数的定义,但是函数没有实例化,也就是说不知道模板参数是什么类型,无法生成具体的函数,链接就会发生错误。
解决方法:
1.将声明和定义放到一个文件
cpp
#include<deque>
//stack.h
namespace my
{
template<class T, class Container = std::deque<T>>
class stack
{
public:
void push(const T& x);
void pop();
T& top()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
template<class T, class Container>
void stack<T, Container>::push(const T& x)
{
_con.push_back(x);
}
template<class T, class Container>
void stack<T, Container>::pop()
{
_con.pop_back();
}
}
2.模板定义的位置显式实例化
cpp
//stack.cpp
#include"Stack.h"
namespace my
{
template<class T, class Container>
void stack<T, Container>::push(const T& x)
{
_con.push_back(x);
}
template<class T, class Container>
void stack<T, Container>::pop()
{
_con.pop_back();
}
//需要实例化多少类型,写多少
template
class stack<int>;
template
class stack<double>;
}