模板与泛型编程
所谓泛型编程就是以独立于任何特定类型的方式编写代码。使用泛型程序时,我们需要提供具体程序实例所操作的类型或值。标准库的容器、迭代器、算法都是泛型编程的例子。在c++中,模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。
1. 函数模板
函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。
eg:
template<typename T>
int compare(const T &v1,const T &v2)
{
if(v1<v2) return -1;
if(v2<v1) return 1;
return 0;
}
(1):模板形参:上面定义了一个名为T的类型形参,在compare内部,可以使用名字T引用一个类型,T表示哪个实际类型有编译器根据所用的函数而确定。类型形参跟在关键字class或typename之后定义,如class T是名为T的类型形参,在这里class和typename没有区别。
(2)使用函数模板
使用函数模板时,编译器会推断哪个模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。
Eg:
int main()
{
cout<< compare(1,0)<<endl;
Strings1=”hi”,s2=”world”;
cout<<compare<<(s1,s2)<<endl;
return 0;
}
编译器将用int代替T创建第一个版本,并用string代替T创建第二个版本。
(3)inline函数模板
template<typenameT> inline T min(constT&,const T&);
inline 放在模板形参表之后,返回类型之前。
2. 类模板
template <class Type> class Queue
{
Public:
Queue();
Type &front();
const Type &front() const;
void push(const Type &);
void pop();
……
};
使用类模板:与调用函数模板形成对比,使用类模板时,必须为目标形参显式指定实参:
Queue <int> qi;
Queue< vector<double> > qc;
Quequ<string> qs;
3. 编写模板时,代码不可能针对特定类型,对实参类型的要求尽可能少是很有益的。
Compare 模板函数说明了编写泛型代码的两个重要原则:
(1) 模板的形参是const引用(2)函数体中的测试只用< 比较(操作符尽可以能少)
通过将形参设为const引用,就可以允许使用不允许复制的类型。大多数类型都可以复制,但也有不可以复制的。将形参设为const引用,保证这种类型可以用于compare函数,而且,如果有比较大的对象调用compare,则这个设计还可以是函数运行得更快。(不需要调用拷贝构造函数)
4. 模板在使用时将进行实例化,类模板在引用实际模板类类型时实例化,类模板的每次实例化都会产生一个独立的类类型,eg :编写Queue <int> qi;时,编译器会自动创建名为Queue<int>的类。函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。
5. 模板实参推断
(1).多个类型的实参必须完全匹配
short si;
compare (si,1024); //错误,从第一个实参推断出的模板类型是short,从第二个实参推断出来int类型,两个类型不匹配,所以模板实参推断失败。
如果compare的设计者允许实参的常规转换,则函数必须用两个类型形参来定义:
template<typename A, typename B>
int compare(const A& v1,const B& v2)
{
if(v1<v2) return -1;
if(v2<v1) return 1;
return 0;
}
compare (si,1024); //正确,
6. 类模板成员
(1).Queue 的实现策略
A.QueueItem 类表示Queue的链表中的节点,该类有两个数据成员item 和next。
B. Queue类将实现上面描述的接口函数,Queue类也有两个数据成员:head和tail,这些成员是QueueItem指针。
(2).QueueItem类
Template <class Type> class QueueItem
{
QueueItem(const Type &t): item(t), next(0) {}
Type item;
QueueItem *next;
};
(3).Queue类
template <class Type> class Queue
{
public:
Queue():head(0), tail(0) {}
Queue(const Queue &Q):head(0), tail(0) { copy_elem(Q); }
Queue& operator=(const Queue&);
~Queue() { destroy(); }
Type &front() { returnhead->item; }
const Type &front() const { return head->item; };
void push(const Type &);
void pop();
bool empty () const { return head==0; }
private:
QueueItem<type> *head;
QueueItem<type> *tail;
void destroy();
void copy_elems(const Queue);
};
通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字Queue是Queue<Type>的缩写表示。因此复制构造函数定义其实等于:
Queue<Type> ( const Queue<Type> &Q ): head(0),tail(0) {copy_elem(Q);}
但是类中其他模板的模板形参必须指定类型形参:
QueueItem<type> *head;
QueueItem<type> *tail;
(4). 类模板成员函数
类模板成员函数的定义具有如下形式:
A. 必须以关键字template开头,后接类的模板形参表
B. 必须指出它是哪个类的成员
C. 类名必须包含其模板形参
从这些规则可以看出,在类外定义的Queue类的成员函数的开头应该是:
Template <class Type> ret-type Queue<Type>::member-name
l Destroy函数
为了举例说明在类外定义的类模板成员函数,我们来看destroy函数:
template<class Type> void Queue<Type>::destroy()
{
while(!empty())
pop();
}
它的工作是遍历这个Queue的每个分支,调用pop除去每一项。
l pop函数
template<class Type> void Queue<Type>::pop()
{
QueueItem<type> *p=head;
head=head->next;
delete p;
}
Pop函数假设用户不会在空Queue上调用pop。它的工作是除去Queue的头元素。必须重置head指针以指向Queue中的下一个元素,然后删除head位置的元素。唯一有技巧的部分是记得保持指向该元素的一个单独指针,以便在重置head指针后可以删除元素。
l push函数
template<class Type> void Queue<Type>::push(const Type &val)
{
QueueItem<type> *pt=newQueueItem<Type>(val);
if (empty())
head=tail=pt;
else
{
tail->next=pt;//添加一个新元素到尾巴
tail=pt;
}
}
首先分配新的QueueItem对象,用传递值初始化它。如果Queue中已经有元素了,则使当前tail元素指向这个新元素。旧的tail不再是最后一个元素了,这也是通过使tail指向新构造的元素指明的。
l copy_elems函数
template<class Type> void Queue<Type>::copy_elem(const Type &orig)
{
for(QueueItem<Type>*pt==orig.head; pt; pt=pt->next)
push(pt->item);//拷贝元素
}
(5).类模板成员函数的实例化
与其他函数模板不同的是,在实例化类模板成员函数的时候,编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定。例如,当调用Queue<int>类型对象的push成员时,
实例化的push函数为void Queue<int>::push(const int &val)
对象的模板实参能够确定成员函数模板形参,这一事实意味着,调用类模板成员函数比调用函数模板更灵活。用模板形参定义的函数形参的实参允许进行常规转换:
Queue<int > qi;
shorts=42;
inti=42;
qi.push(s);//oks is convert to int
qi.push(i);
7. 类模板中的友元声明
在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体的友元关系:
(1) 普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。
(2) 类模板或函数模板的友元声明,授予友元所有实例的访问权
(3) 只授予对类模板或函数模板的特定实例的访问权的友元声明。
A.普通友元
非模板类或非模板函数可以是类模板的友元:
template<class Type> class Bar
{
friend class FooBar;
friend void fcn();
//……
};
这个声明是说,FooBar的成员和fcn函数可以访问Bar类的任一实例的private成员和protected成员。
B.一般模板友元关系
友元可以是类模板或函数模板:
template<class Type> class Bar
{
template <class T> friendclass Fool;
template <class T> friendvoid fcn(const T&);
//……
};
这些友元声明使用与类本身不同的类型形参,该类型形参指的是Fool和fcn的类型形参。Fool的任意实例都可以访问Bar的任意实例的私有元素,类似的,fcn的任意实例可以访问Bar的任意实例。
C.特定的模板友元关系
除了将一个模板的所有实例设为友元,类也可以只授予对特定实例的访问权。
template<class T> class Foo2;
template<class T> class voidtemp_fcn2(const T&);
template<class Type> class Bar
{
friend class Foo2<char *>;
friend void temp_fcn2<char *>(char* const&);
};
即使Foo2本身是类模板,友元关系也只扩展到Foo2的形参类型为char*的特定实例。类似的,temp_fcn2的友元声明是说,只用形参类型为char* 的函数实例是Bar类的友元,形参类型为char* 的Foo2和temp_fcn2的特定实例可以访问Bar的每个实例。
下面形式的友元声明更为常见:
template<class T> class Foo3;
template<class T> class voidtemp_fcn3(const T&);
template<class Type> class Bar
{
friend class Foo3<Type >;
friend void temp_fcn3<char *>( cons Type&);
};
这些友元定义了Bar的特定实例与使用同一模板实参的Foo3或temp_fcn3的实例之间的友元关系。每个Bar实例有一个相关的Foo3和temp_fcn3友元:
Bar<int> bi;///Foo3<int> 和temp_fcn3<int> 是友元
Bar<string> bs;/// Foo3<string > 和temp_fcn3<string> 是友元.因此,Foo3<int>可以访问
Bar<int>的私有部分,但不能访问Bar<string>或者任意其他Bar实例的私有部分。
D.声明依赖性
要想限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数:
template <class T> class A;
template<class T> class B
{
friendclass A <T>; //ok A is knownto be a template
friendclass C; //ok, C must be anordinary ,nontemplate class
template<class S>friend class D; //OK, Dis a template
friendclass E<T>; //error ,E wasn’tdeclared as a template
};
8. Queue和QueueItem的友元声明
QueueItem类不打算为一般程序所用:它的所有成员都是私有的。为了让Queue类使用QueueItem类,QueueItem类必须将Queue类设为友元。
template <class Type>class Queue;
template<class Type>class QueueItem
{
friend class Queue <Type>; //只将与QueueItem类用同样类型实例化的Queue类设为友元
};
A——Queue类接口中可能增加的一个有用的操作,是输出Queue对象的内容的能力。提供输出操作符重载实例,可以做到这一点。这个操作符将遍历Queue中的元素链表并输出每个元素的值,将在一对<>内输出元素。因为希望能够输出任意类型的Queue内容,所以需要将输出操作符也设为模板:
template<class Type>
ostream&operator<< (ostream &os, const Queue<Type> &q )
{
os << “<”;
queueItem<Type> *p;
for(p=q.head; p; p=p->next)
Os<< p->item<<“ ”;
os << “>”;
return os;
}
如果int类型的Queue包含值3、5、8、13,这个Queue的输出显示如下<35 8 13>.
B——将函数模板设为友元
输出操作符需要成为Queue类和QueueItem类的友元。它使用Queue类的head成员和QueueItem类的next 和item成员。我们的类将友元关系授予同样类型实例化的输出操作符的特定实例:
Template<class T >
Std::ostream& operator<<(std::ostream&, constQueue<T>&);
template<class Type>class QueueItem
{
friend class Queue <Type>; //
friend Std::ostream& operator<< <Type> (std::ostream&, constQueue<T>&);
};
template <class Type>class Queue
{
friend Std::ostream& operator<< <Type> (std::ostream&, constQueue<T>&);
};
每个友元声明授予对应operator<<实例的访问权,及输出Queue<int>的输出操作符是Queue <int> 类的友元,它不是任意其他Queue类型的友元。
9. 模板特化
模板特化(templatespecialization)是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:
A. 关键字template后面接一对空的尖括号
B. 再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
C. 函数形参表
D. 函数体
下面程序定义了当模板形参类型绑定到constchar*时,compare函数的特化:
template <>
int compare <constchar*>(const char* const&v1,
const char* const& v2)
{ return strcmp(v1,v2); }
在这个例子中,模板有一个类型形参和两个函数形参,函数形参是类型形参的const引用,在这里,将类型形参固定为const char*,因此,函数形参是const char*的const引用。当调用compare函数的时候,传给它两个字符指针,编译器将调用特化版本。
constchar *cp1=”world”,*cp2=”hi”;
int i1,i2;
compare(cp1,cp2); //calls the specialization
compare(i1,i2); //calls the generic versioninstantiated with int