泛型编程
写一个交换函数,在学习模板之前,为了匹配不同的参数类型,我们可以利用函数重载来实现。
void Swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
void Swap(char& a, char& b)
{
char c = a;
a = b;
b = c;
}
void Swap(double& a, double& b)
{
double c = a;
a = b;
b = c;
}
//...
虽然这样似乎解决了问题,但是这样的设计写着太过麻烦,只要出现新类型就需要写新的函数,代码的复用率很低。有没有什么可以让我们一劳永逸呢?模板就可以实现这一功能。
这种通过抽象和模板化来编写可重用和灵活的代码以此提升代码的可读性和维护性,同时避免代码重复的方式称为泛型编程。
函数模板
函数模板是c++中的一类机制,通过在函数定义中使用模板参数,我们可以编写一个函数,而在调用时根据实际参数的类型自动生成相应的版本。
template <class T>
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
这样编译器就可以根据传入的参数类型来生成对应的Swap()函数,大大提高了代码的复用率。下面我们来尝试运行一下。
template <class T>
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
int main()
{
int a,b;
a = 1; b = 2;
double c, d;
c = 0.0; d = 1.2;
Swap(a, b);
Swap(c, d);
cout << a << " " << b << endl;
cout << c << " " << d << endl;
return 0;
}
我们发现,调用Swap()之后,int类型的ab和double类型的cd都完成了交换。但是他们是否调的是同一个函数呢?
转到反汇编:
我们发现两次调用的是不同的Swap()函数,根据传入参数类型的不同 ,编译器会生成不同的函数。然后再调用生成的函数。
函数模板的实例化
通过函数模板生成对应函数的过程叫做函数实例化。
当模板的参数只有一个时,却传入了不同类型的变量,编译器无法推导出T的类型,出现了推导错误。
template <class T>
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
int main()
{
int a, b;
a = 1; b = 2;
double c, d;
c = 0.0; d = 1.2;
Swap(a, c);
Swap(b, d);
cout << a << " " << b << endl;
cout << c << " " << d << endl;
return 0;
}
然后我们就会发现报错了:
我们写的模板中是两个相同的类型T,在实例化的过程中出现了推导问题不能生成对应的函数。
不重新定义模板参数的情况下,要解决这个问题有两种方法:
1.推导实例化,任然让编译器来推导出T的类型,通过强制类型转换来让传入的变量类型一致。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
template <class T>
T Add(const T& a, const T& b)
{
return a + b;
}
int main()
{
int a1 = 10, a2 = 5;
double d1 = 11.2, d2 = 12.6;
cout << Add(a1, (int)d1) << endl;
cout << Add((double)a1, d1) << endl;
return 0;
}
2.显示实例化,不用编译器推导T的类型,直接指定T的类型。
template <class T>
T Add(const T& a, const T& b)
{
return a + b;
}
int main()
{
int a1 = 10, a2 = 5;
double d1 = 11.2, d2 = 12.6;
/*cout << Add(a1, (int)d1) << endl;
cout << Add((double)a1, d1) << endl;*/
cout << Add<int>(a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
return 0;
}
这两种方法都可以解决推导问题,但是都对精度有影响。 并且当T不作参数时,只能使用显示实例化。
模板函数的匹配原则
当函数模板和现成的函数同时存在时,编译器会选择现成的函数。很简单,有现成的为什么还要自己生成呢。
T Add(const T& a, const T& b)
{
return a + b;
}
int Add(int a,int b)
{
return (a + b) * 10;
}
int main()
{
int a = 1;
int b = 2;
cout << Add(a, b) << endl;
return 0;
}
类模板
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
template <typename T>
class Stack
{
public:
Stack(int n = 4)
:_array(new T[n])
, _capacity(n)
, _size(0)
{
}
void push(const T& x)
{
if (_capacity == _size)
{
T* tmp= new T[2 * _capacity];
memcpy(tmp, _array, _size * sizeof(T));
delete[] _array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = x;
}
~Stack()
{
delete[] _array;
_array = nullptr;
_capacity = _size = 0;
}
private:
T * _array;
int _capacity;
int _size;
};
int main()
{
//类模板都是显示实例化
Stack<int> str1;
str1.push(1);
str1.push(2);
str1.push(3);
return 0;
}
底层:
首先,类模板不能推导实例化。
编译器不能自动推导出类中T的类型,这点和T作返回值不作参数的情况一样,编译器没有推理其中T类型的依据,所以不手动规定类的类型,就会报错。先比于c语言,用类模板的类可以储存不同类型的数据而不用重新在写一个Stack。
当定义和声明分离时,需要重新声明模板,并且99%的情况下,不能把定义和声明放到两个文件中。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
template <typename T>
class Stack
{
public:
Stack(int n = 4)
:_array(new T[n])
, _capacity(n)
, _size(0)
{
}
void push(const T& x);
~Stack()
{
delete[] _array;
_array = nullptr;
_capacity = _size = 0;
}
private:
T * _array;
int _capacity;
int _size;
};
template <typename T>//重新声明模板
void Stack<T>::push(const T& x)
{
if (_capacity == _size)
{
T* tmp = new T[2 * _capacity];
memcpy(tmp, _array, _size * sizeof(T));
delete[] _array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = x;
}
int main()
{
//类模板都是显示实例化
Stack<int> str1;
str1.push(1);
str1.push(2);
str1.push(3);
return 0;
}