C++模板剖析:函数模板、类模板解析

时间:2022-03-21 19:02:53

C++中关于模板&泛型编程问题:

 

问题引入:何编写一个通用加法函数?

 

(1)使用函数重载,针对每个所需相同行为的不同类型重新实现它

int Add(const int &_iLeft, const int&_iRight)

{

return (_iLeft +_iRight);

}

float Add(const float &_fLeft, constfloat &_fRight)

{

return (_fLeft +_fRight);

}

【缺点】

1、只要有新类型出现,就要重新添加对应函数。

2、除类型外,所有函数的函数体都相同,代码的复用率不高

3、如果函数只是返回值类型不同,函数重载不能解决

4、一个方法有问题,所有的方法都有问题,不好维护。

 

(2)使用公共基类,将通用的代码放在公共的基础类里面

 

【缺点】

1、借助公共基类来编写通用代码,将失去类型检查的优点;

2、对于以后实现的许多类,都必须继承自某个特定的基类,代码维护更加困难。

 

(3)使用特殊的预处理程序

#define ADD(a, b) ((a) + (b))

【缺点】

不是函数,不进行参数类型检测,安全性不高

 

综上所述的问题,我们需要引入泛型编程,即为需要的函数或者类编写一个模板,在实用的时候实例化即可。那么,什么是泛型编程?什么是模板?

 

一、   泛型编程

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

二、   函数模板

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

模板函数定义的格式:template<typename T1, teypename T2, ……….typename Tn>

函数返回值 函数名(参数列表)

{

       . . . . . .

}

 

Eg:

       template<typename T>

T Add( T left, T right )

{

       return left+right;

}

       template和typename 为关键字,T为模板形参的名字,可随意命名。

 

typename是用来定义模板参数关键字,也可以使用class。建议尽量使typename。

      

       实例化:模板是一个蓝图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化

C++模板剖析:函数模板、类模板解析


注:模板被编译了两次:

① 实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号

②在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用

 

实参推演:

从函数实参确定模板形参类型和值的过程称为模板实参推断,多个类型形参的实参必须完全匹配

类型形参转换:

一般不会转换实参以匹配已有的实例化,相反会产生新的实例。

编译器只会执行两种转换:

1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用

 

2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。

 

Eg:

template<typenameT>

void FunTest1(constT* t)

{

  cout<<"FunTest1();"<<*t<<endl;

}

template<typenameT>

void FunTest2(constT& t)

{

   cout<<"FunTest2();"<<t<<endl;

}

template<typenameT>

void FunTest3(Tt1, T t2)

{

  cout<<"FunTest3()"<<endl;

}

 

int Add(inta, int b)

{

  return a+b;

}

int main()

{

  int A = 10;

  int* pA = &A;

  FunTest1(pA);

 

  int b = 20;

  int& pB = b;

  FunTest2(pB);

 

  int array1[10];

  int array2[20];

 

  FunTest3(array1, array2);

  FunTest3(Add,Add);

  system("pause");

  return 0;

}

 

 

       模板参数:函数模板有两种类型参数:模板参数和调用参数。

 C++模板剖析:函数模板、类模板解析

(1)   模板形参:

a、模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则。


C++模板剖析:函数模板、类模板解析

b、模板形参的名字在同一模板形参列表中只能使用一次。

Template<typename T, typenameT>

Void FunTest(T t1, T t2)

{};

 

c、 所有模板形参前面必须加上class或者typename关键字修饰,并且两个关键字可以混用。

d、在函数模板的内部不能指定缺省的模板实参。

Template<typename T>

T Add(T = int , T)   //编译出错。

{};

非模板类型参数

 

非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。

C++模板剖析:函数模板、类模板解析 

 

              注:

1、模板形参表使用<>括起来。

2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同。

template<typenameT, typename V …….>

 

3、模板形参表不能为空(模板特化的时候,模板参数为空)。

 

4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后

 

5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换。

 

6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。

 

       模板函数重载:

 C++模板剖析:函数模板、类模板解析

注:

1、函数的所有重载版本的声明都应该位于该函数被调用位置之前

2、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

3、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

4、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。

5、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

 

新的问题引入:

string s1 = "addfhgj";

    string s2 = "addfghjkl";

Max(s1,s2); //未能从“const std::string”为“const std::move_iterator<_RanIt> &”推导 模板 参数。

故模板有一些特殊的情况不能处理,就需要引入模板的特化,什么是模板的特化?

 

       模板函数特化:

 

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

 

Eg:比较两个字符串的大小


C++模板剖析:函数模板、类模板解析

       调试之后发现比较的是字符串地址的大小而不是字符串的大小。因此需要对模板函数进行特化以处理特殊的情况。这就需要对模板类的特殊情况进行处理-------模板特化:

C++模板剖析:函数模板、类模板解析

注意:

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

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

 

三、   模板类

1、      模板类的定义格式:

 

template<class 形参名1, class 形参名2, ...class 形参名n>

