泛型编程
在之前的编程学习中,我们学会了swap函数的写法以及通过define的方式使其更灵活
但若一个程序中,有多个不同类型的变量需要调用swap函数,那swap函数该如何写呢?
如下:
#define Datatype int
void Swap(Datatype& left, Datatype& right)
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int i = 1, j = 2;
double x = 1, y = 2;
char m = 'a', n = 'b';
swap(i, j);
swap(x, y);
swap(m, n);
}
使用函数重载虽然可以实现,但是有一下几个不好的地方:
-
重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
-
代码的可维护性比较低,一个出错可能所有的重载均出错
那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?
若是在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),
来获得材料不同但外形相同的铸件(即生成具体类型的代码)
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
函数模板
函数模板概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板格式
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
模板参数:类似于函数参数,只是模板参数中是模板类型,函数参数中是参数对象
typename后面类型名字T是随便取的,一般都是大写字母或单词
T代表的是一个模板类型(虚拟类型)
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
函数模板的原理
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
函数模板的实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化
让编译器根据实参推演模板参数的实际类型
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
//注意:两个函数并不是同一个函数
Add(a1, a2);
Add(d1, d2);
}
特别注意:使用模板时,编译器一般不会进行类型转换操作。所以,以下代码将不能通过编译:
int a = 10;
double b = 1.1;
Add(a, b);
//解决方法:手动将两个变量的类型统一
Add((double)a, b);
Add(a, (int)b);
因为在编译期间,当编译器看到该实例化时,需要推演其实参类型,通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错
显式实例化
在函数名后的<>中指定模板参数的实际类型
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10;
double b = 1.1;
Add<int>(a, b); //指定模板参数的实际类型为int
return 0;
}
注意:使用显示实例化时,如果传入的参数类型与模板参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,则编译器将会报错。
模板参数的匹配原则
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b); //调用非模板函数,编译器不需要实例化
int d = Add<int>(a, b); //调用编译器实例化的Add函数
return 0;
}
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。
如果模板可以产生一个具有更好匹配的函数, 那么将选择模板函数
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译
//因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。
return 0;
}
类模板
类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
实例:用c++实现栈
template<typename T>
class Stack
{
public:
Stack(size_t capacity = 4)
:_next(nullptr)
, _capacity(0)
, _top(0)
{
if (capacity>0)
{
_next = new T[capacity];
_capacity = capacity;
_top = 0;
}
}
~Stack()
{
delete[] _next;
_next = nullptr;
_capacity = 0;
_top = 0;
}
void push(const T& x)
{
if (_top==_capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
//c++中使用new扩容
//1.开新空间 2.拷贝数据 3.释放旧空间
T* tmp = new T[newcapacity];
if (_next)
{
memset(tmp, _next, sizeof(T)*_top);
delete[] _next;
}
_next = tmp;
_capacity = newcapacity;
}
_next[_top] = x;
_top++;
}
const T& top()
{
assert(_top > 0);
return _next[_top - 1];
}
private:
T* _next;
size_t _capacity;
size_t _top;
};
注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。
template<typename T>
class Stack
{
public:
Stack()
{}
~Stack()
{}
void push(const T& x)
private:
T* _next;
size_t _capacity;
size_t _top;
};
//类模板中的成员函数在类外定义,需要加模板参数列表
template<typename T>
void push(const T& x)
{
if (_top==_capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
//c++中使用new扩容
//1.开新空间 2.拷贝数据 3.释放旧空间
T* tmp = new T[newcapacity];
if (_next)
{
memset(tmp, _next, sizeof(T)*_top);
delete[] _next;
}
_next = tmp;
_capacity = newcapacity;
}
_next[_top] = x;
_top++;
}
注意:类模板不支持分离编译,即声明在xxx.h文件中,而定义却在xxx.cpp文件中。
类模板的实例化
类模板实例化与函数模板实例化不同,类模板都是使用显示实例化,
类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可
类模板名字不是真正的类,而实例化的结果才是真正的类
void test_4()
{
//类模板都是使用显示实例化
//虽然用的都是一个类模板,但是两者不是一个类型
Stack<int> st1;
Stack<char> st2;
}