网上我最喜欢的技术文章是类似某何君所著“CVS快速入门”或者“UML reference card”之类,简短扼要,可以非常快的领着你进入一个新天地。而对于比较长的文章我通常是将其保存到硬盘上,然后准备着“以后有时间”的时候再看,但它们通常的命运都是“闲坐说玄宗”,直到某一天在整理硬盘时将它们以“不知所云”入罪,一并删除。

这篇小文主要是针对刚刚接触模板概念的读者,希望能帮助读者学习模板的使用。为了避免本文也在诸公的硬盘上遭逢厄运,我决定写的短些。“以后有时间”的时候再补充些内容。

TOC

1. 简介

2. 语法

3. 使用技巧

3.1 语法检查

3.2 继承

3.3 静态成员

3.4 模板类的运用

4. 参考资料

1. 简介

模板是C++在90年代引进的一个新概念,原本是为了对容器类(container classes)的支持[1],但是现在模板产生的效果已经远非当初所能想象。

简单的讲,模板就是一种参数化(parameterized)的类或函数,也就是类的形态(成员、方法、布局等)或者函数的形态(参数、返回值等)可以被参数改变。更加神奇的是这里所说的参数,不光是我们传统函数中所说的数值形式的参数,还可以是一种类型(实际上稍微有一些了解的人,更多的会注意到使用类型作为参数,而往往忽略使用数值作为参数的情况)。

举个常用的例子来解释也许模板就从你脑袋里的一个模糊的概念变成活生生的代码了:

在C语言中,如果我们要比较两个数的大小,常常会定义两个宏:

#define min(a,b) ((a)>(b)?(b) a))

#define max(a,b) ((a)>(b)?(a)b))

这样你就可以在代码中:

return min(10, 4);

或者:

return min(5.3, 18.6);

这两个宏非常好用,但是在C++中,它们并不像在C中那样受欢迎。宏因为没有类型检查以及天生的不安全(例如如果代码写为min(a++, b--);则显然结果非你所愿),在C++中被inline函数替代。但是随着你将min/max改为函数,你立刻就会发现这个函数的局限性 —— 它不能处理你指定的类型以外的其它类型。例如你的min()声明为:

int min(int a, int b);

则它显然不能处理float类型的参数,但是原来的宏却可以很好的工作!你随后大概会想到函数重载,通过重载不同类型的min()函数,你仍然可以使大部分代码正常工作。实际上,C++对于这类可以抽象的算法,提供了更好的办法,就是模板:

template <class T> const T & min(const T & t1, const T & t2) {

return t1>t2?t2:t1;

}

这是一个模板函数的例子。在有了模板之后,你就又自由了,可以像原来在C语言中使用你的min宏一样来使用这个模板,例如:

return min(10,4);

也可以:

return min(5.3, 18.6)

你发现了么?你获得了一个类型安全的、而又可以支持任意类型的min函数,它是否比min宏好呢?

当然上面这个例子只涉及了模板的一个方面,模板的作用远不只是用来替代宏。实际上,模板是泛化编程(Generic Programming)的基础。所谓的泛化编程,就是对抽象的算法的编程,泛化是指可以广泛的适用于不同的数据类型。例如我们上面提到的min算法。

2. 语法

你千万不要以为我真的要讲模板的语法,那太难为我了,我只是要说一下如何声明一个模板,如何定义一个模板以及常见的语法方面的问题。

template<> 是模板的标志,在<>中,是模板的参数部分。参数可以是类型,也可以是数值。例如:

template<class T, T t>

class Temp{

public:

...

void print() { cout << t << endl; }

private:

T t_;

};

在这个声明中,第一个参数是一个类型,第二个参数是一个数值。这里的数值,必须是一个常量。例如针对上面的声明:

Temp<int, 10> temp; // 合法

int i = 10;

Temp<int, i> temp; // 不合法

const int j = 10;

Temp<int, j> temp; // 合法

参数也可以有默认值:

template<class T, class C=char> ...

默认值的规则与函数的默认值一样,如果一个参数有默认值,则其后的每个参数都必须有默认值。

参数的名字在整个模板的作用域内有效,类型参数可以作为作用域内变量的类型(例如上例中的T t_),数值型参数可以参与计算,就象使用一个普通常数一样(例如上例中的cout << t << endl)。

模板有个值得注意的地方,就是它的声明方式。以前我一直认为模板的方法全部都是隐含为inline的,即使你没有将其声明为inline并将函数体放到了类声明以外。这是模板的声明方式给我的错觉,实际上并非如此。我们先来看看它的声明,一个作为接口出现在头文件中的模板类,其所有方法也都必须与类声明出现在一起。用通俗的话来说,就是模板类的函数体也必须出现在头文件中(当然如果这个模板只被一个C++程序文件使用,它当然也可以放在.cc中,但同样要求类声明与函数体必须出现在一起)。这种要求与inline的要求一样,因此我一度认为它们隐含都是inline的。但是在Thinking In C++[2]中,明确的提到了模板的non-inline function,就让我不得不改变自己的想法了。看来正确的理解应该是:与普通类一样,声明为inline的,或者虽然没有声明为inline但是函数体在类声明中的才是inline函数。

