函数模板与模板函数及模板类与模板的特化

函数模板( Function templates)

      * 模板(Templates)使得我们可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载。这在一定程度上实现了宏(macro)的作用。它们的原型定义可以是下面两种中的任何一个:

template <class identifier>   function_declaration;
template <typename identifier>
  function_declaration;

上面两种原型定义的不同之处在关键字class 或 typename的使用。它们实际是完全等价的,因为两种表达的意思和执行都一模一样

如何编写一个通用加法函数?
  • 使用函数重载,针对每个所需相同行为的不同类型重新实现它
<pre class="cpp" name="code">int Add(const int &_iLeft, const int &_iRight)
{
     return (_iLeft + _iRight);
}

float Add(const float &_fLeft, const float &_fRight)
{
     return (_fLeft + _fRight);
}
 
  
 
 
  
【缺点】
  1、只要有新类型出现,就要重新添加对应函数。
  2、除类型外,所有函数的函数体都相同,代码的复用率不高
  3、如果函数只是返回值类型不同,函数重载不能解决
  4、一个方法有问题,所有的方法都有问题,不好维护。
  • 使用公共基类,将通用的代码放在公共的基础类里面
 【缺点】
  1、借助公共基类来编写通用代码,将失去类型检查的优点;
  2、对于以后实现的许多类,都必须继承自某个特定的基类,代码维护更加困难。
  • 使用特殊的预处理程
#define  ADD(a, b) ((a) + (b))

  • 泛型编程
泛型编程:编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础

  • 函数模板
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

模板函数的格式
template<typename Param1, typename Param2,...,class Paramn>
返回值类型  函数名(参数列表)
{
    ...
}


typename是用来定义模板参数关键字,也可以使用class。建议尽量使用typename。
注意:不能使用struct代替typename。

模板函数也可以定义为inline函数
<pre class="cpp" name="code">template<typename T>
inline T Add(const T _left, const T _right)
{
     return (_left + _right);
}
 
      
 
     
注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前
模板是一个蓝图,它本身不是类或者函数, 编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
注意:模板被编译了两次:
  • 实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号
  • 在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用
【实参推演】
从函数实参确定模板形参类型和值的过程称为模板实参推断
多个类型形参的实参必须完全匹配
【类型形参转换】
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
编译器只会执行两种转换:
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。 数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
  • 模板参数
函数模板有两种类型参数:模板参数和调用参数
模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则
模板形参的名字在同一模板形参列表中只能使用一次
所有模板形参前面必须加上class或者typename关键字修饰
注意:在函数模板的内部不能指定缺省的模板实参。
下面模板函数声明有问题吗?
<pre class="cpp" name="code">template<class T, U, typename V>
void f1(T, U, V);

template<class T>
T f2(int &T);

template<class T>
T f3 (T, T);

typedef int TYPENAME;
template<typename TYPENAME>
TYPENAME f4(TYPENAME);
 
   
【非模板类型参数】
  非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。

例如数组长度

类型等价性
   const int iByteCnt = 9;
   int b[iByteCnt+1];
   int a[10];

   FunTest(a); // FunTest<int, 10>  两个数组等价
   FunTest(b); // FunTest<int, 10>  编译器不会合成新的函数

模板形参说明
1、模板形参表使用<>括起来
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、模板形参表不能为空
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后
5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型
   使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。
   但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。

  • 模板函数重载
<pre class="cpp" name="code">int Max(const int& left, const int & right)
{
     return left>right? left:right;
}

template<typename T>
T Max(const T& left, const T& right)
{
         return left>right? left:right;
}

template<typename T>
T Max(const T& a, const T& b, const T& c)
{
    return Max(Max(a, b), c);
};

int main()
{
    Max(10, 20, 30);
    Max<>(10, 20);
    Max(10, 20);
    Max(10, 20.12);
    Max<int>(10.0, 20.0);
    Max(10.0, 20.0);
    return 0;
}
 
     

注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。

【说明】
  1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例 
      化为这个非模板函数
   2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板 
      函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 
      那么将选择模板。
   3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,   
      而且所有的模板参数都应该根据实参演绎出来。
   4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

问题:上面的模板还是可以比较两个字符串吗?

【模板函数特化】
 有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下,通用模板定义对于某个 类型可能是完全错误的,或者不能编译,或者做一些错误的事情

