c++Template 的辨析
1、在c++Template中很多地方都用到了typename与class这两个关键字,而且好像可以替换,是不是这两个关键字完全一样呢?
答:class用于定义类,在模板引入c++后,最初定义模板的方法为:template<class T>,这里class关键字表明T是一个类型,后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字,它的作用同class一样表明后面的符号为一个类型,这样在定义模板的时候就可以使用下面的方式了: template<typename T>.在模板定义语法中关键字class与typename的作用完全一样。
2.类模板与模板类的概念
(1) 什么是类模板
一个类模板(也称为类属类或类生成类)允许用户为类定义一种模式,使得类中的某些数据成员、默写成员函数的参数、某些成员函数的返回值,能够取任意类型(包括系统预定义的和用户自定义的)。
如果一个类中数据成员的数据类型不能确定,或者是某个成员函数的参数或返回值的类型不能确定,就必须将此类声明为模板,它的存在不是代表一个具体的、实际的类,而是代表着一类类。
(2)类模板定义
定义一个类模板,一般有两方面的内容:
A.首先要定义类,其格式为:
template <class T>
class foo
{
……
}
foo 为类名,在类定义体中,如采用通用数据类型的成员,函数参数的前面需加上T,其中通用类型T可以作为普通成员变量的类型,还可以作为const和static成员变量以及成员函数的参数和返回类型之用。例如:
template<class T>
class Test{
private:
T n;
const T i;
static T cnt;
public:
Test():i(0){}
Test(T k);
~Test(){}
void print();
T operator+(T x);
};
B. 在类定义体外定义成员函数时,若此成员函数中有模板参数存在,则除了需要和一般类的体外定义成员函数一样的定义外,还需在函数体外进行模板声明
例如
template<class T>
void Test<T>::print(){
std::cout<<"n="<<n<<std::endl;
std::cout<<"i="<<i<<std::endl;
std::cout<<"cnt="<<cnt<<std::endl;
}
如果函数是以通用类型为返回类型,则要在函数名前的类名后缀上“<T>”。例如:
template<class T>
Test<T>::Test(T k):i(k){n=k;cnt++;}
template<class T>
T Test<T>::operator+(T x){
return n + x;
}
C. 在类定义体外初始化const成员和static成员变量的做法和普通类体外初始化const成员和static成员变量的做法基本上是一样的,唯一的区别是需再对模板进行声明,例如
template<class T>
int Test<T>::cnt=0;
template<class T>
Test<T>::Test(T k):i(k){n=k;cnt++;}
(3) 类模板的使用 类模板的使用实际上是将类模板实例化成一个具体的类,它的格式为:类名<实际的类型>。
模板类是类模板实例化后的一个产物。说个形象点的例子吧。我把类模板比作一个做饼干同的模子,而模板类就是用这个模子做出来的饼干,至于这个饼干是什么味道的就要看你自己在实例化时用的是什么材料了,你可以做巧克力饼干,也可以做豆沙饼干,这些饼干的除了材料不一样外,其他的东西都是一样的了。
3.函数模板和模板函数
(1)函数模板
函数模板可以用来创建一个通用的函数,以支持多种不同的形参,避免重载函数的函数体重复设计。它的最大特点是把函数使用的数据类型作为参数。
函数模板的声明形式为:
template<typename(或class) T>
<返回类型><函数名>(参数表)
{
函数体
}
其中,template是定义模板函数的关键字;template后面的尖括号不能省略;typename(或class)是声明数据类型参数标识符的关键字,用以说明它后面的标识符是数据类型标识符。这样,在以后定义的这个函数中,凡希望根据实参数据类型来确定数据类型的变量,都可以用数据类型参数标识符来说明,从而使这个变量可以适应不同的数据类型。例如:
template<typename(或class) T>
T fuc(T x, T y)
{
T x;
//……
}
函数模板只是声明了一个函数的描述即模板,不是一个可以直接执行的函数,只有根据实际情况用实参的数据类型代替类型参数标识符之后,才能产生真正的函数。
(2)模板函数:
模板函数的生成就是将函数模板的类型形参实例化的过程。
例如:
double d;
int a;
fuc(d,a);
则系统将用实参d的数据类型double去代替函数模板中的T生成函数:
double fuc(double x,int y)
{
double x;
//……
}
举例理解:
在程序设计当中经常会出现使用同种数据结构的不同实例的情况。例如:在一个程序中
可以使用多个队列、树、图等结构来组织数据。同种结构的不同实例,也许只在数据元素
的类型或数量上略有差异,如果对每个实例都重新定义,则非常麻烦且容易出错。那么能
否对同种类型数据结构仅定义一次呢?答案是肯定的,C++提供的类模板(Class Template
)就可以实现该功能。
一、类模板
类模板是C++提供的一种特殊机制,通过它我们可以定义一种特殊的类(称为模板类),在类
的定义中可以包含待定的类型参数,在声明类的实例时,系统会自动根据传递的类型生成
用户想要生成的类实例。下面是用C++实现的一个简单的模板类Clist的定义。
Template <class T, int I> class CList
{
public:
int SetItem(int Index, const T &Item);
int GetItem(int Index, T &Item);
private:
T Buffer;
}
在这里,T是类型参数,I是整型常量参数。T和I的实际值是在声明具体类实例时指定的。
模板类的<>号内可以包括任意个类型参数和常量参数(至少要有一个参数)。类型参数和
常量参数可以是任何合法的标准类型和用户自定义类型,包括简单类型及各种结构体。同
其他类一样,类成员函数SetItem的实现可以在类定义内完成,也可以在类CList定义处实
现:
template<class T, int I> int CList<T, I>::SetItem(int Index, const T &Item)
{
if ( (Index<0)||(Index>I-1) )
return 0; // 出错
Buffer[Index]= Item ;
return 1; // 成功
}
值得注意的是,在类定义外完成函数实现时,必须以关键字template和类模板定义中相同
的参数表(<>号内的)开头(上例为template<class T, int I>),并且范围分解操作符前的
类名后应跟上模板参数名清单(上例为CList<T, I>)。另外,与非模板类不同的是,必须将
函数实现包括在调用它的每个源文件中,使编译器能从函数实现产生代码。通常的做法是
将模板类的函数实现也放在定义该类的头文件中,这样只需在调用的源文件中包含该头文
件即可。
那么,如何使用生成特定的类实例呢?我们可以像使用其他类一样来使用模板类,不过必须
指定模板参数的值。例如采用如下声明:
CList <int, 100> IntList;
则使IntList成为CList类的实例,每次出现的T参数都换成int, 每次出现的I参数都换成
100。这样,IntList类中的Buffer就是一个长度为100的整型数组,SetItem和GetItem函数
参数是int值的引用。例:
IntList.SetItem(0, 5); //给数组第一个元素赋为整数5
模板类还可以像其他类一样可以定义构造函数和析构函数。下面我们以一种简单的数据
结构——堆栈为例,来说明如何用类模板来构造通用数据结构。
二、利用类模板实现通用堆栈结构
任何抽象数据结构在计算机中的实现,归根结底都只有两种方式:顺序存储(用数组实现)
,链式存储(用指针实现)。堆栈也不例外,按其实现方式可分为顺序栈(用数组实现)和链
栈(用指针实现)。
1. 通用顺序栈的实现
因为顺序栈中的元素在空间上连续存储,栈顶的元素位置需要注明,所以构造顺序栈的模
板类应该有这样的一些成员变量:一个待定类型和长度的数组Buffer,一个记录栈顶元素
的数组下标的整型变量top。堆栈的基本操作主要有:入栈(Push)、出栈(Pop)、置空(Se
tEmpty)、判断当前状态(IsEmpty)等,它们应用模板类的成员函数来实现。作为一个标准
的类,它还应该有自己的构造函数和析构函数。具有这些功能的模板类,就可以作为一个
通用的顺序栈来使用了。该类的定义如下:
template <class T,int SIZE> class CArrayStackTemp
{
public:
CArrayStackTemp () //缺省构造函数,构造一个空堆栈
{
top= -1;
};
~ CArrayStackTemp (){};//析构函数
void SetEmpty (); //置空堆栈
bool IsEmpty(); //判断堆栈是否为空
bool Push(T element); //入栈
bool Pop(T& element);//出栈
private:
T Buffer[SIZE];
int top;
};
与堆栈的基本操作相对应的成员函数的实现如下:
template <class T, int SIZE> void CArrayStackTemp<T, SIZE>:: SetEmpty ()
{
top= -1; //将栈顶指针赋 -1,并不实际清除数组元素
}
template <class T, int SIZE> bool CArrayStackTemp<T, SIZE>:: IsEmpty ()
{
return(top == -1);
}
template <class T, int SIZE> bool CArrayStackTemp<T, SIZE>:: Push (T element
)
{
top++;
if (top>SIZE-1)
{
top--;
return false; //堆栈已满,不能执行入栈操作
}
Buffer[top]=element;
return true;
}
template <class T, int SIZE> void CArrayStackTemp<T, SIZE>:: Pop (T& element
)
{
if (IsEmpty())
return false;
element =Buffer[top];
top--;
return true;
}
根据实际需要,还可以扩充堆栈功能。例如:加入取栈顶元素、求堆栈长度等操作,其方法
如上。
2. 通用链栈的实现
模板类中允许使用指针和定义自己的结构,这就为实现链式结构提供了保证。这里采用一
个单链表来实现堆栈,栈顶指针指向链表的第一个结点,入栈和出栈均在链表的头进行。
该模板类的定义如下:
template <class T> class CLinkStackTemp
{
public:
//类的缺省构造函数,生成一个空堆栈
CLinkStackTemp ()
{
top=NULL;
};
~ClinkStackTemp(){}; //析构函数
//定义结点结构
struct node
{
T
data; //入栈元素
node* next; //指向下一结点的指针
};
void SetEmpty(); //置空堆栈
bool IsEmpty(); //判断堆栈是否为空
bool Push(T element); //压入堆栈
bool Pop(T& element);//弹出堆栈
private:
node* top;
};
该类的成员函数实现如下:
template <class T> void CLinkStackTemp <T>::SetEmpty()
{
//释放堆栈占用的内存
node* temp;
while (top!=NULL)
{
temp=top;
top=top->next;
delete temp;
}
}
template <class T> bool CLinkStackTemp <T>::IsEmpty()
{
return (top==NULL);
}
template <class T> bool CLinkStackTemp <T>::Push(T element)
{
node* temp=new node();
if (temp ==NULL)
return false ;
temp->data=element;
temp->next=top;
top=temp;
return true;
}
template <class T> bool CLinkStackTemp <T>::Pop(T& element)
{
if ( IsEmpty())
return false;
node* q = top;
element = top->data;
top=top->next;
delete q;
return true;
}
与顺序栈的实现略有不同,链栈不必指定栈的容量,其大小可以是近似"无限"的。为了程
序的使用方便,我们同样可以加入一些增强的功能。
三、通用堆栈类的使用
通用堆栈类的使用较为简单,堆栈类的实例就是一个可以方便使用的堆栈。对堆栈的操作
都是通过类的成员函数来实现的。使用的具体步骤如下:
1. 在要使用堆栈类的程序代码的文件开头包括模板类及其成员函数的定义。
2. 类的实例化,可声明成变量,也可以声明它的指针,如:
CArrayStackTemp <int, 100> intStack; //生成一个长度为100的int型堆栈
//生成一个元素为Record型的堆栈,Record为自定义结构
CLinkStackTemp <Record>* RecordStack;
RecordStack=new CLinkStackTemp<Record>;
应注意在定义顺序栈时,必须指定栈的大小,而链栈则不必。另外在指定指针类型和执行
new操作时,必须对模板参数赋值,并且前后要一致。
3. 对堆栈进行操作,如:
intStack.Push(3); //将整数3入栈
RecordStack.SetEmpty(); //将堆栈置空
无论我们使用哪种堆栈类,对用户来讲都是透明的,操作起来并无差别
c++Template 的辨析的更多相关文章
- C++模板显式实例化,隐式实例化,特化(具体化,偏特化)辨析
最近再次看C++ PRIMER PLUS的时候看到这个部分感觉讲得很烂,前后口径不一致,所以写个辨析让自己明白的同时也希望对此不太清楚的朋友能搞懂. 总结一下,C++只有模板显式实例化(explici ...
- 为.NET Core项目定义Item Template
作为这个星球上最强大的IDE,Visual Studio不仅仅提供了很多原生的特性,更重要的是它是一个可定制的IDE,比如自定义Project Template和Item Template就是一个非常 ...
- jQuery.template.js 简单使用
之前看了一篇文章<我们为什么要尝试前后端分离>,深有同感,并有了下面的评论: 我最近也和前端同事在讨论这个问题,比如有时候前端写好页面给后端了,然后后端把这些页面拆分成很多的 views, ...
- 2000条你应知的WPF小姿势 基础篇<69-73 WPF Freeze机制和Template>
在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000ThingsYou Should Know About C# 和 2,00 ...
- tornado template
若果使用Tornado进行web开发可能会用到模板功能,页面继承,嵌套... 多页应用模板的处理多半依赖后端(SPA就可以动态加载局部视图),就算是RESTfull的API设计,也不妨碍同时提供部分模 ...
- 设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)
今天是五.四青年节,祝大家节日快乐.看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火.这两道菜大部分人都应该吃过,特别是醋溜土豆丝,作为“鲁菜”的代表作之一更是为大众所熟知 ...
- JavaScript 函数节流和函数去抖应用场景辨析
概述 也是好久没更新 源码解读,看着房价蹭蹭暴涨,心里也是五味杂陈,对未来充满恐惧和迷茫 ...(敢问一句你们上岸了吗) 言归正传,今天要介绍的是 underscore 中两个重要的方法,函数节流和函 ...
- C++泛型编程:template模板
泛型编程就是以独立于任何特定类型的方式编写代码,而模板是C++泛型编程的基础. 所谓template,是针对“一个或多个尚未明确的类型”所编写的函数或类. 使用template时,可以显示的或隐示的将 ...
- 新手入门Underscore.js 中文(template)
Underscore.js是一个很精干的库,压缩后只有4KB.它提供了几十种函数式编程的方法,弥补了标准库的不足,大大方便了javaScript的编程.MVC框架Backbone.js就将这个库作为自 ...
随机推荐
- Bars, rectangles with bases on x-axis
Usage geom_bar(mapping = NULL, data = NULL, stat = "bin", position = "stack", .. ...
- 关于Cocos2d-x运行项目时弹框崩溃的解决
想要运行工程的时候,跳出一个框说停止cantnot open the window,还提到什么GLVM之类的,这是显卡驱动出现问题,如果是远程连接电脑的话,很有可能就是用来远程连接的那台电脑的显卡驱动 ...
- 在tomcat下context.xml中配置各种数据库连接池(JNDI)
1. 首先,需要为数据源配置一个JNDI资源.我们的数据源JNDI资源应该定义在context元素中.在tomcat6版本中,context元素已经从server.xml文件中独立出来了,放在一个 ...
- Eclipse最经常使用快捷键总结
1. ctrl+shift+r:打开资源 这可能是全部快捷键组合中最省时间的了. 这组快捷键能够让你打开你的工作区中不论什么一个文件,而你仅仅须要按下文件名称或mask名中的前几个字母,比方appl ...
- Python C :ctypes库
>>> import ctypes >>> from ctypes import * >>> dir(ctypes) ['ARRAY', 'Arg ...
- 【Java面试题】12 内部类可以引用它的包含类的成员吗?有没有什么限制?
完全可以.如果不是静态内部类,那没有什么限制! 如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员,例如,下面的代码: class Ou ...
- js 动态设置 option 的selected 选项
思路:通过for循环判断每个选项,一旦满足条件则设置其selected属性为true即可,关键代码: var obj = document.getElementById(select_id); for ...
- Oracle查询优化--单表查询
--查询所有 select * from emp; select * from emp where comm is null; --错误表达 --select * from emp where com ...
- c++ 用构造函数
1.c++ 用构造函数创建一个对象时,对象里的属性应该是全新的不存在把之前的属性留下的问题.之所以debug里会存在上一个变量的信息,应该是debug里的一个bug,与程序无光.
- ubuntu设置中文拼音输入法
转载 http://www.cnblogs.com/zhj5chengfeng/archive/2013/06/23/3150620.html