在C语言中,想要实现多种类型的变量交换,需要用户定义需要实现交换的类型的函数。
用户自定义一个栈的数据结构,只能用于一种类型的使用,如果想要在一个程序中定义多种类型的栈,C语言是办不到的。那么在C++中就引入了模板。
函数模板
概率:实现这个模板是并没有给出具体的类型,而是在调用的时候,由编译器根据实参类型去生成该类型的函数。
使用格式
template <typename T1, typename T2, typename T3......>
返回值类型 函数名 (参数列表)
{}
//实现一个交换函数的模板
template <typename T>
void swap(T& p1, T& p2)
{
T temp = p1;
p1 = p2;
p2 = temp;
}
// typename 也可以使用class替换,但是不能使用strutct。
函数模板的实例化
概念:当不同类型参数使用函数模板时,称为函数模板实例化。实例化分为:隐式实例化和显式实例化。
//一个与函数模板同名的非函数模板函数可以共存。
int add(int a, int b) // 一个专门处理int类型的加法函数
{
return a + b;
}
template <typename T>
T add(const T& a, const T& b)
{
return a + b;
}int main()
{
int a1 = 1, a2 = 2;
double b1 = 1.1, b2 = 2.2;
add(a1, a2);
add(b1, b2);
// add(a1, b1) 这条语句会报错。编译器会根据a1和b1的类型去推导T的类型,可是这两个变量的类型不一样,所以编译不通过。有两种处理方式:1、强转;2、显式实例化。
// 强转
add(a1, (int)b1); // add((double)a1, b1);
// 显式实例化
add<int>(a1, b1);
add(1, 2); // 调用专门处理int类型的加法函数。
add<int>(1,2); // 调用编译器实例化出的函数
// 当出现函数模板和非函数模板函数同名的情况,如果调用的类型满足非函数模板函数,那么就会优先调用该函数。
return 0;
}
类模板
使用格式
template <class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
template <class T>
class Vector
{
public:
Vector(int capacity = 10)
:_a(new T[capacity]
, _size(0)
, _capacity(capacity)
{}
~Vector();
void push_back(const T& x);
T& operator [] (int pos)
{
assert(pos < _size);
return _a[pos];
}
private:
T* _a;
int _size;
int _capacity;
}
// 当成员函数在类外定义时,需要加模板参数列表
template <class T>
// 指明所属
Vector<T>::~Vector()
{
if(_a)
{
delete[] _a;
}
_size = _capacity = 0;
}
template <class T>
void Vector<T>::push_back(const T& x)
{
_a[_size++] = x;
}
int main()
{
// 注意:类模板不是真正的类,实例化出的才是真正的类。
// 类模板的实例化
Vector<int> v1;
Vector<double> v2;
return 0;
}
非类型模板参数
模板参数分为:类型模板参数、非类型模板参数
类型模板参数:出现在模板参数列表中,class后typename之后。
非类型模板参数:使用一个常量作为类(函数)模板的一个参数,在类(函数)中可以将该参数当成一个常量使用。
namespace yair
{// 定义一个可以修改长度的数组
template<class T, int N = 10> // 给定一个缺省值
class array
{
private:
T _a[N];
};
}
int main()
{
yair::array<int> a0; // a0的长度为10
yair::array<int, 10> a1; // a1的长度为10
yair::array<int, 100> a2; // a2的长度为100
return 0;
}
注意:
1、浮点数、类对象是不可以作为非类型模板参数(这些可以作为非类型模板参数int、size_t、long、long long、char、short)
2、非类型模板参数是一个常量
模板的特化
当我们想使用一个函数模板去比较两个变量的大小时,可以这样
namespace yair
{
template<class T>
bool IsEuqal(T a, T b)
{
return a == b;
}}
int main()
{
int a = 0, b = 1;
cout << yair::IsEuqal(a, b) << endl; // 可以比较,结果正确
const char* p1 = "hello";
const char* p2 = "world";
cout << yair::IsEuqal(p1, p2) << endl; // 可以比较,但是结果是比较两个指针的地址,而不是内容return 0;
}
这个时候就无法比较p1和p2;此时就需要对模板进行特化。
模板特化:在原模板类的基础上,针对特殊类型所进行特殊化处理。
模板特化分为:函数模板特化、类模板特化
函数模板特化
namespace yair
{
template<class T> // 必须要有一个基础模板
bool IsEuqal(T a, T b)
{
return a == b;
}
template<> // 关键字template后加尖括号
bool IsEuqal<const char*>(const char* a, const char* b) // 函数名后跟尖括号,尖括号中必须指定需要特化的类型;函数的形参必须和模板函数的基础参数类型相同。
{
return strcmp(a, b) == 1;
}
}
这样p1和p2就会调用这个特化的函数了。
注意:这样会优点麻烦,也可以直接实现一个字符串类型的比较函数。这样会更简单明了,可读性更高,所以函数模板不建议特化
bool IsEuqal(const char*a, const char* b)
{
return return strcmp(a, b) == 1;
}
类模板特化
全特化
namespace yair
{
template<class T1, class T2>
class Date
{
public:
Date()
{
cout << "Date(T1, T2)" << endl;
}
private:
T1 _a1;
T2 _a2;
};template<>
class Date<int, char>
{
public:
Date()
{
cout << "Date(int, char)" << endl;
}
private:
int _a1;
char _a2;
};
}
int main()
{
yair::Date<int, int> d1;
yair::Date<int, char> d2;
}
偏特化
对模板参数的一部分参数特化
namespace yair
{
template<class T1, class T2>
class Date
{
public:
Date()
{
cout << "Date(T1, T2)" << endl;
}
private:
T1 _a1;
T2 _a2;
};template<class T1>
class Date<T1, char>
{
public:
Date()
{
cout << "Date(T1, char)" << endl;
}
private:
T1 _a1;
char _a2;
};template<typename T1, typename T2> // 偏特化不仅是指特化部分参数,也是对模板参数进一步条件限制
class Date<T1*, T2*>
{
public:
Date()
{
cout << "Data<T1*, T2*>" << endl;
}private:
T1 _a1;
T2 _a2;
};template<class T1, class T2>
class Date<T1&, T2&>
{
public:
Date()
{
cout << "Data<T1&, T2&>" << endl;
}private:
T1 _a1;
T2 _a2;
};
}
int main()
{
yair::Date<int, int> d1; // 调用基础模板
yair::Date<double, char> d2; // 调用特化的int模板
yair::Date<int*, int*> d3; //调用特化的指针模板
yair::Date<int&, int&> d4; //调用特化的引用模板
}
模板分离编译
分离编译的概念:一个程序分为若干个源文件,每个源文件单独编译成一个目标文件,最后将这些目标文件链接在一起,形成一个单一的可执行文件的过程称为分离编译。
// a.h
template<class T> // 声明
T Add(const T& left, const T& right);
// a.cpp
template<class T> //定义
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include"a.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
在编译阶段,Add函数的定义没有实例化,(也就是说不知道它具体的类型)因此没有生成具体的函数。然后主函数中,在链接时,会去寻找函数的地址,但是这个Add函数没有生成具体代码,因此会链接错误。
解决方法:
1、将声明和定义放在一个文件中
2、在模板定义的地方显式实例化,但不实用
显式实例化模板的定义
// a.cpp
template<class T> //定义
T Add(const T& left, const T& right)
{
return left + right;
}
template // 注意这里不需要加尖括号"<>"
int Add(const int& left, const int& right) // 这里只能提供给int类型的加法函数,但是对于其他类型,必须一一实例化。所以说不实用。
{
return left + right;
}
模板总结
优点:
1、模板的复用性很强,节省了代码,C++的标准模板库(STL)因此产生
2、增强了代码灵活性
缺点:
1、模板会导致代码膨胀(因为会有很多实例化)。也会导致编译时间变长
2、出现错误时,对错误不容易定位,维护性变差。