内容简介:C++模板
假设我们有如下 比较 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( ) { ; }
以上所述就是小编给大家介绍的《C++模板》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 网站模板 | 现代时尚创新创意投资组合HTML5模板设计
- ReportLibrary 报表模板库新增 21 张报表模板,加入报表导出功能!
- ReportLibrary 报表模板库新增 21 张报表模板,加入报表导出功能!
- 工具集核心教程 | 第五篇: 利用Velocity模板引擎生成模板代码
- Word 模板引擎 poi-tl V1.3.0 发布,新增模板语法
- React与Vue模板使用比较(一、vue模板与React JSX比较)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JAVA 2核心技术 卷Ⅰ
[美] 霍斯特曼、[美] 科奈尔 / 叶乃文、邝劲筠 等 / 机械工业出版社 / 2006-5 / 88.00元
本书是Java技术经典参考书,多年畅销不衰,第7版在保留以前版本风格的基础上,涵盖Java2开发平台标准版J2SE5.0的基础知识,主要内容包括面各对象程序设计、反射与代理、接口与内部类、事件监听器模型、使用Swing UI工具箱进行图形用户界面设计,异常处理、流输入/输出和对象序列化、泛型程序设计等。 本书内容翔实、深入浅出,附有大量程序实例,极具实用价值,是Java初学者和Java程序员......一起来看看 《JAVA 2核心技术 卷Ⅰ》 这本书的介绍吧!