模板与泛型编程
--类模板成员[续1]
二、非类型形参的模板实参
templateclass Screen { public: Screen():screen(hi * wid,'#'), cursor(hi * wid),height(hi),width(wid) {} //.. private: std::string screen; std::string::size_type cursor; std::string::size_type height,width; };
这个模板有两个形参,均为非类型形参。当用户定义Screen对象时,必须为每个形参提供常量表达式以供使用。
Screen<24,48> hp; //hi的模板实参为24,wid=48
【注解】
非类型模板实参必须是编译时常量表达式。
三、类模板中的友元声明
在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体友元关系:
1)普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。
2)类模板或函数模板的友元声明,授予对友元所有实例的访问权。
3)只授予对类模板或函数模板的特定实例的访问权的友元声明。
1、普通友元
templateclass Bar { friend class FooBar; friend void fcn(); };
普通非模板类FooBar和函数fcn可以访问Bar类的任意private成员和protected成员。
2、一般模板友元关系
友元关系可以是类模板或函数模板:
templateclass Bar { template friend class Foo1; template friend void temp_fcn1(const Type &); };
这些友元声明使用与类本身不同的类型形参,该类型形参指的是Foo1和 temp1_fcn1的类型形参。在这两种情况下,都将没有数目限制的类和函数设为Bar的友元。
这个友元声明在Bar与其友元Foo1和temp_fcn1的每个实例之间建立了一对多的映射。对Bar的每个实例而言,Foo1或 temp1_fcn1的所有实例都是友元。
3、特定的模板友元关系
只授予对特定实例的访问权:
templateclass Foo2; template void temp1_fcn2(const Type &); template class Bar { friend class Foo2 ; friend void temp1_fcn2 (char * const &); };
即使Foo2本身是类模板,友元关系也只扩展到Foo2的形参类型为char*实例。
下面形式的友元更为常见:
templateclass Foo2; template void temp1_fcn2(const Type &); template class Bar { friend class Foo3 ; friend void temp1_fcn3 (const T &); };
这些友元定义了Bar的特定实例与使用同一模板实参的Foo3或 temp1_fcn3的实例之间的友元关系。每个Bar实例有一个相关的Foo3和 temp1_fcn3友元:
Barbi; Bar bs;
只有与给定 Bar实例有相同模板实参的那些Foo3或 temp1_fcn3版本是友元。因此,Foo3
4、声明依赖性
当授予对给定模板的所有实例的访问权的时候,在作用域中不需要存在该类模板或函数模板的声明。实质上,编译器将友元声明也当做类或函数的声明对待。
想要限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数:
templateclass A; template class B { public: friend class A ; //OK:类A已声明 friend class C; //OK:C为一个普通非模板类,相当于声明 template friend class D; //OK:D是一个泛化的模板类 friend class E ; //Error:E在此之前还没有声明或定义 friend class F ; //Error:同上 };
如果没有事先告诉编译器该友元是一个模板,则编译器将认为该友元是一个普通非模板类或非模板函数。
四、Queue和QueueItem的友元声明
QueueItem类不打算为一般程序所用:它的所有成员都是私有的。为了让Queue类使用QueueItem类,QueueItem类必须将Queue声明为友元。
1、将类模板设为友元
对于实例化的Queue类的每种类型,我们想要Queue类和QueueItem类之间的一对一映射:
templateclass Queue; template class QueueItem { friend class Queue ; };
2、Queue输出操作符
因为希望输出任意类型Queue的内容,所以需要将输出操作符也设为模板:
templateostream &operator<<(ostream &os,const Queue &q) { os << "< "; for (Queue *p = q.head; p ; p = p -> next) { os << p -> item << " "; } os << ">"; return os; }
如果Queue为空,则for循环不执行,结果输出一对尖括号。
3、将函数模板设为友元
输出操作符需要成为Queue类和QueueItem类的友元。因为它需要使用Queue的head和QueueItem的next和item。我们的类将友元关系授予用同样类型实例化的输出操作符的特定实例:
templateclass Queue; template ostream &operator<<(ostream &,const Queue &); //首先声明 template class QueueItem { friend class Queue ; friend ostream & operator<< (ostream &,const Queue &); //AS Before }; template class Queue { friend ostream & operator<< (ostream &,const Queue &); //As Before };
每个友元声明授予对对应operator<<实例的访问权。
4、类型依赖性与输出操作符
Queue类的输出operator<<依赖于item对象的operator<<实际输出每个元素:
os << p -> item << " ";
当使用p-> item 作为<<操作符的操作数的时候,使用的是为item所属的任意类型而定义的<<。
因此,绑定到Queue且使用Queue输出操作符的每种类型本身必须有输出操作符。为没有定义输出操作符的类创建Queue对象是合法的,但输出保存这种类型的Queue对象会发生编译时(或链接时)错误。
//P555 习题16.38/39/40 templateclass Screen; template ostream & operator<<(ostream &,const Screen &); templateistream & operator>>(istream &,Screen &); templateclass Screen { friend ostream & operator<< (ostream &,const Screen