c++模板

来源:互联网 发布:淘宝账号是什么 编辑:程序博客网 时间:2024/05/28 09:31

假设我们有如下 比较 Fun

bool IsEqual (const int& left , const int& right)  
{
return left == right;

此时我们比较 int 值是否相等->函数调用->    isEqual( 1, 2 );

但如果我们想比较 两个 string 对象是否相等, 则需要重新写一个Fun:

bool IsEqual (const string& left , const string& right)  
{
return left == right;

string s1 = "yuanyuanyuan";

string s2 = "zhaobaba";

->isEqual( s1, s2 );


这样我们每比较一种类型,就需要重新写一个比较函数,做了大量的重复工作。

c++为了提高代码复用性,提出了模板函数概念:

下面是一个 比较 模板函数框架:

template<typename/*or class*/ T>bool IsEqual( const T& left , const T& right )//传引用也是为了避免生成临时对象 or 临时变量  万一参数为string对象,则会拷贝构造生成临时对象,这里传引用是为了节省空间{return left == right;//如果不是内置类型而是自己定义的类型, 则必须实现即重载 operator==( )函数} 
T代表我们传参数时传入的类型, 我们只需要在模板函数中将参数写为T, 具体函数调用时,编译器会通过实参推演形参类型

void test1( ){string s1( "s1" ), s2( "s2" );cout << IsEqual( s1, s2 ) << endl;//调用不同Fun  T分别为 string类型 与 int类型(编译器去做推演)  模板函数的实例化(编译器根据类型自动生成相应函数)cout << IsEqual( 1,1 ) << endl;//通过实参推演形参类型
//他们通过编译器实例化后是 重载函数 (模板实例化后生成代码(他们参数类型不同)所以重载)}

模板函数   不同类型  T   是调用不同函数.


如果有模板函数和具体类型函数,优先使用有参数的函数(非模板),没有才使用模板函数

例:

bool IsEqual (const int& left , const int& right)//如IsEqual( 1, 2 )优先调用它,而不是下面的Fun( ){return left == right;} template <typename T>bool IsEqual (const T& left , const T& right ){return left == right;}


模板参数匹配及显示实例化:


如第一种 只有一个模板参数T时  传  int 与 double 时,    不匹配   ->  isEqual( 1, 1.2 );

template <typename T>bool IsEqual (const T& left , const T& right ){return left == right;} void test1 (){cout<<IsEqual (1,1)<<endl;cout<<IsEqual(1,1.2)<<endl; // 模板参数不匹配
//下面是一种解决办法:
//templat<class T1, class T2>   ( const T1& left , const T2& right )  
//万一 left 传 int, right 传 double 第一种会出错, 此时必须这样定义( 第一种解决办法,另一种方法是显示实例化 )cout<<IsEqual<int>(给定类型  编译器不会推演,直接生成该类型代码)(1,1.2)<< endl; // 显示实例化(第二种办法)(T规定为int)cout<<IsEqual<double>(1,1.2)<< endl; // 显示实例化}
没有对象实例化时,模板函数编译器不生成响应代码 , 证明:

template <typename T>bool isequal (const t& left , const t& right ){return left == right//这没有“;”时  编译通过, 并没有错,因为没生成响应代码} 

不调用时:函数内部不进行语法检查
但 函数外部 会进行格式检查  如 bool IsEqual ( const T& x, const T& y   少个括号  编译时还是会报错的

模板函数,把属于我们的工作,从实参推演形参,交给编译器去做


说完了模板函数,我们来说说模板类。 同样的问题不只存在于模板函数中,模板类中也存在.

如 类中数据成员的 类型.  

但模板类不能自己推演, 不能根据你传的参数推演T的类型,必须显示实例化!

需要注意的是模板类 类型为ClassName<T>, 类名为ClassName

但普通类, 类名和类类型相同

构造函数还有拷贝构造函数名字和类名相同

But拷贝构造Fun()参数(这个类类型必须加上类型T):

ClassName( const ClassName<T>& s );   经测试有无<T> 无影响


.h中声明     

声明定义分离   标准!类中只有声明
.cpp中定义


类外面定义声明在类中的函数时:

类外定义时:
template<class T> //再写一次  模板
void SeqList<T>::Print( ) //然后注意,这里的返回类型 为   SeqList<T>    类类型需要加<T>
{
...;
}



是T就加引用, 万一T是string , 深拷贝代价太大, 不改变就加 const ->const T&

拷贝构造参数不给引用会循环递归!  想想因为->拷贝构造未完成,所以会一直构造参数,递归死循环。  必须传引用

调试 时 栈溢出错误, 一般都是(递归)死循环

存在才赋值(赋值运算符重载),  不存在就拷贝构造.  s1 = s2  s1,s2均存在属于第一种情况。 string s2 = s1.  s2不存在属于第二种情况。


有了类模板参数后,我们就可以实现容器适配器:


队列:队尾进,对头出,插入在队尾
栈:栈顶进,   栈底->栈顶


适配器模式实现一个队列:

使用  List 实现容器适配器:

template<class T, class Container>class Queue{public:void Push( const T& x );{_con.PushBack( x );}void Pop( ){_con.PopFront( );}T& Front( )//根据要实现的函数功能(此处为队列),再决定在里面调用容器(此处为List)的什么函数{return _con.Front( );}T& Back( )//因为出了作用域还存在!  所以T&{return _con.Back( );}size_t Size( ){return _con.Size( );}bool IsEmpty( ){return _con.IsEmpty( );}protected:Container _con;};void TestQueue( ){Queue<int, List<int>> q;//我们实现一个 List 容器(类模板List), 并且List中 有声明我们使用的Fun()
q.Push( 1 );while (!q.IsEmpty( )){cout << q.Front( ) << " ";q.Pop( );}}


template<class T, class Container = List<T>>//Stack<int> s 这样也可以   模板的缺省参数class Stack{public:void Push( const T& x ){_con.PushBack( x );}T& Top( )//因为栈后进先出,所以Top是最后进来的, 所以是返回Back( ).{return _con.Back( );}protected:Container _con;};void TestStack( )//若是链表,删除时    释放空间,插入又开空间。 不停释放开辟不好,但顺序表不需要,   原空间反复利用,  它删除只是 --size{Stack<int, SeqList<int>> s;s.Push( 1 );while ( !s.IsEmpty( ) ){cout << s.Top( ) << " ";s.Pop( );}}

模板的模板参数:接上面,若这样传参数,Stack<int, SeqList<char>> s;  也就是这个 char 传错 会存在隐患。

为了避免错误,我们进行改进:template<class T, template<class(不用传参数,和T类型一样)>class Container = SeqList(缺省的模板的模板参数)>  (传一个类名,而不是传一个类型)。  避免传char等  出错。
例: Stack<int, List> s;




const size_t N = 100;


template<class T>
class SeqList
{
protected:
T _a[N];
size_t size;
};

非类型模板参数:


现在生成对象,   _a大小只能是200。  不管定义几个。  为了解决这个问题 -> 非类型模板参数。 N是一个常量,可以给缺省参数  它也可以做模板函数的非类型模板参数。


template<class T(类型模板参数),size_t N = 200>


SeqList<int, 200> s1;
SeqList<double, 2000> s2;


浮点数和类对象不允许做非类型模板参数


模板的特化:

1.全特化::

template <typename T>class SeqList{ public :SeqList();~ SeqList();private :int _size ;int _capacity ;T* _data ;};template<typename T>SeqList <T>:: SeqList(): _size(0), _capacity(10), _data(new T[ _capacity]){cout<<"SeqList<T>" <<endl;}template<typename T>SeqList <T>::~ SeqList(){delete[] _data ;}
template <>class SeqList <int>{ public :SeqList(int capacity);~ SeqList();private :int _size ;int _capacity ;int* _data ;};


 特化后定义成员函数不再需要模板形参

SeqList <int>:: SeqList(int capacity): _size(0), _capacity(capacity ), _data(new int[ _capacity]){cout<<"SeqList<int>" <<endl;} // 特化后定义成员函数不再需要模板形参SeqList <int>::~ SeqList(){delete[] _data ;} void test1 (){SeqList<double > sl2;SeqList<int > sl1(2);//优先调用特化部分(调用才生成代码,没有特化的,才是T)}

template <typename T1, typename T2>class Data{ public :Data();private :T1 _d1 ;T2 _d2 ;};template <typename T1, typename T2>Data<T1 , T2>:: Data(){cout<<"Data<T1, T2>" <<endl;}
局部特化第二个参数:

template <typename T1>class Data <T1, int>{public :Data();private :T1 _d1 ;int _d2 ;};template <typename T1>Data<T1 , int>:: Data(){cout<<"Data<T1, int>" <<endl;} 


ps:下面的例子可以看出,偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
 局部特化两个参数为指针类型

template <typename T1, typename T2>class Data <T1*, T2*>{ public :Data();private :T1 _d1 ;T2 _d2 ;T1* _d3 ;T2* _d4 ;};template <typename T1, typename T2>Data<T1 *, T2*>:: Data(){cout<<"Data<T1*, T2*>" <<endl;}

局部特化两个参数为引用:

template <typename T1, typename T2>class Data <T1&, T2&>{ public :Data(const T1& d1, const T2& d2);private :const T1& _d1;const T2& _d2;T1* _d3 ;T2* _d4 ;};template <typename T1, typename T2>Data<T1&, T2&>:: Data(const T1& d1, const T2& d2): _d1(d1 ), _d2(d2 ){cout<<"Data<T1&, T2&>" <<endl;} void test2 (){Data<double , int> d1;Data<int , double> d2;Data<int*, int*> d3;Data<int&, int&> d4(1, 2);} 
模板的全特化和偏特化都是在已定义的模板基础之上,不能单独存在。


template <typename T1>//如果都是具体类型  则 template<>里面啥都不用写,因为不是模板参数,都是具体类型Date<T1, int>:: Date( )//第二个参数特化为 int 类型{;}

template <typename T1, typename T2>class Data<T1*, T2*>调用:Data<int*, double*>template <typename T1, typename T2>class Data<T1, T2*>调用:Data<int, double*>
1.全特化:template<class T>  class Name2.偏特化:template<>  class Name<int>
  template<class T>  class Name<T*>

1.进一步条件限制模板参数
2.多个模板参数时,特化部分参数

编译错误:语法
链接错误:找不到具体定义它地方


模板的分离编译:声明,定义分离

ClassName.h声明
ClassName.cpp实现(这样则链接错误)
Test.cpp测试

template<class T>
void Data<T>::F( )
{}

非模板类是可以分离编译的

模板不支持分离编译:

分离编译后,找不到定义!找不到cpp中的定义,因为大家分开编译(main cpp) 所以, cpp中模板未实例化,未生产相应代码。所以链接错误!
模板实例化才生成具体代码,才语法检查(但并不是不实例化时什么语法都不检查!)


main.cpp
Data.h
Data.cpp


预处理(预编译)(展开头文件,替换宏,去注释,条件编译,处理行号文件名函数名(__FILE__(也是一个宏)等))->main.i 和 Data.i
编译(检查语法错误,)->main.s  汇编代码   一句代码对应多句汇编代码 ++i  (mov  eax, i) -> (add eax) -> (mov  i, eax)  cpu不能识别汇编代码  main.s  Data.s
汇编   汇编代码->二进制机器码(这是一个过程)main.o  Data.o 符号表(函数名+地址  F:0x1122)
链接 ->.exe(Windows)     a.out(Linux)   可执行文件

函数变成指令,第一句指令即Fun 地址

分离编译->链接错误(没有找到定义->函数的地址)


非模板类 .cpp .h
模板类 .hpp(声明定义放一起)


.hpp:

//分离编译template<class T>class Data{public:void F( );protected:T _d;};template<class T>void Data<T>/*类型*/::F( ){;}


1 20