澄清了inline的问题候,我们再回头来看那些我们写的包含了模板类的丑陋的头文件,由于上面提到的语法要求,头文件中除了类接口之外,到处充斥着实现代码,对用户来说,十分的不可读。为了能像传统头文件一样,让用户尽量只看到接口,而不用看到实现方法,一般会将所有的方法实现部分,放在一个后缀为.i或者.inl的文件中,然后在模板类的头文件中包含这个.i或者.inl文件。例如:

// start of temp.h

template<class T> class Temp{

public:

void print();

};

#include "temp.inl"

// end of temp.h

// start of temp.inl

template<class T> void Temp<T>::print() {

...

}

// end of temp.inl

通过这样的变通,即满足了语法的要求,也让头文件更加易读。模板函数也是一样。

普通的类中,也可以有模板方法,例如:

class A{

public:

template<class T> void print(const T& t) { ...}

void dummy();

};

对于模板方法的要求与模板类的方法一样,也需要与类声明出现在一起。而这个类的其它方法,例如dummy(),则没有这样的要求。

3. 使用技巧

知道了上面所说的简单语法后,基本上就可以写出自己的模板了。但是在使用的时候还是有些技巧。

3.1 语法检查

对模板的语法检查有一部分被延迟到使用时刻(类被定义[3],或者函数被调用),而不是像普通的类或者函数在被编译器读到的时候就会进行语法检查。因此,如果一个模板没有被使用,则即使它包含了语法的错误,也会被编译器忽略,这是语法检查问题的第一个方面,这不常遇到,因为你写了一个模板就是为了使用它的,一般不会放在那里不用。与语法检查相关的另一个问题是你可以在模板中做一些假设。例如:

template<class T> class Temp{

public:

Temp(const T & t): t_(t) {}

void print() { t.print();}

private:

T t_;

};

在这个模板中,我假设了T这个类型是一个类,并且有一个print()方法(t.print())。我们在简介中的min模板中其实也作了同样的假设,即假设T重载了'>'操作符。

因为语法检查被延迟,编译器看到这个模板的时候,并不去关心T这个类型是否有print()方法,这些假设在模板被使用的时候才被编译器检查。只要定义中给出的类型满足假设,就可以通过编译。

之所以说“有一部分”语法检查被延迟,是因为有些基本的语法还是被编译器立即检查的。只有那些与模板参数相关的检查才会被推迟。如果你没有写class结束后的分号,编译器不会放过你的。

3.2 继承

模板类可以与普通的类一样有基类,也同样可以有派生类。它的基类和派生类既可以是模板类,也可以不是模板类。所有与继承相关的特点模板类也都具备。但仍然有一些值得注意的地方。

假设有如下类关系:

template<class T> class A{ ... };

|

+-- A<int> aint;

|

+-- A<double> adouble;

则aint和adouble并非A的派生类,甚至可以说根本不存在A这个类,只有A<int>和A<doubl>这两个类。这两个类没有共同的基类,因此不能通过类A来实现多态。如果希望对这两个类实现多态,正确的类层次应该是:

class Abase {...};

template<class T> class A: public Abase {...};

|

+-- A<int> aint;

|

+-- A<double> adouble;

也就是说,在模板类之上增加一个抽象的基类,注意,这个抽象基类是一个普通类,而非模板。

再来看下面的类关系:

template<int i> class A{...};

|

+-- A<10> a10;

|

+-- A<5> a5;

在这个情况下,模板参数是一个数值,而不是一个类型。尽管如此,a10和a5仍然没有共同基类。这与用类型作模板参数是一样的。

3.3 静态成员

与上面例子类似:

template<class T> class A{ static char a_; };

|

+-- A<int> aint1, aint2;

|

+-- A<double> adouble1, adouble2;

这里模板A中增加了一个静态成员,那么要注意的是,对于aint1和adouble1,它们并没有一个共同的静态成员。而aint1与aint2有一个共同的静态成员(对adouble1和adouble2也一样)。

这个问题实际上与继承里面讲到的问题是一回事,关键要认识到aint与adouble分别是两个不同类的实例,而不是一个类的两个实例。认识到这一点后,很多类似问题都可以想通了。

3.4 模板类的运用

模板与类继承都可以让代码重用,都是对具体问题的抽象过程。但是它们抽象的侧重点不同,模板侧重于对于算法的抽象,也就是说如果你在解决一个问题的时候,需要固定的step1 step2...,那么大概就可以抽象为模板。而如果一个问题域中有很多相同的操作,但是这些操作并不能组成一个固定的序列,大概就可以用类继承来解决问题。以我的水平还不足以在这么高的层次来清楚的解释它们的不同,这段话仅供参考吧。

模板类的运用方式,更多情况是直接使用,而不是作为基类。例如人们在使用STL提供的模板时,通常直接使用,而不需要从模板库中提供的模板再派生自己的类。这不是绝对的,我觉得这也是模板与类继承之间的以点儿区别,模板虽然也是抽象的东西,但是它往往不需要通过派生来具体化。

