模板
模板是一种泛型编程的机制,也是一种复用的手段。
//模板函数的格式:
template<class 形参1,... ,class 形参n>
返回值 fun(参数列表)
{...}
//模板类的格式:
template<class 形参1,... ,class 形参n>
class A
{...};
如何实例化
编译器调用模板函数时,编译器会根据实参的类型,推演出模板的类型,并再生产相应的代码。
A<int> a; //类只能显式实例化,没法推演。
fun<int>(1,2); //显式实例化,不需要推演
fun(1,2) //需要推演
如果你想学习C/C++可以来这个裙,首先是330,中间是859,中间是766,里面可以学习和交流,也有资料可以下载。
模板函数的重载
根据模板函数的形参类型的不同,调用函数时编译器会去找和自己类型更匹配的模板进行推演并实例化。
- 浮点数和类类型不可以作为非类型模板参数
模板的特化
为什么需要特化,因为在对不同类型的参数进行一些相同的处理时,如增容,拷贝等,不同类型的处理方式可能有些不同,如string增容时和其他类型就不同,它需要考虑深拷贝,所以要另外处理,这时候就用特化。
定义模板类时,会先去找实例化的好的且类型匹配的模板,所以调用的模板类的优先级 全特化,半特化,普通模板
- 全特化 特化所有的模板,就是制定所有模板的类型
template<>
class<int, char>
{...};
- 偏特化 特化部分模板参数+指定模板参数是某种特殊的类型 如指针或引用:template<T*>
用模板实现容器的适配器
我们可以用其他容器实现某一个容器,如我们可以用顺序变实现stack,也可以链表实现stack,那么可以写一个模板类,
template<class T, class Container>
class stack
{...};
这样我们想要什么容器实现,就可以在定义时,传什么容器。
队列适配器和栈适配器代码:github
类型萃取
模板特化的延伸,提取出类型,提前把处理动作一样的类型typedef成一个类型,其他自定义的类型就也可以typedef成一个类型去同一个指定的动作。这样就可以根据提取出来的类型判断它属于萃取后的哪种类型,然后进行相应的操作。
为什么要有类型萃取?
如果一个类里的成员函数对不同类型需要有不同操作的的地方只有那么一小块代码,如有一个顺序表的类,string需要深拷贝,其他内置类型可以浅拷贝。那么模板显然很不划算,因为只能对函数或者类进行特化,上面如果要用模板的话,需要再特化一个相同的类。现在要根据不同类型对部分代码块进行相应的操作,这时就需要类型萃取,通过把类型萃取后,在需要不同操作的地方可以通过萃取的这个类型重载这个函数,也可以在函数里需要不同操作的地方进行if else判断相应类型来进行相应的操作。
看下面代码理解萃取
#include<iostream>
#include<string>
#include<assert.h>
using namespace std;
struct TypeFalse
{
bool Get()
{
return false;
}
};
struct TypeTrue
{
bool Get()
{
return true;
}
};
template<class T>
struct TypeTraits
{
typedef TypeFalse IsFodType;
};
template<>
struct TypeTraits<int>
{
typedef TypeTrue IsFodType;
};
template<>
struct TypeTraits<char>
{
typedef TypeTrue IsFodType;
};
template<>
struct TypeTraits<double>
{
typedef TypeTrue IsFodType;
};
//顺序表
template<typename T>
class Seqlist
{
public:
Seqlist()
:_a(NULL)
, _size(0)
, _capacity(0)
{}
void _CheckCapacity()
{
if (_capacity <= _size)
{
_capacity = _capacity * 2 + 3;
T* tmp = new T[_capacity];
if (TypeTraits<T>::IsFodType().Get() == false) //自定义类型或者string
{
for(size_t i = 0; i < _size; i++) ///深拷贝
{
tmp[i] = _a[i];
}
}
else
{
memcpy(tmp, _a, sizeof(T)*_size); //值拷贝
}
delete _a;
_a = tmp;
}
}
Seqlist(const Seqlist<T>& s)
:_a(NULL)
, _size(0)
, _capacity(0)
{
while (_size < s._size)
{
PushBack(s._a[_size]);
}
_capacity = s._capacity;
}
void PushBack(const T& x)
{
_CheckCapacity();
_a[_size] = x;
_size++;
}
~Seqlist()
{
if (_a != NULL)
{
delete[] _a;
_size = _capacity = 0;
_a = NULL;
}
}
void print()
{
for (int i = 0; i < _size; i++)
{
cout << _a[i] << endl;
}
}
private:
T* _a;
size_t _size;
size_t _capacity;
};
int main()
{
Seqlist<int> s1;
s1.PushBack(2);
s1.PushBack(1);
s1.PushBack(3);
s1.PushBack(4);
s1.print();
Seqlist<string> s2;
s2.PushBack("rrrrrrrrrrrrrrrrrrrrrr");
s2.PushBack("qqqq");
s2.PushBack("wwww");
s2.PushBack("eeee");
s2.print();
return 0;
}
模板的分离编译
模板类的成员函数定义放在data.cpp源文件,声明放在data.h源文件,在另外一个main.cpp文件里调用使用模板类调用成员函数则会发生链接错误。所以模板不支持分离编译
模板为什么不支持分离编译?
铺垫:
程序源文件在执行链接时,如果函数的定义和声明不在同一文件,那么链接时就会去其他.o文件的符号表里去找函数定义的处的地址,找到函数定义的地址后,并添加到调用函数处的call命令后面(call fun 0xadd),才能链接成功,才可生生执行程序。
解释:
因为模板没有实例化时是不生成相应的代码的,因为编译的时候两个原文件互不影响,所以没有实例化,因此在链接的时候就找不函数定义的地方,那么就会出现链接错误。
解决模板的分离编译
- 显式实例化,在定义模板的地方,主动的去实例化相应类型的模板。template class<int>;
- 把声明定义都写在.hpp里,main.c调用模板类的函数时就可以了,其实.hpp相当于.h,预处理时就会头文件展开在.cpp里。