C++ 模板

c++ primer第三版笔记
第16章 类 模 板
1.template <class T>
class QueueItem;
类模板的定义和声明都以关键字template 开头。关键字后面是一个用逗号分隔的模板参
数表,用尖括号<> 括起来这个表被称为类模板的模板参数表(template parameter list)。
它不能为空,模板参数可以是一个类型参数,也可以是一个非类型参数。如果是非类型参数,
则代表一个常量表达式。
2.在模板参数表中,关键字class 和typename 的意义相同。
在标准C++之前关键字typename 没有被支持。因为有时
必须要靠它来指导编译器解释模板定义。


3.一个类模板可以有多个类型参数
template <class T1, class T2, class T3>
class Container;
但是每个模板类型参数的前面都必须有关键宇class 或typename。
一旦声明了类型参数那么在类模板定义的余下部分中它就可以被用作类型指示符。
它在类模板中的使用方式与内置的或用户定义的类型在非模板类定义中的用法一样。
模板非类型参数(nontype parameter) 由一个普通参数声明构成,一个非类型参数指示
该参数代表了一个潜在的值,而这个值又代表类模板定义中的一个常量。例如
template <class Type, int size>
class Buffer;
一个类定义或声明紧跟在模板参数表后面。除了模板参数外,类模板的定义看起来和非
模板类相同。


类型替换的过程被称为模板实例化template instantiation。
模板参数的名字在它被声明为模板参数后,一直到模板声明或定义的结束都可以被
使用。如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。


模板参数名不能被用作在类模板定义中声明的类成员的名字。


模板参数的名字在模板参数表中只能被引入一次。例如
// 错误: 重复使用名为Type 的模板参数
template <class Type, class Type>
class container;
在不同的类模板声明或定义之间模板参数的名字可以被重复使用。
// ok: 名字 'Type' 在不同模板之间可被重复使用
template <class Type>
class QueueItem;
template <class Type>
class Queue;


在类模板的前向声明和类模板定义中,模板参数的名字可以不同。例如
// 所有三个 QueueItem 声明都引用同一个类模板
// 模板的声明
template <class T> class QueueItem;
template <class U> class QueueItem;
// 模板的真正定义
template <class Type>
class QueueItem { ... };


类模板的参数可以有缺省实参。这对类型参数和非类型参数都一样。
模板参数的缺省实参是一个类型或值。当模板被实例化时,如
果没有指定实参,则使用该类型或者值。
template <class Type, int size = 1024>
class Buffer;
类模板的后续声明可以为模板参数提供附加的缺省实参。正如函数参数的缺省实参的情
形一样,在向左边的参数提供缺省实参之前,必须首先给最右边未初始化的参数提供缺省实
参。例如
template <class Type, int size = 1024>
class Buffer;
// ok: 考虑两个声明中的缺省实参
template <class Type = string , int size>
class Buffer;


template <class Type>
class QueueItem {
public:
QueueItem( const Type & );
private:
Type item;
QueueItem *next;
};
QueueItem 类模板名的每次出现都是以上形式的缩写
QueueItem<Type>。
这种简写形式,只能被用在类模板QueueItem 自己的定义中,以及在类模板定义之外出现
的成员定义中。
当QueueItem 在其他模板定义中被用作一个类型指示符时,
我们必须指定完整的模板参数表。在下面的例子中类模板被用在函数模板
display 的定义中,在这种情况下类模板QueueItem 的名字必须跟有模板参数:
template <class Type>
void display( QueueItem<Type> &qi )
{
QueueItem<Type> *pqi = &qi;
// ...
}
 
4.从通用的类模板定义中生成类的过程被称为模板实例化template instantiation。
同一个类模板针对不同类型的实例之间并没有特殊的关系。类模板的每个实例都构成一
个独立的类类型。
类模板实例的名字是Queue<int>或Queue<string> 。在类模板名Queue 后面的符号<int>
或<string>被称为模板实参,模板实参必须是在一个由逗号分隔的列表中被指定,并放在一对
尖括号(<>, 一个小于号和一个大于号)中。


