假设我们有如下 比较 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 Name
2.偏特化: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( )
{
;
}