泛型编程——template
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
函数模板
用swap函数对比函数重载和模板泛型编程:
函数重载解决了类似函数不能同名的缺陷,但我们依然要手动实现类似的函数体(cv)。
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
template<typename T1>
void swap(T1 x, T1 y)
{
T1 temp = x;
x = y;
y = temp;
}
再如:
template<class T1, class T2>
double Add(T1 x, T2 y)
{
return x + y;
}
类模板
引入:C语言实现数据结构时,我们将顺序表存储的数据类型用宏替换为SLDateType,如果想用顺序表存储多种数据类型,就需要cv出不同的类。
// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
//这里的class T就是内部存储的数据类型
class Vector
{
public :
Vector(size_t capacity = 10)
: _pData(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
void PushBack(const T& data);
void PopBack();
// ...
size_t Size() {return _size;}
T& operator[](size_t pos)
{
assert(pos < _size);
return _pData[pos];
}
private:
T* _pData;
size_t _size;
size_t _capacity;
};
显式实例化
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;
非类型模板参数
非类型模板参数,顾名思义,是指模板参数不仅限于类型,还可以是普通的值。在基于类型的模板中,模板实例化时所依赖的是某一类型的模板参数。但对于非类型模板参数,它面对的是一个固定类型的常量值,而不是一个类型。让我来详细解释一下。
- 非类型模板参数:这些参数用一个常量作为类(或函数)模板的一个参数,在模板中可以将该参数当作常量来使用。例如,我们要实现一个静态数组的类,就需要用到非类型模板参数。以下是一个示例:
template<class T, size_t N>
class StaticArray {
public:
size_tarraysize(){
return N;
}
private:
T _array[N]; // 利用非类型模板参数指定静态数组的大小
};
int main(){
StaticArray<int, 10> a1; // 定义一个大小为10的静态数组
cout << a1.arraysize() << endl; // 输出:10
StaticArray<int, 100> a2; // 定义一个大小为100的静态数组
cout << a2.arraysize() << endl; // 输出:100return0;
}
在上述示例中,我们使用非类型模板参数 N
来指定静态数组的大小。
注意,非类型模板参数只允许使用整型家族,不允许使用浮点数、类对象或字符串作为非类型模板参数。此外,非类型的模板参数在编译期需要确认结果,因为编译器在编译阶段需要根据传入的非类型模板参数生成对应的类或函数。
函数模板特化
函数模板的特化是一种在特定类型上定制函数模板的过程。让我们详细看看这个步骤:
- 基础函数模板:首先,你需要有一个通用的函数模板。这个模板可以接受不同类型的参数,但它还没有被特定类型的参数实例化。
- 关键字template和尖括号:接下来,在你的函数定义之前,使用关键字
template
,然后跟随一对空的尖括号<>
。这表示你要开始特化函数模板。 - 特化的类型:在函数名后面,再加上一对尖括号,尖括号中指定你想要特化的具体类型。例如,如果你想特化一个接受
int
类型参数的函数,你可以这样写:
template <>
void myFunction<int>(int param) {
// 特化的代码
}
这里,myFunction
是你的基础函数模板,<int>
表示你要特化的类型是int
。
函数模板特化允许你根据特定类型的需求来定制函数的实现。这在处理不同数据类型的算法时非常有用,例如在处理不同大小的整数、浮点数或自定义类型时。
类模板的全特化和偏特化
- 类模板全特化:全特化即将模板类型里的所有类型参数全部具体指明之后处理。例如,你可以针对特定的类型对类模板进行全特化,以满足特定需求。全特化的类模板会针对所有类型参数提供具体的实现。
- 类模板偏特化:
- 类模板偏特化允许你在特定条件下对类模板进行特化。
- 偏特化的语法类似于全特化,但是只对部分类型参数进行特化。
- 你可以根据类型参数的某些属性(例如指针类型、引用类型、const限定等)来定义偏特化版本。
举个例子,假设你有一个类模板 A
,它有两个类型参数 T
和 C
:
template<typename T, typename C>
struct A {
A() { cout << "泛化版本构造函数" << endl; }
voidfunc(){
cout << "泛化版本" << endl;
}
};
类模板全特化:现在,你可以对这个类模板进行全特化,针对特定的类型 int
和 int
template<>
struct A<int, int> {
A() { cout << "int, int特化版本构造函数" << endl; }
};
类模板偏特化:
1、你也可以只针对类模板其中的一部分类型参数,我们称之为部分特化
template<typename T>
struct A<T, char> {
A() { cout << "T, char偏特化版本构造函数" << endl; }
};
2、限制类模板传入参数的类型(指针类型、引用类型、const类型等),我们称之为对参数的特定限制
template<typename T>
struct A<T*,T*> {
A() { cout << "指针偏特化版本构造函数" << endl; }
};