模板声明或定义可以引用类模板或类模板的实例。
// 函数模板声明
template <class Type>
void bar( Queue<Type> &, // 引用通用的模板
Queue<double> & // 和模板实例
)
但是在模板定义的上下文环境之外只能使用类模板实例。


只有当代码中使用了类模板的一个实例的名字,并且上下文环境要求必须存在类的定义
时,这个类模板才被实例化,并不是每次使用一个类都要求知道该类的定义。
class Matrix;
Matrix *pm; // ok: 不需要类 Matrix 的定义
void inverse( Matrix & ); // ok 也不需要
所以,声明一个类模板实例的指针和引用不会引起类模板被实例化。


定义一个类类型的对象时需要该类的定义。
class Matrix;
Matrix obj1; // 错误: Matrix 没有被定义
class Matrix { ... };
Matrix obj2; // ok
所以,如果一个对象的类型是一个类模板的实例,那么当对象被定义时,类模板也被实
例化。
Queue<int> qi; // Queue<int> 被实例化
在这一点上,Queue<int>类的定义对于编译器变成已知的,该点被称为类Queue<int>的
实例化点point of instantiation。
在定义模板时根据模板被实例化的类型我们必须做一些设计上的考虑。
另外一种设计考虑在于这个构造函数的实现之中。


类模板参数也可以是一个非类型模板参数,对可以被用于这种非类型模板参数的模板
实参的种类有一些限制。
绑定给非类型参数的表达式必须是一个常量表达式,即它必须能在编译时被计算出结
果。
非const 对象的值不是一个常量表达式,它不能被用作非类型模板参数的实参。
但是名字空间域中任何对象的地址(即使该对象不是const 类型)是一个常量表达式,而
局部对象的地址则不是。sizeof 表达式的结果是一个常量表达式,所以它可以被用作非类型模板参数的
实参。


对于一个模板非类型参数,如果两个不同的表达式的求值结果相同,则它们被认为是等价的模板实参:
// 三者都是类型 Screen< 24, 80 >
Screen< 2*12, 40*2 > scr0;
Screen< 6+6+6+6, 20*2+40 > scr1;
Screen< width, height > scr2;


在模板实参的类型和非类型模板参数的类型之间,允许进行一些转换(能被允许的转换集
是函数实参上被允许的转换的子集):
1 左值转换,包括从左值到右值的转换,从数组到指针的转换,以及从函数到指针的转

2 限定修饰转换。
3 提升
4 整值转换


5.与非模板类一样,类模板的成员函数也可以在类模板的定义中定义,在这种情况下该
成员函数是inline 成员函数,或者成员函数也可以被定义在类模板定义之外。
被定义在类模板定义之外的成员函数必须使用特殊的语法,来指明它是一个类模板的成
员。成员函数定义的前面必须加上关键字template 以及模板参数。
template <class Type>
inline Queue<Type>::
Queue( ) { front = back = 0; }
Queue 的第一次出现在域操作符::之前,后面紧跟着模板参数表,这表示成员函数所属
的类模板。Queue 的第二次出现紧跟在域操作符之后,表示构造函数的名字,它的名字可
以但是不必紧跟在模板参数表后面。
类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者
取地址时才被实例化。
当类模板被实例化时,类模板的成员函数并不自动被实例化。只有当一个成员函数被程
序用到(函数调用或取地址)时它才被实例化。


