1、编译器如何处理模板代码? 编译器遇到模板代码时,它首先进行语法检查,但是并不对模板进行编译(因为它不知道模板将用于什么数据类型)。编译器遇到模板实例化时,比如 Grid<int> myIntGrid; 会使用int来代替T生成int版代码。同样,如果遇到其他类型,编译器也会生成另一个对应的代码版本。 如果编程语言不支持模板的话,那么要为每一种类型都要编写独立的类,使用了模板以后,编写独立的类的工作就由编译器来做。 如果程序中没有针对任何类型的模板的实例化,则永远也不会编译类的方法定义。并且你实例化了那种类型,编译器就只会产生对应的类版本, 不会产生多余的代码。 2、无类型模板参数 无类型参数是指诸如int和指针这样的“常规”参数。模板允许无类型参数取“简单”的类型的值:int、enum、指针和引用。 template<typename T,int WIDTH,int HEIGHT> class Grid { public: void setElementAt( int x,int y,const T& inElem ); T& getElementAt( int x,int y ); const T& getElementAt( int x,int y ) const; int getHeight( ) const {return mHeight;} int getWidth( ) const {return mWidth;} protected: T mCells[ WIDTH ][ HEIGHT ]; }; template<typename T,int WIDTH,int HEIGHT> void Grid<T,WIDTH,HEIGHT>::setElementAt( int x,int y,const T& inElem ) //注意格式 { mCells[ x ][ y ]=inElem; } 在模板类表指定无类型参数的主要优点是,代码在进行编译之前就已经知道参数的值了。 注意: 无类型模板参数变成了实例化对象类型规范的一部分。 也就是说Grid<int,10,10>与Grid<int,12,13>属于两种不同的类型。 无类型参数会变成类的特性的一部分,这是值得关注的一点。 C++允许使用类似的语法给模板参数提供默认值: template<typename T,int WIDTH=10,int HEIGHT=10> class Grid{...}; 3、方法模板 C++允许对类的单个方法进行模板化。这些方法可以出现在类模板中,也可以出现在非模板类中。 注意:不能模板化虚方法和析构函数。 //该版本采用了方法模板,来实现类模板不同实例对象之间的赋值 //grid.h #ifndef GRID_H #define GRID_H template<typename T> class Grid { public: Grid( int inWidth=kDefaultWidth,int inHeight=kDefaultHeight); Grid( const Grid<T>& src ); //注意,仍保留原来的方法 template <typename E> //方法模板 Grid( const Grid<E>& src ); ~Grid( ); Grid<T>& operator=( const Grid<T>& rhs ); template <typename E> //方法模板,注意参数类型和返回类型 Grid<T>& operator=( const Grid<E>& rhs ); void setElementAt( int x.int y,const T& inElem ); T& getElement( int x,int y ); const T& getElement( int x,int y ) const; int getHeight( ) const {return mHeight;} int getWidth( ) const {return mWidth;} static const int kDefaultWidth=10; static const int kDefaultHeight=10; protected: void copyForm( const Grid<T>& src ); template <typename E> //方法模板 void copyForm( const Grid<E>& src ); T** mCells; int mWidth,mHeight; }; template <typename T> template <typename E> Grid<T>::Grid(const Grid<E>& src) { copyForm(src); } 注意:成员模板方法不会取代同名的非模板的成员方法。 如果编写了复制构造函数和operator=的模板化版本,并去掉了非模板化的复制构造函数和operator=,编译器不会为同一类型的类对象复制运算调用这些新的模板化构造函数和operator=,也就是E不会包含T。相反,编译器会生成一个复制构造函数和operator=,来完成两个同类型网格的创建和赋值,而这不是我们想要的。因此,还必须保留老的非模板化的复制构造函数和operator=。 注意: 一些编译器要求在类定义中内联提供方法模板的定义,但是C++标准允许在类定义外部定义方法模板。 4、带有无类型参数的方法模板 //此类的定义采用了无类型参数以及方法模板进行定义类 template<typename T,int WIDTH=10,int HEIGHT=10> class Grid { public: Grid( ){} template <typename E,int WIDTH2,int HEIGHT2> Grid(const Grid<E,WIDTH2,HEIGHT2>& src ); template <typename E,int WIDTH2,int HEIGHT2> Grid<T,WIDTH,HEIGHT>& operator=( const Grid<E,WIDTH2,HEIGHT2>& rhs ); void setElementAt( int x,int y,const T& inElem ); T& getElementAt( int x,int y ); const T& getElementAt( int x,int y ) const; int getHeight( ) const {return mHeight;} int getWidth( ) const {return mWidth;} protected: template<typename E,int WIDTH2,int HEIGHT2> void copyForm( const Grid<E,WIDTH2,HEIGHT2>& src ); T mCells[ WIDTH ][ HEIGHT ]; }; template<typename T,int WIDTH,int HEIGHT> template<typename E,int WIDTH2,int HEIGHT2> Grid<T,WIDTH,HEIGHT>::Grid( const Grid<E,WIDTH2,HEIGHT2>& src ) { copyForm( src ); } 注意: 零初始化(zero-intialization): mCells[i][j]=T(); 若T是简单的基本类型,则返回0;若为类,则会调用构造函数。 5、模板类特殊化template specialization //模板特殊化 //必须要求按照一定的格式,另外要包含模板了的定义 //特殊化模板的时候,不会继承任何代码,必须全部重写类的实现 #include "grid.h" template <> class Grid<char*> { public: Grid( int inWidth=kDefaultWidth,int inHeight=kDefaultHeight); Grid( const Grid<char*>& src ); ~Grid( ); Grid<char*>& operator=( const Grid<char*>& rhs ); void setElementAt( int x.int y,const char* inElem ); char& getElement( int x,int y ); const char& getElement( int x,int y ) const; int getHeight( ) const {return mHeight;} int getWidth( ) const {return mWidth;} static const int kDefaultWidth=10; static const int kDefaultHeight=10; protected: void copyForm( const Grid<char*>& src ); char*** mCells; int mWidth,mHeight; }; //在编写类实现的时候,可以不用在些template<typename T> Grid<char*>::Grid( const Grid<char*>& src ) { copyForm( src ); } 6、从模板类派生子类 可以编写类模板的子类。如果子类是从模板类本身继承的话,那么子类也要成为模板。或者,可以编写从模板类特定实例化继承的子类,这种情况下,不要求子类是模板。 比如,有一个父类模板Grid<T>和一个子类模板GameBoard<T>,我们不应该说GameBorad类继承了Grid类,而应该说: GameBoard模板针对特定类型的各个实例化类型是派生自Grid模板针对该类型实例化的类的子类。 注意:继承和特殊化是有区别的。 要采用继承来扩展实现多态性;而使用特殊化为特定类型建立定制实现。 7、函数模板 template<typename T> int Find( T& value,T* arr,int size ) { for( int i=0;i<size;++i ) { if( arr[ i ]==value ) return ( i ); } return ( -1 ); } 针对函数模板的特殊化: template <> int Find<char*>( char*& value,char** arr,int size ) { for( int i=0;i<size;++i ) { if( strcmp( arr[ i ],value)==0 ) return ( i ); } return ( -1 ); } 函数模板的重载: 我们也可以使用非模板函数来重载模板函数。如: int Find( char*& value,char** arr,int size ) { for( int i=0;i<size;++i ) { if( strcmp( arr[ i ],value)==0 ) return ( i ); } return ( -1 ); } 虽然,重载的函数和原来模板的实例化函数作用一样,但是在程序运行,进行调用的时候是不一样: Find<char*>(word,arr,4); //Calls the Find template with T=char* Find(word,arr,4); //Calls the Find nontemplate function! 与实例化的模板函数相比,编译器会更喜欢非模板的函数,所以,除非显示调用指名,否则编译器会使用非模板类函数。 像模板类方法一样,函数模板定义(不仅仅是原型)必须对使用他们的所有源文件都可见。因此,如果不只一个源文件要用到函数模板的定义,就应该把这些定义放在头文件中。 8、类模板的友元函数模板 //Grid.h #include <iostream> using std::ostream; //Forward declare Grid template template<typename T> class Grid; //Prototype(原型) for templatized operator<< template<typename T> ostream& operator<<( ostream& out,const Grid<T>& grid ); template<typename T> class Grid { public: //Omitted for brevity friend ostream& operator<< <T>( ostream& out,const Grid<T>& grid ); //需要注意要特别的声明为<T> //Omitted for brevity }; 我们应该注意其声明的格式。另外,它们都使用T,说明在类实例化和函数实例化之间有一个一对一的友元映射关系。 9、类模板的部分特殊化 类模板的参数只有部分被特殊化,如,原来的类模板为: template<typename T,int WIDTH,int HEIGHT> class Grid{...}; 进行部分初始化: template<int WIDTH,int HEIGHT> class Grid<char*,WIDTH,HEIGHT> {...}; 进行语法定义的时候: template<int WIDTH,int HEIGHT> class Grid<char*,WIDTH,HEIGHT>::Grid() { //... } 上面这个例子并没有真正展示出部分特殊化的真正作用。可以给部分可能的类型(只是所有类型的一个子集)编写特殊化的实现,而不是各个类型都进行特殊化。例如,可以针对所有指针类型编写Grid类的特殊化。这个特殊化用于完成指针的深复制。 格式为: template<typename T> class Grid<T*> {...}; 定义方法时: template<typename T> Grid<T*>::Grid(const Grid<T*>& src) {...} 需要注意,如果像这样实例化一个网格:Grid<int*> myIntGrid;那么T实际是int而不是int*。虽然,语法给人感觉别扭,但是它正是这样 工作的。 10、利用重载模板函数来实现部分特殊化 注意:C++标准不允许对模板函数进行部分特殊化。或许有的编译器支持模板函数部分特殊化,但是这样的代码将不是可移植的。 错误写法: template <typename T> int Find<T*>( T*& value,T** arr,int size ) { for( int i=0;i<size;++i ) if( *arr[ i ]==*value ) return ( i ); return ( -1 ); } 正确的放法是重新编写一个模板: template <typename T> int Find( T*& value,T** arr,int size ) { for( int i=0;i<size;++i ) if( *arr[ i ]==*value ) return ( i ); return ( -1 ); } 按照正确的方法进行编写的代码,将是可移植的正确的。 注意: 对于各种模板的特殊实例化和重载,编译器总是选择“最特定”的函数,非模板函数总是比模板函数更优先。