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

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。

      

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



注:模板被编译了两次:

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

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

 

实参推演:

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

类型形参转换:

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

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

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

 

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

 

Eg:

template<typename T>

void FunTest1(const T* t)

{

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

}

template<typename T>

void FunTest2(const T& t)

{

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

}

template<typename T>

void FunTest3(T t1, T t2)

{

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

}

 

int Add(int a, 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;

}

 

 

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

 

(1)   模板形参:

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



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

Template<typename T, typenameT>

Void FunTest(T t1, T t2)

{};

 

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

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

Template<typename T>

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

{};

非模板类型参数

 

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

 

 

              注:

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

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

template<typenameT, typename V …….>

 

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

 

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

 

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

 

6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为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:比较两个字符串的大小



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


注意:

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

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

 

三、   模板类

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;

}

 

 

 

2、   模板类的实例化

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

SeqList<int > sl1;

SeqList<double > sl2;

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

 

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

#include "List.h"

//底层使用List 容器

 

template <class T, class container = list<T>> //模板参数

class Queue

{

public:

  void push(const T& 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 <class T, template<classclass container = List> //使用模板的模板参数

class Queue

{

public:

  void push(const T& 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;//使用模板的模板参数构造不同类型对象

}

 

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

template <typename T, 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 <typename T, size_t MAX_SIZE>

Array <T,MAX_SIZE>::Array()

  : _size(0)

  {}

 

void Test()

{

  Array<int> a1;

  Array<int , 20> a2;

}

(4)类模板的特化:

//顺序表类的部分实现

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>:: SeqList()" <<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);

}

 

//偏特化(部分特化)

 

template <typename T1, typename T2>

class Data

{

public :

    Data();

};

template <typename T1, typename T2>

Data<T1 , T2>::Data()

{

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

}

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

template <typename T1>

class Data <T1, int>

{

public :

    Data();

 

};

template <typename T1>

Data<T1 , int>::Data()

{

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

}

 

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

 

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

template <typename T1, typename T2>

class Data <T1*, T2*>

{

public :

    Data();

 

};

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

 

};

template <typename T1, typename T2>

Data<T1 &, T2&>:: Data(const T1& 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);

}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459463.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Studio 1.1的安装和遇到的坑

Google的Android Studio 出1.0稳定版本也有很久的时间了&#xff0c;一直喜欢Jetbrains公司的IDE&#xff0c;不同语言的IDE操作习惯都比较统一。 而Android Studio 是基于IntelliJ IDEA的社区版开发的 &#xff0c;怎么也要尝尝鲜才行。 今天安装了下&#xff0c;被几个小坑卡…

BestCoder Round #39 解题报告

现场只做出前三题w 不过不管怎样这既是第一次认真打BC 又是第一次体验用在线编译器调代码 订正最后一题花了今天一整个下午&#xff08;呜呜 收获还是比较大的^_^ Delete wld有n个数(a1,a2,...,an)&#xff0c;他希望进行k次删除一个数的操作&#xff0c;使得最后剩下的n−k个数…

linux :vim 实现命令行下输出进度条

1、 进度条原理&#xff1a; 进度条的的动态增长是利用人的视觉短暂停留效果的&#xff0c;不断从输出缓冲区刷新出相同的内容&#xff0c;在肉眼看来进度条在不断的增长。 在显示器上先输出[# ][%1] 刷新一次之后&#xff0c; …

***jquery选择器 之 获取父级元素、同级元素、子元素

一、获取父级元素1、 parent([expr]): 获取指定元素的所有父级元素 <div id"par_div"><a id"href_fir" href"#">href_fir</a><a id"href_sec" href"#">href_sec</a><a id"href_thr&q…

Sql Server字符串函数

字符串函数用于对字符和二进制字符进行各种操作 1.ASCII()函数 ASCII(character_expression)函数用于返回字符串表达式中最左侧的ASCII代码值。参数character_expression必须是一个char或varchar类型的字符串表达式。 eg: select ASCII(s),ASCII(sql),ASCII(1);执行结果如图:字…

linux 编辑器vim配置

1、 基本配置 对vim进行配置的目的&#xff1a; 进行vim配置&#xff0c;可以让我们在后续敲代码更加方便。按F5可以直接编译并执行C、C代码以及执行shell脚本&#xff1b;按“F8”可进行C、C代码的调试&#xff1b;“Ctrl A”为全选并复制快捷键&#xff0c;方便复…

centos 7上ambari安装试用

2019独角兽企业重金招聘Python工程师标准>>> 1、有三台centos7&#xff0c;名字分别是ws11.localdomain, ws12.localdomain, ws13.localdomain。配置epel源(安装epel-release)。 2、配置root用户ssh无密码访问。 3、安装ntp对时服务。 4、关闭防火墙。centos7下使…