6.
有三种友元声明可以出现在类模板中:
1 非模板友元类或友元函数。
class Foo {
void bar();
};
template <class T>
class QueueItem {
friend class foobar;
friend void foo();
friend void Foo::bar();
// ...
};
在类模板QueueItem 把foobar 类和函数foo()声明为友元之前,它们不必在全局域中被声
明或定义,但是在QueueItem 类把Foo 类的一个成员声明为友元之前,Foo 类必须已经被
定义。记住一个类成员只能由该类的定义引入。
2 绑定的bound 友元类模板或函数模板。下列例子中,在类模板QueueItem 的实例
和它的友元(也是模板实例)之间定义了一对一的映射。
template <class Type>
class foobar{ ... };
template <class Type>
void foo( QueueItem<Type> );
template <class Type>
class Queue {
void bar();
// ...
};
template <class Type>
class QueueItem {
friend class foobar<Type>;
friend void foo<Type>( QueueItem<Type> );
friend void Queue<Type>::bar();
// ...
};
在一个模板可以被用在一个类模板的友元声明中之前,它的声明或定义必须先被给出。
foo()的友元声明的语法看起来或许令人吃惊:
friend void foo<Type>( QueueItem<Type> );
函数名后面紧跟着显式的模板实参表foo<type>, 这种语法可用来指定该友元声明所引
用的函数模板foo()的实例。如果省略了显式的模板实参,如下所示
friend void foo( QueueItem<Type> );
则友元声明会被解释为引用了一个非模板函数,且该函数的参数类型是类模板
QueueItem 的一个实例。
我们必须为引用函数模板实例的友元声明指定显式的模板参数表。
3 非绑定的unbound 友元类模板或函数模板。在下面的例子中,在类模板QueueItem
的实例和其友元之间定义了一对多的映射。
template <class Type>
class QueueItem {
template <class T>
friend class foobar;
template <class T>
friend void foo( QueueItem<T> );
template <class T>
friend void Queue<T>::bar();
// ...
};


用户可能需要能够显示Queue 对象的内容.要做到这一点,一种方法
是提供输出操作符的重载实例.这个操作符需要被声明为Queue 类模板的友元函数,因为它
必须访问该类的私有成员。
可以定义一个一般化的输出操作符它能够处理Queue 的全部实例。为了使其能够工作,接下来我们必须使这个重载的输出操作符成为一个函数模
板:
template <class Type> ostream&
operator<< ( ostream &, const Queue<Type> & );


下一件我们需要做的事情是把这个操
作符声明为Queue 的友元
template <lass Type>
class Queue {
friend ostream&
operator<< ( ostream &, const Queue<Type> & );
// ...
};
这种声明创建了Queue 的实例与相应的operator<<()实例之间的一一映射的关系。


7.
类模板也可以声明静态数据成员。类模板的每个实例都有自己的一组静态数据成员。
对类模板QueueItem 定义的修改如下
#include <cstddef>
template <class Type>
class QueueItem {
// ...
private:
void *operator new( size_t );
void operator delete( void *, size_t );
// ...
static QueueItem *free_list;
static const unsigned QueueItem_chunk;
// ...
};
剩下要做的事情就是初始化静态成员free_list 和QueueItem_chunk:


template <class T>
QueueItem<T> *QueueItem<T>::free_list = 0;


template <class T>
const unsigned int
QueueItem<T>::QueueItem_chunk = 24;
静态数据成员的模板定义必须出现在类模板定义之外,因此模板定义以关键字template
开始,后面是类模板参数表<class T> 。静态数据成员的名字前需要加上前缀QueueItem<T>::,
表明该成员属于类模板QueueItem 。这些静态数据成员的定义被加入到头文件Queue.h 中,
且必须被包含在使用这些静态数据成员的实例的文件中。
只有当程序使用静态数据成员时,它才会从模板定义中被真正实例化。类模板的静态成
员本身就是一个模板。静态数据成员的模板定义不会引起任何内存被分配。只有对静态数据
成员的某个特定的实例才会分配内存。每个静态数据成员实例都与一个类模板实例相对应。
因此,一个静态数据成员的实例在被引用的时候总是要通过一个特定的类模板实例。例如:
// 错误: QueueItem 不是一个真正的实例
int ival0 = QueueItem::QueueItem_chunk;
int ival1 = QueueItem<string>::QueueItem_chunk; // ok
int ival2 = QueueItem<int>::QueueItem_chunk; // ok


8.
在类模板Queue 的私有区中嵌入类模板QueueItem 的定义。


