1、泛型编程
——即实现一个通用的标准容器库。所谓通用的标准容器库,就是要做到:比如List类存放所有肯恩类型的对象这样的事;泛型编程让你编写一个完全一般化并可重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。泛型即是指具有在多种数据类型上皆可操作的意思,与模板有些类似。
——泛型编程的代表作品STL是一种高效、泛型、可交互操作的软件组件。
2、怎样编写一个通用的加法??
1》使用函数重载,针对每个所需相同行为的不同类型重新实现它。
缺点:1》只要有新类型出现,就要重新添加对应函数。
2》除类型外,所有函数的函数体都相同,代码的复用率不高。
3》如果函数只是返回值类型不同,函数重载不能解决问题。
4》一个方法有问题,所有的方法都有问题,不好维护。
2》使用公共基类,将通用代码放在公共基类里面。
缺点:1》借助公共基类来编写通用代码,将失去类型检查的优点。
2》对于以后实现的很多类,都必须继承自某个特定的基类,代码维护更加困难。
3》使用特殊的预处理程序。
缺点:1》不是函数,不进行参数类型检查,安全性不高。
4》还可以使用泛型编程。
3、泛型编程:
1》编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础。
2》模板分为:函数模板、类模板
4、函数模板:
1》函数模板代表了一个函数家族,在使用时被参数化,根据实参类型产生函数的特定类型。
2》函数模板的格式:
注意:1>typename是用来定义模板参数关键字,也可以使用class(建议尽量使用typename)
2>但是不能使用struct
3》函数模板也可以定义成inline函数。(但是inline必须放在模板参数表之后,返回值之前,不能放在template之前)
4》模板是一个蓝图,它本身不是类或者函数,编译器用模板产生特定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
5、实参推演:
从函数实参确定模板形参类型和值的过程叫做模板实参推演。多个类型形参的实参必须完全匹配。
6、类型形参转换:
1》一般不会转换实参来匹配已有的实例化,相反会产生新的实例。
2》编译器只会执行两种转换:
1>const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2>数组或者函数到指针的转换:如果模板形参不是引用类型,则对数组或者函数类型的实参应该用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
7、模板参数:
1》函数模板有两种类型的参数:模板参数和调用参数。
2》而模板参数又分为:类型形参和非类型形参
3》模板形参名字只能在模板形参之后到模板声明或定义的结尾之间使用,遵循名字屏蔽规则。
4》模板形参的名字在同一模板形参列表中只能使用一次。
5》所有模板形参前面必须加上class或者typename关键字修饰。
6》注意在函数模板内部不能指定缺省的模板实参。
8、非模板类型形参:
非模板类型形参是在模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。
9、模板形参说明:
1》模板形参表使用<>括起来。
2》和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不同。
3》模板形参表不能为空。
4》模板形参可以是类型形参,也可以是非类型形参。类型形参跟在class和typename之后。
5》模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型的使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换。
6》模板参数表中,class和typename具有形同的含义,可以互相交换,使用typename更加直观。但是关键字typename是作为C++标准加入到C++中,就得编译器可能不支持。
9+、模板函数重载:
注意:函数和所有的重载版本的声明都应该位域该函数被调用位置之前。
说明:
1》一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板可以被实例化为这个非模板函数。
2》对于非模板函数和同名函数模板,如果其他条件都相同,再调用时会优先调用非模板函数,而不会从该模板产生一个实例化。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3》显示指定一个空的模板实参列表,该语法告诉编译器只有姆安巴你才能匹配这个调用。而且所有的模板都应该根据实参演绎出来。
4》模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
10、函数模板的特化:
模板函数特化形式如下:
1》关键字template后面接一对空的尖括号<>
2》再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3》函数形参表
4》函数体
注意:在特化之前,这个函数模板必须已经存在,并且两者形参个数一样。
template<typename T> bool IsEqual(T left, T right) { return left == right; } template<> bool IsEqual<const char*>( const char * pleft, const char * pright) //这里的<const char*>就相当于代替了T { return pleft == pright; } 11、模板参数——适配器:stack(使用模板实现栈——后进先出) template <typename T> class SeqList { private : int _size ; int _capacity ; T* _data ; }; // template <class T, template<class> class Container> template <class T, template<class> class Container = SeqList> // 缺省参数 class Stack { public : void Push(const T& x ); void Pop(); const T& Top(); bool Empty(); private : Container<T > _con; }; void Test() { Stack<int> s1; Stack<int , SeqList> s2; }
模板的模板参数——实现队列 template<typename T,template< typename T > class Containter> //再嵌套一个模板类型参数,,这里的关键字一定是class————只有类模板参数才能指定缺省值 class Queue { public: Queue() {} void PushBack(const T& d) { _con.PushBack(); } void PopFront() { _con.PopFront(); } private: Containter<T > _con; }; int main() { Queue<int ,SeqList> q; q.PushBack(1); q.PushBack(2); q.PopFront(); return 0; }
12、非类型的模板参数:
// 静态顺序表 //template<typename T, size_t MAX_SIZE> template <typename T, size_t MAX_SIZE = 10> //带缺省模板参数 class SeqList { public : SeqList(); private : T _array [MAX_SIZE]; int _size ; }; template <typename T, size_t MAX_SIZE> SeqList <T, MAX_SIZE>::SeqList() : _size(0) {} void Test() { SeqList<int> s1; SeqList<int , 20> s2; }
13、模板类:
模板类也是模板,必须以关键字template开头,后接模板形参表。
14、模板类的实例化:
1》只要有一种不同的类型,编译器就会实例化出一个对应的类。
2》 SeqList<int > sl1 SeqList<double > sl2;当定义上述两种类型的顺序表时,编译器会使用int和double分别代替模板形参,重新编写SeqList类,最后创建名为SeqList<int>和SeqList<double>的类。
15、类模板的特化:分两种——局部特化、全特化
注意:全特化后定义成员函数,不再需要模板形参
1》类型萃取
2》POD类型萃取
16、模板的分离编译:
——解决方法:
1》在模板头文件 xxx.h 里面显示实例化->模板类的定义后面添加 template class SeqList<int >; 一般不推荐这种方法,一方面老编译器可能不支持,另一方面实例化依赖调用者。(不推荐)
2》将声明和定义放到一个文件 "xxx.hpp" 里面,推荐使用这种方法。
17、模板总结:
——优点:
1》模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2》增强了代码的灵活性。
——缺点:
1》模板让代码变得凌乱复杂,不易维护,编译代码时间变长。
2》 出现模板编译错误时,错误信息非常凌乱,不易定位错误。