如何编写一个通用加法函数?
使用函数重载,针对每个所需相同行为的不同类型重新实现它
int Add(const int &_iLeft, const int &_iRight) { return (_iLeft + _iRight); } float Add(const float &_fLeft, const float &_fRight) { return (_fLeft + _fRight); }
缺点
- 只要有新类型出现,就要重新添加对应函数。
- 除类型外,所有函数的函数体都相同,代码的复用率不高
- 如果函数只是返回值类型不同,函数重载不能解决
- 一个方法有问题,所有的方法都有问题,不好维护。
使用公共基类,将通用的代码放在公共的基础类里面
缺点
- 借助公共基类来编写通用代码,将失去类型检查的优点;
- 对于以后实现的许多类,都必须继承自某个特定的基类,代码维护更加困难。
使用特殊的预处理程序
#define ADD(a, b) ((a) + (b))
【缺点】 不是函数,不进行参数类型检测,安全性不高
从而引出了泛型函数
泛型函数
编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础。模板包括函数模板和类模板
函数模板
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本.
模板函数的格式
template<typename Param1, typename Param2,...,class Paramn> 返回值类型 函数名(参数列表) { ... }
typename是用来定义模板参数关键字,也可以使用class。建议尽量使用typename。 注意:不能使用struct代替typename。
模板函数也可以定义为inline函数 template<typename T> inline T Add(const T _left, const T _right) { return (_left + _right); }
注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前。
模板是一个蓝图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产 生模板特定类型的过程称为函数模板实例化。注意:
模板被编译了两次:
- 实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号。
- 在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用
实参推演
从函数实参确定模板形参类型和值的过程称为模板实参推断,多个类型形参的实参必须完全匹配
类型形参转换
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
编译器只会执行两种转换:
- const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用.
- 数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指 针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
模板参数
函数模板有两种类型参数:模板参数和调用参数
- 模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则
- 模板形参的名字在同一模板形参列表中只能使用一次
- 所有模板形参前面必须加上class或者typename关键字修饰
注意:在函数模板的内部不能指定缺省的模板实参。非模板类型参数
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。
模板形参说明
- 模板形参表使用<>括起
- 和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
- 定义模板函数时模板形参表不能为空
- 模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
- 模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型 使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
- 模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。 但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
模板函数重载
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。 说明
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例 化为这个非模板函数.
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板 函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
- 显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用, 而且所有的模板参数都应该根据实参演绎出来。
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
模板函数特化
有时候并不总是能够写出对所有可能被实例化的类型都合适的模板,在某些情况下,通用模板定 义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情模板函数特化形式如下:
- 关键字template后面接一对空的尖括号<>
- 函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
- 函数形参表
- 函数体
template<> 返回值 函数名<Type>(参数列表) { // 函数体 }
注意:
- 在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。
- 假如少了模板形参表,只是定义了一个普通函数,该函数含有返回类型和与模板实例化相匹配的形参表。
- 特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然 后使用该特化版本的每个源文件包含该头文件。
模板类
模板类也是模板,必须以关键字template开头,后接模板形参表。
模板类格式
template<class 形参名1, class 形参名2, ...class 形参名n>
class 类名 { ... };
模板类的实例化
只要有一种不同的类型,编译器就会实例化出一个对应的类。
SeqList<int > sl1;
SeqList<double > sl2;
当定义上述两种类型的顺序表时,编译器会使用int和double分别代替模板形参,重新编写 SeqList类,后创建名为SeqList<int>和SeqList<double>的类。
注意:浮点数和类对象是不允许作为非类型模板参数的
类模板的特化
全特化 偏特化(局部特化):偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限 制所设计出来的一个特化版本。
模板的全特化和偏特化都是在已定义的模板基础之上,不能单独存在。所以分离编译会出错 解决方法:
- 在模板头文件 xxx.h 里面显示实例化->模板类的定义后面添 加 template class SeqList<int >; 一般不推荐这种方法,一方面老编译器可能不支持,另一方 面实例化依赖调用者。(不推荐)
- 将声明和定义放到一个文件 "xxx.hpp" 里面,推荐使用这种方法。
模板总结
【优点】 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。 增强了代码的灵活性。
【缺点】 模板让代码变得凌乱复杂,不易维护,编译代码时间变长。 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
类型萃取
struct __TrueType { bool Get () { return true ; } }; struct __FalseType { bool Get () { return false ; } }; template <class _Tp> struct TypeTraits { typedef __FalseType __IsPODType; }; template <> struct TypeTraits< char> { typedef __TrueType __IsPODType; }; template <> struct TypeTraits< int> { typedef __TrueType __IsPODType; }; template <class _Tp> struct TypeTraits< _Tp*> { typedef __TrueType __IsPODType; }; // // 使用参数推导的萃取处理 template <class T> void Copy (const T* src , T* dst, size_t size, __False Type ) { cout<<"__FalseType:" <<typeid( T).name ()<<endl; for (size_t i = 0; i < size ; ++i) { dst[i ] = src[ i]; } } template <class T> void Copy (const T* src , T* dst, size_t size, __TrueT ype ) { cout<<"__TrueType:" <<typeid( T).name ()<<endl; memcpy(dst , src, size*sizeof (T)); } // // 使用萃取判断类型的Get函数判断是否是 POD类型来处理 // template <class T> void Copy (const T* src , T* dst, size_t size) { cout<<"__TrueType:" <<typeid( T).name ()<<endl; if (TypeTraits <T>:: __IsPODType().Get ()) { memcpy(dst , src, size*sizeof (T)); } else { for (size_t i = 0; i < size ; ++i) { dst[i ] = src[ i]; } } } void Test1 () { string s1 [10] = {"1", "2", "3" , "4444444444444444444444444" }; string s2 [10] = {"11", "22", "33" }; Copy(s1 , s2, 10, TypeTraits <string>:: __IsPODType()); Copy(s1 , s2, 10); int a1 [10] = {1,2,3}; int a2 [10] = {0}; Copy(a1 , a2, 10, TypeTraits <int>:: __IsPODType()); Copy(a1 , a2, 10); }
Vector实现
List实现
stack实现
queue实现
更多推荐
C++模板详谈
发布评论