类模板的嵌套类自动成为一个类模板。在嵌套类模板内部可以使用外围类模板的模
板参数。
例如:
template <class Type>
class Queue {
// ...
private:
class QueueItem {
public:
QueueItem( Type val )
: item( val ), next( 0 ) { ... }
Type item;
QueueItem *next;
};
// 因为 QueueItem 是一个嵌套类型
// 不是在 Queue 外定义的模板
// 所以可以省略 QueueItem 之后的模板实参 <Type>
QueueItem *front, *back;
// ...
};
这种在类模板QueueItem 的实例和外围类模板Queue 的实例之间的映射是一对一的。
当外围类模板被实例化时它的嵌套类不会自动被实例化。
在类模板中也可以声明枚举和typedef。
对于类模板的公有嵌套类型或嵌套枚举类型的一个枚举值,
一般的程序只能引用该嵌套类型的一个实例。在这种情况上,嵌套
类型的名字前必须要加上类模板实例的名字。例如
// 错误: Buffer 的哪一个实例?
Buffer::Buf_vals bfv0;
Buffer<int,512>::Buf_vals bfv1; // ok
即使嵌套类型并没有使用外围类模板的参数,这条规则也同样适用。


9.
函数或类模板可以是一个普通类的成员,也可以是一个类模板的成员。成员模板的定义
看起来像一般模板的定义成员定义前面加上关键字template 及模板参数表。例如:
template <class T>
class Queue {
private:
// 类成员模板
template <class Type>
class CL
{
Type member;
T mem;
};
// ...
public:
// 函数成员模板
template <class Iter>
void assign( Iter first, Iter last )
{
while ( ! is_empty() )
remove(); // calls Queue<T>::remove()
for ( ; first != last; ++first )
add( *first ); // calls Queue<T>::add( const T & )
}
};
成员模板的声明有它自己的模板参数。例如类成员模板CL 有自己的名为Type 的模板
参数,而函数成员模板assign()也有自己的模板参数Iter 。另外成员模板的定义也可以使用
外围类模板的模板参数。


在类模板Queue 中声明一个成员模板,意味着Queue 的一个实例包含了可能无限多个
联套类CL 和可能无限多个成员函数assign()。
成员模板遵循与其他类成员相同的访问规则。
只有当成员模板被程序中使用时它才被实例化。
类模板Queue 的函数成员模板assign()是说明为什么“支持容器类型需要成员模板”的
一个很好的例子。成员模板assign()正好允许我们这样做,因为它可以使用任何容器类型。所
以我们编写函数成员模板assign()时,用迭代器作为接口,这样就能够把它的实现与迭代器指
向的实际类型分离开。


任何成员函数都可以被定义为成员模板,例如构造函数也可以被定义为成员模板。
为类模板Queue 定义这样的构造函数:
template <class T>
class Queue {
// ...
public:
// 构造函数成员模板
template <class Iter>
Queue( Iter first, Iter last )
: front( 0 ), back( 0 )
{
for ( ; first != last; ++first )
add( *first );
}
};
这样的构造函数允许用另一个容器的内容进行初始化。
 
像非模板成员一样一个成员模板也可以被定义在其外围类或类模板定义之外。如下所示:
template <class T>
class Queue {
private:
template <class Type> class CL;
// ...
public:
template <class Iter>
void assign( Iter first, Iter last );
// ...
};
template <class T> template <class Type>
class Queue<T>::CL<Type>
{
Type member;
T mem;
};
template <class T> template <class Iter>
void Queue<T>::assign( Iter first, Iter last )
{
while ( ! is_empty() )
remove();
for ( ; first != last; ++first )
add( *first );
}
如果一个成员模板被定义在类模板定义之外,则在它的定义前面就必须加上类模板参数
表,然后再跟上它自己的模板参数表,这就是成员函数模板assign()以
template <class T> template <class Iter>
开头的原因。
第一个模板参数表template<class T>是类模板Queue 的,而第二个模板参数表
template<class Iter>是成员模板assign()的。模板参数不一定与类模板定义中指定的名字相同。
例如下面的语句仍然定义了类模板Queue 的函数成员模板assign():
template <class TT> template <class IterType>
void Queue<TT>::assign( IterType first, IterType last )
{ ... }