可以下面这样来定义

模板函数特化形式如下:
1、关键字template后面接一对空的尖括号<>
2、再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3、函数形参表
4、函数体
template<>
返回值 函数名<Type>(参数列表)
{
      // 函数体
}

特化的声明必须与特定的模板相匹配,否则

假如少了模板形参表?
<span style="color:#993399;">int compare(int v1, int v2)
{
           return 0;
}
</span>

只是定义了一个普通函数,该函数含有返回类型和可与模板实例化相匹配的形参表。

注意:在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,
      如果不匹配,编译器将为实参模板定义中实例化一个实例。


注意:特化不能出现在模板实例的调用之后应该在头文件中包含模板特化的声明,然
      后使用该特化版本的每个源文件包含该头文件。

  • 模板类
【普通顺序表】
typedef int DataType;
//typedef char DataType;
class SeqList
{
private :     
     DataType* _data ;
     int _size ;
     int _capacity ;
};

模板类也是模板,必须以关键字template开头,后接模板形参表。

【模板类格式】
<pre class="cpp" name="code">template<class 形参名1, class 形参名2, ...class 形参名n>  
 class 类名
 { ... };

template<typename T>
class SeqList
{
private :
     T* _data ;
     int _size ;
     int _capacity ;
};

// 以模板方式实现动态顺序表
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])
{}

template <typename T>
SeqList <T>::~ SeqList()
{
     delete [] _data ;
}

void test1 ()
{
     SeqList<int > sl1;
     SeqList<double > sl2;
}
 
         

【模板类的实例化】
只要有一种不同的类型,编译器 就会实例化出一个对应的类。
 SeqList<int > sl1;
 SeqList<double > sl2;
当定义上述两种类型的顺序表时,编译器会使用int和double分别代替模板形参,重新编写
SeqList类,最后创建名为SeqList<int>和SeqList<double>的类。

  • 模板参数--实现容器适配器
<pre class="cpp" name="code">template <typename T>
class SeqList
{
private :
     int _size ;
     int _capacity ;
     T* _data ;
};

// template <class T, class Container>
template <class T, class Container = SeqList<T> > // 缺省参数
class Stack
{
public :
     void Push (const T& x);
     void Pop ();
     const T& Top();
     bool Empty ();

private :
     Container _con ;
};

void Test()
{
     Stack<int> s1;
     Stack<int , SeqList<int>> s2 ;

     // 思考下面这种使用场景会怎样?
     Stack<int , SeqList<char>> s3 ;
}
 
         
        
  • 模板的模板参数--容器适配器
<pre class="cpp" name="code">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;
}
 
         
        

  • 非类型的类模板参数
// 静态顺序表
<pre class="cpp" name="code">//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;
}
 
         
注意:浮点数和类对象是不允许作为非类型模板参数的
<pre class="cpp" name="code">//template<class T, string name>
template <class T, double MaxSize>
class Test
{
private :
     double _value ;
};
 
           
类的模板特化:
全特化
<pre class="cpp" name="code">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);
}
 
          
         
  • 偏特化(局部特化)
<pre class="cpp" name="code">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:下面的例子可以看出,偏特化并不仅仅是指特殊部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本
<pre class="cpp" name="code">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);
}
 
        

模板的全特化和偏特化都是在已定义的模板基础之上,不能单独存在。

  • 模板的分离编译

解决办法:
  1. 在模板头文件 xxx.h 里面显示实例化->模板类的定义后面添加 template class SeqList<int >; 一般不推荐这种方法,一方面老编译器可能不支持,另一方面实例化依赖调用者。(不推荐)
  2. 将声明和定义放到一个文件 "xxx.hpp" 里面,推荐使用这种方法

【分离编译的扩展阅读】

  • 模板总结
【优点】
  模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
  增强了代码的灵活性。

【缺点】
  模板让代码变得凌乱复杂,不易维护,编译代码时间变长。
  出现模板编译错误时,错误信息非常凌乱,不易定位错误。

模板还有一些高深晦涩的玩法,在实际运中的大多数的场景下上面的特性已经够我们玩了,所以不用去纠结模板的复杂莫测的玩法。真有兴趣深入学习的童鞋,或者有要用到一些复杂特性时,可以通过《C++ Templates 中文版》学习。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值