进程管理—进程描述符(task_struct)

本文章转载自&#xff1a;http://blog.csdn.net/qq_26768741/article/details/54348586?locationNum4&fps1 前言 当把一个程序加载到内存当中&#xff0c;此时&#xff0c;这个时候就有了进程&#xff0c;关于进程&#xff0c;有一个相关的叫做进程控制块&#xff08;PCB&…

如何使用emacs编写c语言程序,并编译运行

vi和emacs被分别被称为编辑器之神和神之编辑器。vi的入门精通都很难&#xff0c;emacs入门容易&#xff0c;精通难&#xff1b;vi使用起来不停地切换模式&#xff0c;而emacs则不停地ctrl&#xff0c;meta等组合键。因此&#xff0c;高德纳大师说操作Emacs&#xff0c;就像弹奏…

操作系统中常见的进程调度算法

一、调度与调度算法 调度&#xff1a;操作系统管理了系统的有限资源&#xff0c;当有多个进程&#xff08;或多个进程发出的请求&#xff09;要使用这些资源时&#xff0c;因为资源的有限性&#xff0c;必须按照一定的原则选择进程&#xff08;请求&#xff09;来占用资源。这…

粘滞位 File文件内容

t权限&#xff08;粘滞位)&#xff1a; 是‘不可删除’权限&#xff0c;就是说即使某用户拥有这个文件的rwx权限&#xff0c;可以随意修改文件内容&#xff0c;但是就是不能删除&#xff0c;甚至不能修改文件名&#xff0c;只有root才行。t权限也可以直接用 chmod ot/at fil…

QQuickRenderControl

2019独角兽企业重金招聘Python工程师标准>>> http://doc.qt.io/qt-5/qquickrendercontrol.html http://translate.google.com.hk/translate?hlzh-CN&slauto&tlen&uhttp%3A%2F%2Fhabrahabr.ru%2Fpost%2F247477%2F http://www.kdab.com/overview-qt3d-2-…

线程的控制(创建、等待、终止)、分离线程

一、线程控制 1、线程&#xff1a;线程是资源调度的基本单位&#xff0c;线程是进程内部的一个执行流&#xff0c;在进程的地址空间内运行。在Linux 下没有真正意义上的线程&#xff0c;线程是用进程模拟的&#xff0c;又被称为轻量级进程。 2、由于同⼀一进程的多个线程共享同…

从netty-example分析Netty组件

分析netty从源码开始 准备工作&#xff1a; 1.下载源代码&#xff1a;https://github.com/netty/netty.git 我下载的版本为4.1 2. eclipse导入maven工程。 netty提供了一个netty-example工程&#xff0c; 分类如下&#xff1a; Fundamental Echo ‐ the very basic client and …

cep

cep posted on 2015-12-16 17:03 秦瑞It行程实录 阅读(...) 评论(...) 编辑 收藏 转载于:https://www.cnblogs.com/ruiy/p/5051673.html

RHCS集群原理概述

一、 什么是RHCSRHCS是Red Hat Cluster Suite的缩写&#xff0c;也就是红帽集群套件&#xff0c;RHCS是一个能够提供高可用性、高可靠性、负载均衡、存储共享且经济廉价的集群工具集合&#xff0c;它将集群系统中三大集群架构融合一体&#xff0c;可以给web应用、数据库应用等提…

Linux学习笔记11——文件I/O之二

一、文件共享 内核使用三种数据结构表示打开的文件&#xff0c;它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。 1、每个进程在进程表中都有一个记录项&#xff0c;记录项中包含有一张打开文件描述表  2、内核为所有打开文件维持一张文件表  3、每…

Git Proxy开关

2019独角兽企业重金招聘Python工程师标准>>> 这个是配合ShadowSocks使用的&#xff0c;在~/.bash_aliases或者~/.bash_profile中设置以下代码&#xff1a; #git proxy enable alias gitpe"git config --global http.proxy socks5://127.0.0.1:1080;git config …

平衡二叉查找树插入节点操作( AVLTree ):旋转、调整平衡

AVL树的插入 在向一棵本来高度平衡的AVL树中插入一个新节点时&#xff0c;如果树中某个结点的平衡因子的绝对值 > 1&#xff0c;则出现了不平衡。设新插入结点为P&#xff0c;从结点P到根节点的路径上&#xff0c;每个结点为根的子树的高度都可能增加1&#xff0c;因此在每…

Fork/Join框架介绍

转http://www.infoq.com/cn/articles/fork-join-introduction/ 1. 什么是Fork/Join框架 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架&#xff0c; 是一个把大任务分割成若干个小任务&#xff0c;最终汇总每个小任务结果后得到大任务结果的框架。 我们再通过Fork和…