10.
只有当上下文环境要求类模板的完整类定义时类模板才被实例化。
只有当编译器看到了实际的类模板定义,而不仅仅只是声明时它才能实例化类模板。
为了确保在每个必须实例化类模板的文件中都有类模板的定义类模板定义应该被放在头文件中。
针对一个特定类型而实例化类模板不会引起针对同一类型自动实例化类模板成员的
定义,只有当程序需要知道成员的定义时(即如果嵌套类被使用时要求它的完整类类型
或如果调用成员函数或如果做成员地址或如果查看静态数据成员的值),成员才会被实
例化。
在包含编译模式下,类模板的成员函数和静态成员的定义必须被包含在要将它们实例
化的所有文件中。对于类模板定义中被定义为inline 的内联成员函数,这是自动发生的。
但是如果一个成员函数被定义在类模板定义之外,那么这些定义应该被放在含有该类模板
定义的头文件中。


在分离编译模式下,类模板定义和其inline 成员函数定义都被放在头文件中,而非inline
成员函数和静态数据成员被放在程序文本文件中。在这种模式下,类模板及其成员的定义的
组织方式与我们组织非模板类及其成员的定义的方式相同。例如
// ----- Queue.h -----
// 声明 Queue 是一个可导出的 (exported) 类模板
export template <class Type>
class Queue {
// ...
public:
Type& remove();
void add( const Type & );
// ....
};
// ----- Queue.C -----
// exported definition of class template Queue in Queue.h
#include "Queue.h"
template <class Type>
void Queue<Type>::add( const Type &val ) { ... }
template <class Type>
Type& Queue<Type>::remove() { ... }
使用成员函数实例的程序只需要在使用它之前包含这个头文件
// ----- User.C -----
#include "Queue.h"
int main() {
}
为了使其成为可能类模板必须以一种特殊的
方式来声明——声明为exported 可导出的类模板。
即使一个类模板被声明为可导出的,类模板自身的定义也不能从User.C 中省略掉。
在User.C 中的Queue<int>类的实例提供了声明成员函数Queue<int>::add()和
Queue<int>::remove() 的类定义在可以调用这些成员函数之前,这些声明是必需的。因此
即使类模板自身被声明为可导出的,关键字export也只影响类模板的成员函数和静态数据成
员。
我们也可以只把类模板的个别成员声明为可导出的,在这种情况上关键字export 不是
被指定在类模板上,而是被指定在要被导出的成员定义上。例如如果类模板Queue 的作者
只想让成员函数Queue<type>::add()被导出,则关键字export 可以被指定在成员函数add()的定义上。
// ----- Queue.h -----
template <class Type>
class Queue {
// ...
public:
Type& remove();
void add( const Type & );
};
// 必需的, 因为 remove() 不是可导出的
template <class Type>
Type& Queue<Type>::remove() { ... }
// ----- Queue.C -----
#include "Queue.h"
// 只有成员函数 add() 是可导出的
export template <class Type>
void Queue<Type>::add( const Type &val ) { ... }


”类模板成员函数或静态数据成员的定义在一个程序中被定义为可导出的“只能有一次。


标准C++提供了显式实例声明explicit instantiation declaration, 以允许程序员控制模板实例化发生的时
间。
在显式实例声明中,关键字template 后面跟着关键字class 以及类模板实例的名字。例如:
#include Queue.h
// 显式实例声明
template class Queue<int>;
显式实例化类模板时,它的所有成员也被显式实例化,而且针对同一组模板实参类型。
这暗示着在显式实例声明出现的地方,不但要提供类模板的定义,而且还要提供类模板成员
的全部定义,如果不存在这些定义则显式实例声明是错误的。


