泛型编程:编写与类型无关的代码,是代码复用的一种手段。模板是泛型编程的基础。
利用函数模板写一个通用的交换函数:
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
函数模板适用于函数体相同、函数的参数个数相同而类型不同的情况。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
隐式实例化
编译器根据实参推演模板参数的实际类型
显式实例化:在函数名后的<>中指定模板参数的实际类型
显式实例化时编译器不进行类型推演,如果类型不匹配,编译器会进行隐式类型转换,无法转换成功则报错。
上面的加法函数模板也可以完成自定义类型对象的加法,如定义一个复数类Complex,只需要在类内部对’+'进行重载即可
调用结果:
完整代码:
#include
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{
cout << typeid(T).name() << endl;
return left + right;
}
class Complex
{
friend ostream& operator<<(ostream& _cout, const Complex & x);
public:
Complex(double real = 0.0, double image = 0.0)
: _real(real)
, _image(image)
{}
Complex operator+(const Complex& c)const
{
return Complex(_real + c._real, _image + c._image);
}
private:
double _real;
double _image;
};
ostream& operator<<(ostream& _cout, const Complex & x)
{
_cout << x._real << "." << x._image;
return _cout;
}
int main()
{
Complex c1(1.0, 2.0);
Complex c2(3.0, 4.0);
Complex c3;
c3 = Add(c1, c2);
cout << c3 << endl;
system("pause");
return 0;
}
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从改模板产生出一个实例。如果模板可以产生一个更好匹配的函数,那么选择模板。
模板不允许自动类型转化
template<tpyemame T1, typename T2, ..., typename Tn>
class Name
{
//
};
#include
#include
#include
using namespace std;
template <typename T>
class Seqlist
{
public:
Seqlist(size_t InitCapicity = 3)
:_array(new T[InitCapicity])
, _size(0)
, _capacity(InitCapicity)
{
}
~Seqlist()
{
if (_array)
{
delete[]_array;
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
//尾插
void PushBack(const T& data)
{
CheckCapacity();
_array[_size] = data;
_size++;
}
//尾删
void PopBack()
{
if (Empty())
{
return;
}
_size--;
}
//头插
void PushFront(const T& data)
{
CheckCapacity();
for (int i = (int)_size; i > 0; i--)
{
_array[i] = _array[i - 1];
}
_array[0] = data;
_size++;
}
size_t Capacity()
{
return _capacity;
}
size_t Size()
{
return _size;
}
//重载[]
T operator[](int position)
{
//assert()内的条件满足则不会触发断言
assert(position < _size);
return _array[position];
}
T& Back();
T& Front();
private:
void CheckCapacity()
{
if (_size == _capacity)
{
//2倍扩容
_capacity *= 2;
T* temp = new T[_capacity];
//将原来顺序表的内容拷贝到新的空间
for (int i = 0; i < _size; i++)
{
temp[i] = _array[i];
}
//清空旧空间内容
delete[] _array;
_array = temp;
}
}
//判空
bool Empty()
{
return 0 == _size;
}
T* _array;
size_t _size;
size_t _capacity;
};
template<typename T>
T& Seqlist<T>:: Back()
{
return _array[_size - 1];
}
template<typename T>
T& Seqlist<T>::Front()
{
return _array[0];
}
class Date
{
//类内声明为友元函数
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year=2022, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
//类外定义
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
int main()
{
Seqlist<int> s1;
s1.PushBack(1);
s1.PushBack(2);
s1.PushBack(3);
s1.PushBack(4);
s1.PushBack(5);
s1.PushFront(0);
cout << s1.Back() << endl;
cout << s1.Capacity() << endl;
cout << s1.Front() << endl;
cout << s1.Size() << endl;
cout << s1[3] << endl;
Seqlist<double> s2;
s2.PushBack(1.8);
s2.PushBack(2.2);
s2.PushBack(3.5);
s2.PushBack(4.9);
s2.PushBack(5.2);
s2.PushFront(6.6);
cout << s2.Back() << endl;
cout << s2.Capacity() << endl;
cout << s2.Front() << endl;
cout << s2.Size() << endl;
cout << s2[3] << endl;
//将Date类对象保存在顺序表中
Seqlist<Date> s3;
Date d1(2022, 8, 4);
Date d2(2022, 8, 5);
Date d3(2022, 8, 6);
s3.PushBack(d1);
s3.PushBack(d2);
s3.PushBack(d3);
cout << s3.Back() << endl;
cout << s3.Capacity() << endl;
cout << s3.Front() << endl;
cout << s3.Size() << endl;
cout << s3[2] << endl;
system("pause");
return 0;
}
将顺序表写成类模板后,可以自定义要存放在顺序表中的数据类型,只需要定义对应数据类型的对象即可,这里分别定义了int、double和自定义类型Date类的数据。
要注意的是,这里Seqlist不是具体的类,而是类模板名,是编译器根据被实例化的类型生成具体类的模具。Seqlist
特化:特殊化处理
template<typename T>
const T Max(const T& left, const T& right)
{
if (left > right)
{
return left;
}
return right;
}
对于上面的函数模板,int 类型和double类型都可以比较大小,但是不能能处理字符串
函数模板的特化步骤:
因为函数模板特化较为复杂且不实用,函数模板一般不需要进行特化 ,如果模板哪种类型出错,直接将该类型对应的普通函数写出来。对于上面字符串类型不能直接比较的情况,直接写出字符串类型比较的普通函数即可。
比较char*类型的普通函数:
char* Max(char* left, char* right)
{
if (strcmp(left, right) > 0)
return left;
return right;
}
所有的参数都具体化,类型化的类模板
模板类
template<typename T1,typename T2>
class Date
{
public:
Date()
{
cout << "Date(T1,T2)" << endl;
}
private:
T1 _d1;
T2 _d2;
};
全特化的模板类,专门用于处理参数为int,char类型
template<>
class Date<int,char>
{
public:
Date()
{
cout << "Date" << endl;
}
private:
int _d1;
int _d2;
};
定义Date类对象时,
//类模板的第二个参数做了特殊化处理
template<typename T1>
class Date<T1,int>
{
public:
Date()
{
cout << "Date(T1,int)" << endl;
}
private:
T1 _d1;
int _d2;
};
对于这个偏特化的类模板,定义对象时,只要第二个参数是int类型,就会调用特化处理的模板
//两个参数偏特化为指针类型
class Date<T1*, T2*>
{
public:
Date()
{
cout << "Date(T1*, T2*)" << endl;
}
private:
T1* _d1;
T2* _d2;
};
模板特化的应用:类型萃取
参考文章:
C++之类型萃取
类型萃取
概念:
每个项目由多个源文件共同实现,而每个源文件单独编译成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译。
注意:头文件不参与编译,在预处理阶段,头文件已经展开,即:将头文件中的内容拷贝一份放到源文件。
如上图所示,在template.cpp源文件中定义了Sub函数,并在template.h头文件中进行了声明。
但是在编译过程中,编译器是对各个源文件进行单独编译的,template.cpp源文件进行编译的过程中,没有检测到Sub函数模板的实例化,所以不会生成对应的代码,在main.cpp源文件中进行调用,链接阶段便会出错。如图:
理解两个概念:
这里main.cpp源文件编译完成后,没有找到Sub函数的定义,但是由于头文件中进行了声明,在预处理阶段头文件中的声明会拷贝到源文件中,所以并不会立即报错,而是将Sub函数放在未解决符号表中,链接阶段,在template.cpp文件的导出符号表中找Sub函数的入口地址,而如果Sub函数没有生成则会报错。
将模板定义在 .hpp 文件中
调用时只需要包含模板定义所在的.hpp文件即可