包含对象成员的类
valarray类是由头文件valarray支持的。顾名思义,这个类用于处理数值,他支持诸如将数组中的所有元素的值想家以及在数组中找出最大和最小的值等操作。valarray被定义为一个模板类,以便能够处理不同的数据类型。
一个Student类----一个getline导致构造函数递归的类
// readBook2.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "valarray" #include "string" class CStudent { public: typedef std::valarray<double> ArrayDb; CStudent() : sz_Name( "Null Student" ), m_ArrScores() { } CStudent( const string& name ) : sz_Name( name ), m_ArrScores() { } explicit CStudent( int n ) : sz_Name( "Nully" ), m_ArrScores( n ) { } CStudent( const string& name, int n ) : sz_Name( name ), m_ArrScores( n ) { } CStudent( const string& name, const ArrayDb& a ) : sz_Name( name ), m_ArrScores( a ) { } CStudent( const char* name, const double* pd, int n ) : sz_Name( name ), m_ArrScores( pd, n ) { } double Average() const { if ( m_ArrScores.size() > 0 ) { return ( m_ArrScores.sum() / m_ArrScores.size() ); } else { return 0; } } const string& GetName() const { return sz_Name; } double& operator[]( int i) { return m_ArrScores[ i ]; } double operator[]( int i ) const { return m_ArrScores[ i ]; } ostream& CStudent::arr_out( ostream& os ) const { int i; int lim = m_ArrScores.size(); if ( lim > 0 ) { for ( i = 0; i < lim; i++ ) { os << m_ArrScores[ i ] << " "; if ( 4 == i % 5 ) { os << endl; } } if ( 0 != i % 5 ) { os << endl; } } else { os << "empty array"; } return os; } friend istream& operator >>( istream& is, CStudent& stu ); friend istream& operator <<( istream& os, const CStudent& stu ); friend istream& getline( istream& is, const CStudent& stu ); ~CStudent(){}; private: string sz_Name; ArrayDb m_ArrScores; }; istream& operator >>( istream& is, CStudent& stu ) { is >> stu.sz_Name; return is; } ostream& operator <<( ostream& os, const CStudent& stu ) { os << "this student name is:" << stu.GetName() << endl; os << "this student scores is:" << endl; stu.arr_out( os ); return os; } istream& getline( istream& is, const CStudent& stu ) { getline( is, stu.sz_Name ); return is; } const int puplis = 3; const int quizzes = 5; void set( CStudent& sa, int n ); int _tmain(int argc, _TCHAR* argv[]) { CStudent ada[ puplis ] = { CStudent( quizzes ), CStudent( quizzes ), CStudent( quizzes ) }; int i; for ( i = 0; i < puplis; ++i ) { set( ada[ i ], quizzes ); } cout << "\nStudent List:" << endl; for ( i = 0; i < puplis; ++i ) { cout << ada[ i ].GetName() << endl; } cout << "\nResults:" << endl; for ( i = 0; i < puplis; i++ ) { cout << endl << ada[ i ]; cout << "average" << ada[ i ].Average() << endl; } cout << "Done." << endl; return 0; } void set( CStudent& sa, int n ) { cout << "Please enter the student name:" << endl; getline( cin, sa ); cout << "Please enter " << n << "quiz scores:" << endl; for ( int i = 0; i < n; i++ ) { cin >> sa[ i ]; } while( '\n' != cin.get() ) { continue; } }
// 在
istream& getline( istream& is, const CStudent& stu )
{
getline( is, stu.sz_Name );
return is;
}
//const CStudent& stu导致递归
修改之后的版本
// readBook2.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "valarray" #include "string" class CStudent { public: typedef std::valarray<double> ArrayDb; CStudent() : sz_Name( "Null Student" ), m_ArrScores() { } CStudent( const string& name ) : sz_Name( name ), m_ArrScores() { } explicit CStudent( int n ) : sz_Name( "Nully" ), m_ArrScores( n ) { } CStudent( const string& name, int n ) : sz_Name( name ), m_ArrScores( n ) { } CStudent( const string& name, const ArrayDb& a ) : sz_Name( name ), m_ArrScores( a ) { } CStudent( const char* name, const double* pd, int n ) : sz_Name( name ), m_ArrScores( pd, n ) { } double Average() const { if ( m_ArrScores.size() > 0 ) { return ( m_ArrScores.sum() / m_ArrScores.size() ); } else { return 0; } } const string& GetName() const { return sz_Name; } double& operator[]( int i) { return m_ArrScores[ i ]; } double operator[]( int i ) const { return m_ArrScores[ i ]; } ostream& CStudent::arr_out( ostream& os ) const { int i; int lim = m_ArrScores.size(); if ( lim > 0 ) { for ( i = 0; i < lim; i++ ) { os << m_ArrScores[ i ] << " "; if ( 4 == i % 5 ) { os << endl; } } if ( 0 != i % 5 ) { os << endl; } } else { os << "empty array"; } return os; } friend istream& operator >>( istream& is, CStudent& stu ); friend istream& operator <<( istream& os, const CStudent& stu ); friend istream& getline( istream& is, CStudent& stu ); ~CStudent(){}; private: string sz_Name; ArrayDb m_ArrScores; }; istream& operator >>( istream& is, CStudent& stu ) { is >> stu.sz_Name; return is; } ostream& operator <<( ostream& os, const CStudent& stu ) { os << "this student name is:" << stu.GetName() << endl; os << "this student scores is:" << endl; stu.arr_out( os ); return os; } istream& getline( istream& is, CStudent& stu ) { getline( is, stu.sz_Name ); return is; } const int puplis = 3; const int quizzes = 5; void set( CStudent& sa, int n ); int _tmain(int argc, _TCHAR* argv[]) { CStudent ada[ puplis ] = { CStudent( quizzes ), CStudent( quizzes ), CStudent( quizzes ) }; int i; for ( i = 0; i < puplis; ++i ) { set( ada[ i ], quizzes ); } cout << "\nStudent List:" << endl; for ( i = 0; i < puplis; ++i ) { cout << ada[ i ].GetName() << endl; } cout << "\nResults:" << endl; for ( i = 0; i < puplis; i++ ) { cout << endl << ada[ i ]; cout << "average" << ada[ i ].Average() << endl; } cout << "Done." << endl; return 0; } void set( CStudent& sa, int n ) { cout << "Please enter the student name:"; getline( cin, sa ); cout << "Please enter " << n << "quiz scores:" << endl; for ( int i = 0; i < n; i++ ) { cin >> sa[ i ]; } while( '\n' != cin.get() ) { continue; } }
私有继承
c++还有另一种实现has-a关系的途径----私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
使用公有继承,基类的公有方法将成为派生类的公有方法。简而言之,派生类将继承基类的接口,这是is-a关系的一部分。使用私有继承,基类的公有方法将成为派生类的私有方法。简而言之,派生类不能继承基类的接口。正如从被包含对象中看到的,这种不完全继承是has-a关系的一部分。
因此私有继承提供的特性与包含相同:获得实现,但不获得接口。所以,私有继承也可以用来实现has-a关系。
#include "stdafx.h" #include "iostream" using namespace std; #include "valarray" #include "string" class CStudent : private valarray<double>, private string { private: typedef std::valarray<double> ArrayDb; public: CStudent() : string( "Null Student" ), ArrayDb() { } CStudent( const string& name ) : string( name ), ArrayDb() { } explicit CStudent( int n ) : string( "Nully" ), ArrayDb( n ) { } CStudent( const string& name, int n ) : string( name ), ArrayDb( n ) { } CStudent( const string& name, const ArrayDb& a ) : string( name ), ArrayDb( a ) { } CStudent( const char* name, const double* pd, int n ) : string( name ), ArrayDb( pd, n ) { } ~CStudent(){}; double Average() const { if ( ArrayDb::size() > 0 ) { return ( ArrayDb::sum() / ArrayDb::size() ); } else { return 0; } } const string& GetName() const { return ( const string& ) *this; } double& operator[] ( int i ) { return ArrayDb::operator[]( i ); } const double operator[] ( int i ) const { return ArrayDb::operator[]( i ); } ostream& arr_out( ostream& os ) const { int i; int lim = ArrayDb::size(); if ( lim > 0 ) { for ( i = 0; i < lim; i++ ) { // os << ArrayDb[ i ] << " "; os << ArrayDb::operator[]( i ) << " "; if ( 4 == i % 5 ) { os << endl; } } if ( 0 != i % 5 ) { os << endl; } } else { os << "empty array"; } return os; } friend istream& operator >>( istream& is, CStudent& stu ); friend istream& operator <<( istream& os, const CStudent& stu ); friend istream& getline( istream& is, CStudent& stu ); }; istream& operator >>( istream& is, CStudent& stu ) { is >> ( string& )stu; return is; } ostream& operator <<( ostream& os, const CStudent& stu ) { os << "this student name is:" << stu.GetName() << endl; os << "this student scores is:" << endl; stu.arr_out( os ); return os; } istream& getline( istream& is, CStudent& stu ) { getline( is, ( string& )stu ); return is; } const int puplis = 3; const int quizzes = 5; void set( CStudent& sa, int n ); int _tmain(int argc, _TCHAR* argv[]) { CStudent ada[ puplis ] = { CStudent( quizzes ), CStudent( quizzes ), CStudent( quizzes ) }; int i; for ( i = 0; i < puplis; ++i ) { set( ada[ i ], quizzes ); } cout << "\nStudent List:" << endl; for ( i = 0; i < puplis; ++i ) { cout << ada[ i ].GetName() << endl; } cout << "\nResults:" << endl; for ( i = 0; i < puplis; i++ ) { cout << endl << ada[ i ]; cout << "average" << ada[ i ].Average() << endl; } cout << "Done." << endl; return 0; } void set( CStudent& sa, int n ) { cout << "Please enter the student name:"; getline( cin, sa ); cout << "Please enter " << n << "quiz scores:" << endl; for ( int i = 0; i < n; i++ ) { cin >> sa[ i ]; } while( '\n' != cin.get() ) { continue; } }
const string& GetName() const { return ( const string& ) *this; }
引用stu不会自动转换为string引用,根本原因在于,在私有继承中,不在进行显示类型转换的清华下,不能讲指向派生类的引用或指针赋给基类引用或指针。不过,即使这个例子使用的是公有继承,也必须使用显示类型转换。原因之一是,如果不适用类型转换,代码is >>stu;与友元函数原型匹配,从而导致递归调用:
istream& operator >>( istream& is, CStudent& stu ) { is >> ( string& )stu; return is; }
另一个原因是,由于这个类使用的是多重继承,编译器将无法确定应转换成哪个基类,如果两个基类都提供了函数operator<<()。
使用包含还是私有继承
由于既可以使用包含,也可以使用私有继承来建立has-a关系。大多数c++程序员倾向于前者。不过私有继承所提供的特性确实比包含多。例如,假设类包含保护成员,则这样的成员在派生类中式可用的,但在继承层次机构外是不可用的。如果使用组合奖这样的类保护在另一类中,则后者将不是排成类,而是位于继承层次结构之外,因此不能访问保护成员。但通过继承的到的将是派生类,因此他能够访问保护成员。
另一种需要使用私有继承的情况是需要重新定义虚函数。派生类可以重新定义虚函数,但包含类不能。使用私有继承,重新定义的函数将只能在类中使用,而不是公有的。
多重继承(MI)
为了解决多重继承而引入虚基类,也就是继承的两个类都含有相同函数的时候,产生无法识别该函数,引入了虚基类,只需要在调用某个父类的方法的时候,加上类名限定符即可
一个例子(注意这里为了能够进行显示转换需要使用virtual public方式继承)
// testMI.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" class Worker { public: Worker( const string& Name, long Id ) : m_fullName( Name ), m_lId( Id ) { } Worker() : m_fullName( "no one" ), m_lId( 0L ) { } virtual ~Worker() = 0; virtual void Set() = 0; virtual void Show() const = 0; protected: void Data() const { cout << "Name:" << m_fullName << endl; cout << "Employee ID:" << m_lId << endl; } void Get() { getline( cin, m_fullName ); cout << "Enter worker's ID:"; cin >> m_lId; while ( '\n' != cin.get() ) { continue; } } private: string m_fullName; long m_lId; }; Worker::~Worker() { } class Waiter : virtual public Worker { public: Waiter() : Worker(), m_nPanache( 0 ) { } Waiter( const string& Name, long Id, int p = 0 ) : Worker( Name, Id ), m_nPanache( p ) { } Waiter( const Worker& rWorker, int p = 0 ) : Worker( rWorker ), m_nPanache( p ) { } void Set() { cout << "Enter waiter's name:"; Worker::Get(); Get(); } void Show() const { cout << "Category:waiter" << endl; Worker::Data(); Data(); } protected: void Data() const { cout << "Panache rating:" << m_nPanache << endl; } void Get() { cout << "Enter waiter's Panache rating:"; cin >> m_nPanache; while ( '\n' != cin.get() ) { continue; } } private: int m_nPanache; }; class Singer : virtual public Worker { public: Singer() : Worker(), voice( 0 ) { } Singer( const string& Name, long Id, int v = 0 ) : Worker( Name, Id ), voice( v ) { } Singer( const Worker& rWorker, int v = 0 ) : Worker( rWorker ), voice( v ) { } void Set() { cout << "Enter singer's name:"; Worker::Get(); Get(); } void Show() const { cout << "Category:singer" << endl; Worker::Data(); Data(); } protected: enum{ other, alto, contralto, soprano, base, baritone, tenor }; enum{ Vtypes = 7 }; void Data() const { cout << "Vocal range:" << pv[ voice ] << endl; } void Get() { cout << "Enter number for singer's vocal range:" << endl; int i; for ( i = 0; i < Vtypes; i++ ) { cout << i << ":" << pv[ i ] << " "; if ( 3 == i % 4 ) { cout << endl; } } if ( 0 != i % 4 ) { cout << endl; } cin >> voice; while ( '\n' != cin.get() ) { continue; } } private: static char* pv[ Vtypes ]; int voice; }; char* Singer::pv[ Singer::Vtypes ] = { "other", "alto", "contralto", "Soprano", "bass", "baritone", "tenor" }; class SingingWaiter : public Singer, public Waiter { public: SingingWaiter(){} SingingWaiter( const string& Name, long Id, int p = 0, int v = other ) : Worker( Name, Id ), Waiter( Name, Id, p ), Singer( Name, Id, v ) { } SingingWaiter( const Worker& rWorker, int p = 0, int v = other ) : Worker( rWorker ), Waiter( rWorker, p ), Singer( rWorker, v ) { } SingingWaiter( const Waiter& rWaiter, int v = other ) : Worker( rWaiter ), Waiter( rWaiter ), Singer( rWaiter, v ) { } SingingWaiter( const Singer& rSinger, int p = 0 ) : Worker( rSinger ), Waiter( rSinger, p ), Singer( rSinger ) { } void Set() { cout << "Enter singing waiter's name:"; Worker::Get(); Get(); } void Show() const { cout << "Category:singing waiter" << endl; Worker::Data(); Data(); } protected: void Data() const { Singer::Data(); Waiter::Data(); } void Get() { Waiter::Get(); Singer::Get(); } }; const int SIZE = 5; int _tmain(int argc, _TCHAR* argv[]) { Worker* loals[ SIZE ]; int ct; for ( ct = 0; ct < SIZE; ct++ ) { char choice; cout << "Enter the employee category:" << endl; cout << "w:waiter s:singer " << "t:sing waiter q:quit" << endl; cin >> choice; while ( NULL == strchr( "wstq", choice ) ) { cout << "Please enter a, w, s, t, or, q:"; cin >> choice; } if ( 'q' == choice ) { break; } switch ( choice ) { case 'w': loals[ ct ] = new Waiter; break; case 's': loals[ ct ] = new Singer; break; case 't': loals[ ct ] = new SingingWaiter; break; } cin.get(); loals[ ct ]->Set(); } cout << "\nHere is your staff:" << endl; int i; for ( i = 0; i < ct; i++ ) { cout << endl; loals[ i ]->Show(); } for ( i = 0; i < ct; i++ ) { delete loals[ i ]; } cout << "Done." << endl; return 0; }
类模板
模板的声明template<typename Type>,关键字typename告诉编译器,将要定义一个模板。尖括号中的内容相当于函数的参数列表。可以把关键字typename看作是变量的类型名,该变量接受类型作为其值,把Type看做是该变量的名称。
类模板里面的成员函数,每个函数头都将以相同的模板声明打头
除非编译器实现了新的export关键字,否则将模板成员函数放置在一个独立的实现文件中将无法运行。因为模板不是函数,它们不能单独编译。模板必须与特定的模板实例化请求一起使用。为此,最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。
深入探讨模板类
使用指针堆栈的方法之一是,让调用程序提供一个指针数组,其中每个指针都指向不同的字符串。把这些指针放在堆栈中是有意义的,因为每个指针都将指向不同的字符串。注意,创建不同指针时调用程序的职责,而不是堆栈的职责。堆栈的任务是管理指针,而不是创建指针。
例如,假设要模拟下面的情况。某人将一车文件夹交付给了Plodson。如果Plodson的收取篮(in-basket)是空的,他将取出车中最上面的文件夹,将其放入收取篮(in-basket)中。如果收取篮既不是空的也不是满的,Plodson将处理收取篮中最上面的文件,也可能取出车中的下一个文件,把它放入收取篮。他采取了自认为是比较鲁莽的行为----仍硬币来决定要才去的措施。
可以用一个指针数组来模拟这种情况,其中的指针指向表示车中文件的字符串。每个字符串都包含所描述的人的姓名。可以用堆栈表示收取篮,并使用第二个指针数组来表示发出篮。通过将指针从输入数组压入到堆栈中来表示将文件添加到收取篮中,同时通过从堆栈中弹出项目,并将它添加到发出篮中来表示处理文件。
堆栈类的模拟:
// testTemplate.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" #include "ctime" template<typename Type> class CStack { public: explicit CStack( int ss = SIZE ); CStack( const CStack& st ); ~CStack() { if ( items ) { delete[] items; } } public: bool isEmpty() { return ( 0 == m_nTop ); } bool isFull() { return ( m_StackSize == m_nTop ); } bool push( const Type& Item ); bool pop( Type& Item ); CStack& operator= ( const CStack& rCStack ); private: enum { SIZE = 10 }; int m_StackSize; Type* items; int m_nTop; }; template<typename Type> CStack<Type>::CStack( int ss /* = SIZE */ ) : m_StackSize( ss ), m_nTop( 0 ) { items = new Type[ m_StackSize ]; } template<typename Type> CStack<Type>::CStack( const CStack<Type>& st ) { m_StackSize = st.m_StackSize; m_nTop = st.m_nTop; items = new Type[ st.m_StackSize ]; for ( int i = 0; i < m_StackSize; i++ ) { items[ i ] = st.items[ i ]; } } template<typename Type> bool CStack<Type>::push( const Type& Item ) { if ( !isFull() ) { items[ m_nTop ] = Item; m_nTop++; return true; } return false; } template<typename Type> bool CStack<Type>::pop( Type& Item ) { /* if ( !isEmpty() ) { Item = items[ m_nTop ]; // 注意这样写是不对的,因为未满的时候items[ m_nTop ]指向未知 m_nTop--; return true; }*/ if ( m_nTop > 0) { Item = items[ --m_nTop ]; return true; } return false; } template<typename Type> CStack<Type>& CStack<Type>::operator= ( const CStack<Type>& rCStack ) { if ( rCStack == *this) { return *this; } if ( items ) { delete[] items; } m_StackSize = st.m_StackSize; m_nTop = st.m_nTop; items = new Type[ st.m_StackSize ]; for ( int i = 0; i < m_StackSize; i++ ) { items[ i ] = st.items[ i ]; } } const int NUM = 10; int _tmain(int argc, _TCHAR* argv[]) { srand( time( 0 ) ); cout << "Please enter stack size:"; int stacksize; cin >> stacksize; CStack< const char* > st( stacksize ); // in basket const char* in[ NUM ] = { "1:Hack Gilgamesh", "2:KiKi Ishtar", "3:Betty Rocker", "4:Ian Flagranti", "5:Wolfgang Kibble", "6:Portia Koop", "7:Joy Almondo", "8:Xaverie Parika", "9:Juan Moore", "10:Misha Mache" }; // out basket const char* out[ NUM ]; int processed = 0; int nextin = 0; while ( processed < NUM ) { if ( st.isEmpty() ) { st.push( in[ nextin++ ] ); } else if ( st.isFull() ) { st.pop( out[ processed++ ] ); } else if ( rand() % 2 && nextin < NUM ) { st.push( in[ nextin++ ] ); } else { st.pop( out[ processed++ ] ); } } for ( int i = 0; i < NUM; i++ ) { cout << out[ i ] << endl; } cout << "Done." << endl; return 0; }
数组模板范例和非类型参数
模板常被用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型。确实,为容器类提供可重用代码是引入模板的主要动机。
比如Array<double, 12> MyArray;其中的表达式参数可以是整形、枚举、引用或指针。因此,double m是不合法的,但double* r是合法的。另外,模板代码不能修改参数的值,也不能使用参数的地址。
可以将用于常规类的技术用于模板类。模板类可用作基类,也可用作组件类,还可用作其他模板的类型参数。
1.递归使用模板
// testTemplate2.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" template<typename T, int n> class MyArray { public: MyArray(){} explicit MyArray( const T& v ); public: virtual T& operator[]( int i ); virtual T operator[]( int i ) const; private: T ar[ n ]; }; template<typename T, int n> // init MyArray<T, n>::MyArray( const T& v ) { for ( int i = 0; i < n; i++ ) { ar[ i ] = v; } } template<typename T, int n> // init T& MyArray<T, n>::operator[]( int i ) { if ( i < 0 || i >= n) { cerr << "Error in array limits:" << i << " is out of range" << endl; exit( EXIT_FAILURE ); } return ar[ i ]; } template<typename T, int n> // init T MyArray<T, n>::operator[]( int i ) const { if ( i < 0 || i >= n) { cerr << "Error in array limits:" << i << " is out of range" << endl; exit( EXIT_FAILURE ); } return ar[ i ]; } int _tmain(int argc, _TCHAR* argv[]) { MyArray<int ,10> sums; MyArray<double, 10> aves; MyArray< MyArray<int, 5>, 10 > twodee; int i, j; for ( i = 0; i < 10; i++ ) { sums[ i ] = 0; for ( j = 0; j < 5; j++ ) { twodee[ i ][ j ] = ( i + 1 ) * ( j + 1 ); sums[ i ] += twodee[ i ][ j ]; } aves[ i ] = ( double )sums[ i ] / 10; } for ( i = 0; i < 10; i++ ) { for ( j = 0; j < 5; j++ ) { cout.width( 2 ); cout << twodee[ i ][ j ] << " "; } cout << ":sum = "; cout.width( 3 ); cout << sums[ i ] << ", average = " << aves[ i ] << endl; } cout << "Done." << endl; return 0; }
MyArray< MyArray<int, 5>, 10 > twodee;这使得twodee是一个包含10个元素的数组,其中每个元素都是一个包含5个int元素的数组。与之等价的常规数组声明如下:int twodee[ 10 ][ 5 ];在模板句法中,维的顺序与等价的二维数组相反。
2.模板中使用多个类型参数
模板可以保护多个类型参数。例如,假设希望类可以保存两种值,则可以创建并使用Pair模板来保存两个不同的值(标准模板库提供了类似的模板,名为pair)。
// testPair.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" template<typename T1, typename T2> class CPair { public: CPair(){} CPair( const T1& aval, const T2 bval ) : a( aval ), b( bval ) { } public: T1& first() { return a; } T2& second() { return b; } T1& first() const { return a } T2& second() const { return b } private: T1 a; T2 b; }; int _tmain(int argc, _TCHAR* argv[]) { CPair<string, int> ratings[ 4 ] = { CPair<string, int>( "The Purple Duke", 5 ), CPair<string, int>( "Jake's Frisco Al Fresco", 4 ), CPair<string, int>( "Mont Souffle", 5 ), CPair<string, int>( "Gertie's Eats", 3 ), }; int joins = sizeof( ratings ) / sizeof( CPair<string, int> ); cout << "Raring:\tEatery\n"; for ( int i = 0; i < joins; i++ ) { cout << ratings[ i ].second() << ":\t" << ratings[ i ].first() << endl; } cout << "Oops ! Revised rating:" << endl; ratings[ 3 ].first() = "Gertie's Fab Eat"; ratings[ 3 ].second() = 6; cout << ratings[ 3 ].second() << ":\t" << ratings[ 3 ].first() << endl; cout << "Done." << endl; return 0; }
模板的具体化
类模板与函数模板很相似,因为可以有隐式实例化、显示实例化和显示具体化,他们统称为具体化(specialization)。模板以通用类型的方式描述类,而具体化是使用具体的类型生成类声明。
1.隐式实例化
一般的永华都是隐式实例化(implicit instantiation),即他们声明一个活多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义
2.显示实例化
当使用关键字template并支出所需类型来声明类时,编译器将生成类声明的显示实例化(explicit insantiation)。声明必须位于模板定义所在的名称空间中。例如,
template class MyArray<string, 100>
将MyArray<string, 100>声明为一个类。在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。
3.显示具体化(explicit specialization)
显示具体化是特定类型(用于替换模板的通用类型)的定义。有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。在这种情况下,可以创建显示具体化。例如:
template<typename T>
class CSortedArray
{};
在排序的时候,使用>操作符来对值进行比较。对于数字,这管用;如果T表示一种类,则只用定义了T::operator>()方法,这也管用;但如果T是有char*表示的字符串,这将不管用。所以需要对其具体化
template<> class CSortedArray<char *>
{};
4.部分具体化
c++还允许部分具体化(partial specialization),即部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型
template<class T1, class T2> class CPair {};
template<class T1> class CPair<T1, int> {}:
关键字后面的<>声明的是没有被具体化的类型参数。因此,上述第二个声明将T2具体化为int,但T1保存不变。注意,如果指定所有的类型,则<>内将为空,这将导致显示具体化。
template<> class CPair<int, int> {};
// testParSpe.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; template< typename T1, typename T2 > class Test { public: Test( T1 a, T2 b ) { m_a = a; m_b = b; } void show() { cout << "the a value is:" << m_a << endl; cout << "the b value is:" << m_b << endl; } private: T1 m_a; T2 m_b; }; template< typename T1 > class Test< T1, int > { public: Test( T1 a, int b ) { m_a = a; m_b = b; } void show() { cout << "this template< typename T1 >, the a value is:" << m_a << endl; cout << "this template< typename T1 >, the b value is:" << m_b << endl; } private: T1 m_a; int m_b; }; int _tmain(int argc, _TCHAR* argv[]) { Test< double, int > test( 20.1, 4 ); test.show(); return 0; }
// testTemplateFriend.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" template <typename T> class CBeta { public: CBeta( T t, int i ) : q( t ), n( i ) {} template<typename U> U blab( U u, T t ) { return ( n.Value() + q.Value() ) * u / t; } void show() const { q.show(); n.show(); } private: template<typename V> class CHold { public: CHold( V v = 0 ) : val( v ){} void show() const { cout << val << endl; } V Value() const { return val; } private: V val; }; CHold<T> q; CHold<int> n; }; int _tmain(int argc, _TCHAR* argv[]) { CBeta<double> quy( 3.5, 3 ); quy.show(); cout << quy.blab( 10, 2.3 ) << endl; cout << "Done." << endl; return 0; }
在这个程序中CHold模板是在私有部分声明的,因此只能在CBeta类中访问他。CBeta类使用CHold模板声明了两个数据成员:
CHold<T> q;
CHold<int> n;
n是基于int类型的CHold对象,而q成员是基于T类型(CBeta模板参数)的CHold对象。
将模板用作参数
模板可以包含类型参数(如typename T)和非类型参数(例如int n)。模板还可以包含本身就是模板的参数。这种参数是模板新增的特性,用于实现STL。
// testTemplateParam.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" #include "ctime" template<typename Type> class CStack { public: explicit CStack( int ss = SIZE ); CStack( const CStack& st ); ~CStack() { if ( items ) { delete[] items; } } public: bool isEmpty() { return ( 0 == m_nTop ); } bool isFull() { return ( m_StackSize == m_nTop ); } bool push( const Type& Item ); bool pop( Type& Item ); CStack& operator= ( const CStack& rCStack ); private: enum { SIZE = 10 }; int m_StackSize; Type* items; int m_nTop; }; template<typename Type> CStack<Type>::CStack( int ss /* = SIZE */ ) : m_StackSize( ss ), m_nTop( 0 ) { items = new Type[ m_StackSize ]; } template<typename Type> CStack<Type>::CStack( const CStack<Type>& st ) { m_StackSize = st.m_StackSize; m_nTop = st.m_nTop; items = new Type[ st.m_StackSize ]; for ( int i = 0; i < m_StackSize; i++ ) { items[ i ] = st.items[ i ]; } } template<typename Type> bool CStack<Type>::push( const Type& Item ) { if ( !isFull() ) { items[ m_nTop ] = Item; m_nTop++; return true; } return false; } template<typename Type> bool CStack<Type>::pop( Type& Item ) { /* if ( !isEmpty() ) { Item = items[ m_nTop ]; // 注意这样写是不对的,因为未满的时候items[ m_nTop ]指向未知 m_nTop--; return true; }*/ if ( m_nTop > 0) { Item = items[ --m_nTop ]; return true; } return false; } template<typename Type> CStack<Type>& CStack<Type>::operator= ( const CStack<Type>& rCStack ) { if ( rCStack == *this) { return *this; } if ( items ) { delete[] items; } m_StackSize = st.m_StackSize; m_nTop = st.m_nTop; items = new Type[ st.m_StackSize ]; for ( int i = 0; i < m_StackSize; i++ ) { items[ i ] = st.items[ i ]; } } template< template<typename T> class Thing > class Crab { public: Crab(){} bool push( int iA, double fB ) { return ( s1.push( iA ) && s2.push( fB ) ); } bool pop( int& iA, double& fB ) { return ( s1.pop( iA ) && s2.pop( fB ) ); } private: Thing<int> s1; Thing<double> s2; }; int _tmain(int argc, _TCHAR* argv[]) { Crab<CStack> nebula; int nj; double nb; cout << "Enter int double pairs, such as 4 3.5 ( 0 0 to be end):" << endl; while ( cin >> nj >> nb && nj > 0 && nb > 0 ) { if ( !nebula.push( nj, nb ) ) { break; } } while ( nebula.pop( nj, nb) ) { cout << nj << ", " << nb << endl; } cout << "Done." << endl; return 0; }
在这个类里还可以使用模板参数和常规参数,例如:
template< template<typename t> class Thing, typename U, typename V>
class Crab
{
private:
Thing<U> s1;
Thing<V> s2;
}
模板类和友元
模板类声明也可以有友元。模板的友元分3类:
1.非模板友元
// testTemplateFriend2.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" template<typename T> class CHasFriend { public: CHasFriend( const T& t ) : item( t ) { ct++; } ~CHasFriend() { ct--; } friend void counts(); friend void report( const CHasFriend<T>& ); private: explicit CHasFriend( const CHasFriend& rCHasFriend ){} private: T item; static int ct; }; template<typename T> int CHasFriend<T>::ct = 0; void counts() { cout << "int count:" << CHasFriend<int>::ct; cout << " double count:" << CHasFriend<double>::ct << endl; } void report( const CHasFriend<int>& hf ) { cout << "CHasFriend item:" << hf.item << endl; } void report( const CHasFriend<double>& hf ) { cout << "CHasFriend item:" << hf.item << endl; } int _tmain(int argc, _TCHAR* argv[]) { cout << "No objects declared:"; counts(); CHasFriend<int> hfil1( 10 ); cout << "After hfil1 declared:"; counts(); CHasFriend<int> hfil2( 20 ); cout << "After hfil2 declared:"; counts(); CHasFriend<double> hfdb( 10.5 ); cout << "After hfdb declared:"; counts(); report( hfil1 ); report( hfil2 ); report( hfdb ); return 0; }
代码中声明使counts()函数成为模板所有实例化的友元。它内部的CHasFriend<int>和CHasFriend<double>是针对不同的模板的。
2.约束(bound)模板友元,即友元的类型取决于类呗实例化时的类型
// testTemplateFriend3.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" template<typename T> void counts() { cout << "this count value is:" << CHasFriend<T>::ct << endl; } template<typename T> void report( const T& hr ) { cout << "this count value is:" << hr.m_item << endl; } template<typename TT> class CHasFriend { public: CHasFriend( const TT item ) : m_item( item ) { ct++; } ~CHasFriend() { ct--; } friend void counts<TT>(); friend void report<>( const CHasFriend<TT>& hr ); private: explicit CHasFriend( const CHasFriend& rCHasFriend ){} private: TT m_item; static int ct; }; template<typename T> int CHasFriend<T>::ct = 0; int _tmain(int argc, _TCHAR* argv[]) { counts<int>(); CHasFriend<int> hfil( 10 ); CHasFriend<int> hfi2( 20 ); CHasFriend<double> hfdb( 10.5 ); report( hfil ); report( hfi2 ); report( hfdb ); cout << "counts<int>() output :" << endl; counts<int>(); cout << "counts<double>() output :" << endl; counts<double>(); return 0; }
声明中的<>指出这是模板具体化。对于report(),<>可以为空,这是因为可以从函数参数推断出模板类型参数(CHasFriend<TT>)。不过,也可以使用report< CHasFriend<TT> >( CHasFriend<TT>& )。但counts()函数没有参数,因此必须使用模板参数句法(<TT>)来指明其具体化。还需要注意的是,TT是CHasFriend类的参数类型。
3.非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元
// testTemplateFriend4.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; #include "string" template<typename T> class CManyFriend { public: CManyFriend( const T& i ) : item( i ) { } template<typename C, typename D> friend void show( C&, D& ); private: T item; }; template<typename C, typename D>void show( C& c, D& d ) { cout << c.item << " " << d.item << endl; } int _tmain(int argc, _TCHAR* argv[]) { CManyFriend<int> hfil( 10 ); CManyFriend<int> hfil2( 20 ); CManyFriend<double> hfdb( 10.5 ); cout << "hfil, hfil2:"; show( hfil, hfil2 ); cout << "hfil2, hfdb:"; show( hfil2, hfdb ); return 0; }
前面的1、2,int类具体化获得int函数具体化,依此类推。通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的:
template<typename C, typename D> friend void show( C&, D& );
但前提是必须在类内部声明模板