当一个显式实例声明出现在程序文本文件中时,如果其他文本文件也使用了该类模板实
例,则会发生什么?我们必须使用“抑制模板隐式实例化”的编译器选项。
 
11.
为了介绍模板特化我们
把这些函数定义为类模板Queue 的成员函数
template <class Type>
class Queue {
// ...
public:
Type min();
Type max();
// ...
};
// 找到 Queue 中的最小值
template <class Type>
Type Queue<Type>::min( )
{
assert( ! is_empty() );
Type min_val = front->item;
for ( QueueItem *pq = front->next; pq != 0; pq = pq->next )
if ( pq->item < min_val )
min_val = pq->item;
return min_val;
}
// 找到 Queue 中的最大值
template <class Type>
Type Queue<Type>::max( )
{
assert( ! is_empty() );
Type max_val = front ->item;
for ( QueueItem *pq = front->next; pq != 0; pq = pq->next )
if ( pq->item > max_val )
max_val = pq->item;
return max_val;
}
在成员函数min()中的下列语句用于比较Queue 中的两个数据项:
pq->item< min_val
这引入了对于Queue 类模板被实例化所针对的类型的隐含要求:被用作模板实参的
类型必须能够使用为内置类型而预定义的小于操作符,或者是定义了operator<()的用户定义
的类类型。
假设有如下类型我们想用它实例化类模板Queue
class LongDouble {
public:
LongDouble( double dval ) : value( dval ) { }
bool compareLess( const Lo ngDouble & );
private:
double value;
};
一种解决方案是定义全局操
作符operator<()和operator>(), 它们使用LongDouble 的成员函数compareLess()来比较两个
Queue<LongDouble>类型的值。
一种方案如果模板实参是LongDouble 类型,则我们不希望使用类模板Queue 的通用
成员函数定义来实例化成员函数mim()和max() 。我们希望专门定义
Queue<LongDouble>::mln()和Queue<LongDouble>::max()实例,让它们使用Long(double)成员
函数compareLess()。
为此,我们可以通过一个显式特化定义explicit specialization definition ,为类模板实
例的一个成员提供一个特化定义。


显式特化定义包括关键字template ,后跟一对尖括号(<>
一个小于号和一个大于号),以及后面的类成员的特化定义。例如:
// 显式特化定义
// explicit specialization definitions
template<> LongDouble Queue<LongDouble>::min( )
{
assert( ! is_empty() );
LongDouble min_val = front->item;
for ( QueueItem *pq = front->next; pq != 0; pq = pq->next )
if ( pq->item.compareLess( min_val ) )
min_val = pq->item;
return min_val;
}
template<> LongDouble Queue<LongDouble>::max( )
{
assert( ! is_empty() );
LongDouble max_val = front->item;
for ( QueueItem *pq = front->next; pq != 0; pq = pq->next )
if ( max_val.compareLess( pq->item ) )
max_val = pq->item;
return max_val;
}
即使类类型Queue<LongDouble>是根据通用类模板定义而被实例化的,
Queue<LongDouble>的每个对象仍可以使用成员函数min()和max()的特化——这些成员函数
并不根据类模板Queue 的通用成员定义而被实例化。


因为成员函数min()和max()的显式特化定义是函数定义而不是模板定义,而且因为这些
定义没有被声明为内联的,所以它们不能被放在头文件中必须被放在程序文本文件中。
幸运的是我们可以只是声明函数模板显式特化而不定义它。例如:
// 函数模板显式特化声明
template<> LongDouble Queue<LongDouble>::min( );
template<> LongDouble Queue<LongDouble>::max( );
把这些声明放在头文件中,以及把相关的定义放在程序文本文件中。我们就可以像对其
他非模板类成员定义一样地组织显式特化的代码。