在设计模式[4]中,提到了一个模板方法模式,这个模式的核心就是对算法的抽象,也就是对固定操作序列的抽象。虽然不一定要用C++的模板来实现,但是它反映的思想是与C++模板一致的。

4. 参考资料

[1] 深度C++对象模型,Stanley B.Lippman, 侯捷译

[2] Thinking In C++ 2nd Edition Volumn 1, Bruce Eckel

[3] 定义-- 英文为definition,意思是"Make this variable here",参见[2] p93

[4] Design Patterns - Elements of Reusable Object-Oriented Software GOF

C++中的模板(template)相关推荐

  1. C++中的模板template

    1.Cpp中的模板template 模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码.模板是创建泛型类或函数的蓝图或公式.库容器,比如迭代器和算法都是泛型编程的例子,它们都使用了 ...

  2. Tornado框架中视图模板Template的使用

    上文的程序中有这样一段: class MessageHandler(tornado.web.RequestHandler):def get(self):self.write(''' <html& ...

  3. C++中函数模板template和函数参数为指针,且有返回值的结合使用

    1 #include<iostream> 2 using namespace std; 3 // 利用模板函数计算一个表达式 4 template<class Type> 5 ...

  4. 模板template

    这个是C++中的模板..template<typename T> 这个是定义模板的固定格式 模板应该可以理解到它的意思吧.. 比如你想求2个int float 或double型变量的值,只 ...

  5. ES6, Angular,React和ABAP中的String Template(字符串模板)

    String Template(字符串模板)在很多编程语言和框架中都支持,是一个很有用的特性.本文将Jerry工作中使用到的String Template的特性做一个总结. ES6 阮一峰老师有一个专 ...

  6. Vue中 模板template的四种写法

    <div id="app"><h1>我是直接写在构造器里的模板1</h1> </div><template id=" ...

  7. T4((Text Template Transformation Toolkit))模版引擎之基础入门 C#中文本模板(.tt)的应用...

    1 关于C#中文本模板(.tt)的简单应用 https://blog.csdn.net/zunguitiancheng/article/details/78011145 任何一个傻瓜都能写出计算机能理 ...

  8. 微信小程序模板template

    上面是官方的讲解, 主要是方便在不同的地方调用. 下面自己说下使用, 先创建一个模板名字是自己随便取的, 在template.wxml中填写模板 最外层用template标签 设置一个name属性 & ...

  9. C++中标准模板库std::vector的实现

    以下实现了C++标准模板库std::vector的部分实现,参考了 cplusplus. 关于C++中标准模板库std::vector的介绍和用法可以参考 https://blog.csdn.net/ ...

  10. OpenCV中使用模板匹配识别空闲的货架空间

    但是点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 假设你是一名在超市工作的员工,被要求在商店里四处走动,检查需要 ...

最新文章

  1. MySQL 绿色版安装方法图文教程
  2. 卸载重装得会员、偷删本地文件?网易云回应了:系造谣攻击,悬赏10万找线索...
  3. 【Flink】Flink写入es报错failed to get node info for request_id time out out after
  4. HttpsessionListener 实现在线人数统计
  5. PHP学习笔记三(数组API)
  6. 自我监督学习和无监督学习_弱和自我监督的学习-第2部分
  7. leetcode 栈 二叉树的前向遍历
  8. querydsl动态 sql_JPA整合Querydsl入门篇
  9. 使用iPhone系统设置开发者,进行弱网测试
  10. 桑佛德大学计算机科学,美国桑佛德大学专业都有哪些?每个专业都有什么优势?一起来了解下吧?...
  11. Google邮箱账号登陆存在异常活动怎么办?
  12. Python如何安装OpenCV库
  13. 每人都会遇到的三件事: 1.楼上传来弹珠的声,2.曾经的梦里出现过,3.马上就要睡着却突然感觉下坠...
  14. 论文阅读 (二十三):Attention-based Deep Multiple Instance Learning (2018)
  15. WinUsb_ReadPipe和WinUsb_WritePipe函数功能理解
  16. PAT 1080. Graduate Admission (30) 模拟高考志愿录取规则
  17. 线性和非线性方程数值解法_数值分析计算方法
  18. 认识vue.js(一)
  19. flutter 中的深拷贝
  20. 仿微信查看图片、H5的图片轮播插件PhotoSwipe、SuperSlide

热门文章

  1. 怎样推断server为虚拟机还是物理真机?
  2. 在 cmd 中启动 Android 模拟器
  3. 联想怎么启动windows无线服务器,Windows7系统下开启无线的多种方法
  4. iMeta | 南京医科大学孔祥清团队创建前瞻性多组学纵向原发高血压队列eHypertension...
  5. 数据分析2——核心思维技巧
  6. gis 六边形网格_六边形网格快速定位
  7. 阿里云DNS 新增云上线路的智能解析功能
  8. 如何给华硕笔记本在光驱位加装另一块linux系统固态硬盘?
  9. 出现Cannot find module 'xxx' 错误
  10. 干货来啦!「敏捷开发畅想与实战」沙龙回顾