C++中的模板(template)
当前位置:以往代写 > C/C++ 教程 >C++中的模板(template)
2019-06-13

C++中的模板(template)

C++中的模板(template)

副标题#e#

网上我最喜欢的技能文章是雷同某何君所著“CVS快速入门”可能“UML reference card”之类,简短简要,可以很是快的领着你进入一个新天地。而对付较量长的文章我凡是是将其生存到硬盘上,然后筹备着“今后有时间”的时候再看,但它们凡是的运气都是“闲坐说玄宗”,直到某一天在整理硬盘时将它们以“不知所云”入罪,一并删除。

这篇小文主要是针对方才打仗模板观念的读者,但愿能辅佐读者进修模板的利用。为了制止本文也在诸公的硬盘上遭逢恶运,我抉择写的短些。“今后有时间”的时候再增补些内容。

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算法。


#p#副标题#e#

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函数。

#p#分页标题#e#

澄清了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(),则没有这样的要求。

#p#副标题#e#

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仍然没有配合基类。这与用范例作模板参数是一样的。

#p#副标题#e#

3.3 静态成员

与上面例子雷同:

template<class T> class A{ static char a_; };
|
+-- A<int> aint1, aint2;
|
+-- A<double> adouble1, adouble2;

#p#分页标题#e#

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

这个问题实际上与担任内里讲到的问题是一回事,要害要认识到aint与adouble别离是两个差异类的实例,而不是一个类的两个实例。认识到这一点后,许多雷同问题都可以想通了。

3.4 模板类的运用

模板与类担任都可以让代码重用,都是对详细问题的抽象进程。可是它们抽象的偏重点差异,模板偏重于对付算法的抽象,也就是说假如你在办理一个问题的时候,需要牢靠的step1 step2…,那么或许就可以抽象为模板。而假如一个问题域中有许多沟通的操纵,可是这些操纵并不能构成一个牢靠的序列,或许就可以用类担任来办理问题。以我的程度还不敷以在这么高的条理来清楚的表明它们的差异,这段话仅供参考吧。

模板类的运用方法,更多环境是直接利用,而不是作为基类。譬喻人们在利用STL提供的模板时,凡是直接利用,而不需要从模板库中提供的模板再派生本身的类。这不是绝对的,我以为这也是模板与类担任之间的以点儿区别,模板固然也是抽象的对象,可是它往往不需要通过派生来详细化。

在设计模式[4]中,提到了一个模板要领模式,这个模式的焦点就是对算法的抽象,也就是对牢靠操纵序列的抽象。固然不必然要用C++的模板来实现,可是它反应的思想是与C++模板一致的。

    关键字:

在线提交作业