在某些情况下,整个类模板的定义对于某个特殊的类型并不合适。在这样的情况下,程
序员可以提供一个定义来特殊化整个类模板。
例如程序员可以针对Queue<LongDouble>提供一个完整的定义:
// QueueLD.h: 定义类的特化 Queue<LongDouble>
#include "Queue.h"
template<> class Queue<LongDouble> {
Queue<LongDouble>();
~Queue<LongDouble>();
LongDouble& remove();
void add( const LongDouble & );
bool is_empty() const;
LongDouble min();
LongDouble max();
private:
// 某些特殊的实现
};
只有当通用的类模板被声明(不一定被定义之后),它的显式特化才可以被定义,即
在模板被特化之前编译器必须知道类模板的名字。


即使我们定义了一个类模板特化,也必须定义与这个特化相关的所有成员函数或静态数
据成员。类模板的通用成员定义不会被用来创建显式特化的成员的定义,这是因为类模板特
化可能拥有与通用模板完全不同的成员集合。


如果整个类被特化了,那么标记特化定义的符号template<>只能被放在类模板的显式
特化的定义之前。类模板特化的成员定义不能以符号template<>作为打头。例如:
#include "QueueLD.h"
// 定义类模板特化的成员函数 min()
LongDouble Queue<LongDouble>::min( ) { }


类模板不能够在某些文件中根据通用模板定义被实例化,而在其他文件中却针对同一组
模报实参被特化例.


12.
有人可能希望提供这样一个模板,它仍然是一个通用的模板,只不过某些模板参数已经被实际的类型或值取代。
通过使用类模板部分特化partial specialization ,这是有可能实现的。
template <int hi, int wid>
class Screen {
// ...
};
// 类模板 Screen 的部分特化
template <int hi>
class Screen<hi, 80> {
public:
Screen();
// ...
private:
string _screen;
string::size_type _cursor;
short _height;
// 为 80 列的屏幕使用特殊的算法
};
类模板部分特化也是一个模板。它的定义看起来就像一个模板定义。这样的定义以关键
字template 开始,后面是尖括号中的模板参数表。类模板部分特化的参数表与对应的通用类
模板定义的参数表不同。而部分特化的模板参数表只列出模板实参仍然未知的那些参数。Screen 的部分特化只有一个非类型模板参数hi ,因为wid 的模板实
参已知为80 。
当程序使用类模板部分特化时,它是被隐式实例化的。在下面的例子中,类模板部分特
化将用24 作为hi 的模板实参而被实例化。
Screen<24,80> hp2621;


当程序声明了类模板部分特化时,编译器选择针对该实例而言最为特化的模板定义进行实例化,
当没有特化可被使用时才使用通用模板定义。


部分特化的定义与通用模板的定义完全无关。部分特化可能拥有与通用类模板完全不同
的成员集合。类模板部分特化必须有它自己对成员函数静态数据成员和嵌套类的定义。类
模板成员的通用定义不能被用来实例化类模板部分特化的成员。例如我们必须定义部分特
化Screen<hi,80>的构造函数下面是一种可能的定义。
// 部分特化 Screen<hi, 80> 的构造函数
template<int hi>
Screen<hi,80>::Screen() : _height( hi ), _cursor ( 0 ),
_screen( hi * 80, bk )
{ }


13.
在模板的所有实例中,意义相同的语法结构体是指不依赖于模板参数的语法结构体。
在类模板定义中或类模板成员的定义中,名字解析的两个步骤如下:
1.在模板被定义时解析出不依赖于模板参数的名字。
2.在模板被实例化时解析出依赖于模板参数的名字。


14.
与任何其他的全局域定义一样,类模板定义也可以被放在名字空间中。


类模板或类模板成员的特化声明必须被声明在定义通用模板的名字空间中。


我们也可以在名字空间之外定义模板特化,只要该特化的定义
出现在名字空间cplusplus_primer 的外围名字空间中,并且特化的名字被正确的名字空间名限
定修饰。例如
namespace cplusplus_primer
{
// Queue 及其成员函数的定义
}
// cplusplus_primer::Queue<char*> 的特化声明
template<> class cplusplus_primer::Queue<char*> { ... };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w_yuetian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值