class 类名

{ ... };

 

// 使用模板方式实现动态顺序表

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 Test()

{

         SeqList<int>s1;

         SeqList<double>s2;

         SeqList<char>s3;

}

 

 C++模板剖析:函数模板、类模板解析

 

2、   模板类的实例化

只要有一种不同的类型,编译器就会实例化出一个对应的类。

SeqList<int > sl1;

SeqList<double > sl2;

当定义上述两种类型的顺序表时,编译器会使用int和double分别代替模板形参,重新编写SeqList类,最后创建名为SeqList<int>和SeqList<double>的类。

 

(1)模板参数实现容器适配器。

#include "List.h"

//底层使用List容器

 

template <classT, classcontainer = list<T>>//模板参数

class Queue

{

public:

  void push(constT& x)

  {

     return _con.PushBack(x);

  }

  void pop()

  {

     return _con.PopFront();

  }

  const T& GetHead()

  {

     return _con.Front();

  }

  const T& GetTail()

  {

     return _con.Back();

  }

  bool IsEmpty()

  {

     return _con.Empty();

  }

private:

  container _con;

};

void Test2()

{

  Queue<int> q1;//使用缺省的模板参数构造对象

  Queue<int, List<int>> q2;//使用模板参数构造对象

}

(2)模板的模板参数实现容器适配器。

    template <classT, template<classclass container = List>//使用模板的模板参数

class Queue

{

public:

  void push(constT& x)

  {

     return _con.PushBack(x);

  }

  void pop()

  {

     return _con.Pop();

  }

  const T& GetHead()

  {

     return _con.Front();

  }

  const T& GetTail()

  {

     return _con.Back();

  }

  bool IsEmpty()

  {

     return _con.Empty();

  }

private:

  container<T>  _con;

};

void Test1()

{

Queue<int>q1;//使用缺省的模板类的模板参数构造对象

  Queue<int,List> q2;//使用模板的模板参数构造不同类型对象

}

C++模板剖析:函数模板、类模板解析 

(3)非类型的类模板参数

template <typenameT, size_t MAX_SIZE = 10>//带缺省模板参数

//template<typename T, double MAX_SIZE = 10.0> //“double”: 非类型模板参数“MAX_SIZE”的类型非法

 

class Array

{

public :

  Array();

private :

  T _array [MAX_SIZE];

int _size ;

};

template <typenameT, size_t MAX_SIZE>

Array <T,MAX_SIZE>::Array()

  : _size(0)

  {}

 

void Test()

{

  Array<int> a1;

  Array<int , 20> a2;

}

(4)类模板的特化:

//顺序表类的部分实现

template <typenameT>

class SeqList

{

public :

    SeqList();

    ~ SeqList();

private :

    int _size ;

    int _capacity ;

    T* _data ;

};

template<typenameT>

SeqList <T>:: SeqList()

    : _size(0)

    , _capacity(10)

    , _data(new T[ _capacity])

    {

        cout<<"SeqList<T>:: SeqList()" <<endl;

    }

template<typenameT>

SeqList <T>::~ SeqList()

{

    delete[] _data ;

}

 

//全特化:

template <>

class SeqList <int>

{

public :

    SeqList(int capacity);

    ~ SeqList();

private :

    int _size ;

    int _capacity ;

    int* _data ;

};

// 特化后定义成员函数、成员函数不再需要模板形参列表

SeqList <int>:: SeqList(intcapacity)

    : _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);

}

 

//偏特化(部分特化)

 

template <typenameT1, typenameT2>

class Data

{

public :

    Data();

};

template <typenameT1, typenameT2>

Data<T1 ,T2>::Data()

{

    cout<<"Data<T1,T2>"<<endl;

}

// 局部特化第二个参数为某个具体的类型,如int、 double 等

template <typenameT1>

class Data <T1, int>

{

public :

    Data();

 

};

template <typenameT1>

Data<T1 ,int>::Data()

{

cout<<"Data<T1,int>"<<endl;

}

 

下面的例子可以看出,偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

 

// 局部特化两个参数为指针类型

template <typenameT1, typenameT2>

class Data <T1*, T2*>

{

public :

    Data();

 

};

template <typenameT1, typenameT2>

Data<T1 *,T2*>:: Data()

{

    cout<<"Data<T1*,T2*>"<<endl;

}

// 局部特化两个参数为引用

template <typenameT1, typenameT2>

class Data <T1&, T2&>

{

public :

    Data(const T1& d1, const T2& d2);

 

};

template <typenameT1, typenameT2>

Data<T1 &,T2&>:: Data(constT1& d1,const T2&d2)

    {

       cout<<"Data<T1&,T2&>"<<endl;

    }

void Test()

{

    Data<double ,int> d1;

    Data<int ,double> d2;

    Data<int *,int*> d3;

    Data<int&,int&> d4(1, 2);

}

C++模板剖析:函数模板、类模板解析