下面出现的例子中,RatedPlayer类为派生类,TableTennisPlayer类为基类。
1.基本知识
使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有成员也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数。
一个举例:
view plaincopy to clipboardprint? // tabtenn1.h -- simple inheritance #ifndef TABTENN1_H_ #define TABTENN1_H_ // simple base class class TableTennisPlayer { private: enum {LIM = 20}; char firstname[LIM]; char lastname[LIM]; bool hasTable; public: TableTennisPlayer (const char * fn = "none", const char * ln = "none", bool ht = false); void Name() const; bool HasTable() const { return hasTable; } ; void ResetTable(bool v) { hasTable = v; }; }; // simple derived class class RatedPlayer : public TableTennisPlayer { private: unsigned int rating; public: RatedPlayer (unsigned int r = 0, const char * fn = "none", const char * ln = "none", bool ht = false); RatedPlayer(unsigned int r, const TableTennisPlayer & tp); unsigned int Rating() { return rating; } void ResetRating (unsigned int r) {rating = r;} }; #endif // tabtenn1.h -- simple inheritance #ifndef TABTENN1_H_ #define TABTENN1_H_ // simple base class class TableTennisPlayer { private: enum {LIM = 20}; char firstname[LIM]; char lastname[LIM]; bool hasTable; public: TableTennisPlayer (const char * fn = "none", const char * ln = "none", bool ht = false); void Name() const; bool HasTable() const { return hasTable; } ; void ResetTable(bool v) { hasTable = v; }; }; // simple derived class class RatedPlayer : public TableTennisPlayer { private: unsigned int rating; public: RatedPlayer (unsigned int r = 0, const char * fn = "none", const char * ln = "none", bool ht = false); RatedPlayer(unsigned int r, const TableTennisPlayer & tp); unsigned int Rating() { return rating; } void ResetRating (unsigned int r) {rating = r;} }; #endif view plaincopy to clipboardprint? // tabtenn1.cpp -- base-class methods and derived-class methods #include "tabtenn1.h" #include <iostream> #include <cstring> // TableTennisPlayer methods TableTennisPlayer::TableTennisPlayer (const char * fn, const char * ln, bool ht) { std::strncpy(firstname, fn, LIM - 1); firstname[LIM - 1] = '\0'; std::strncpy(lastname, ln, LIM - 1); lastname[LIM - 1] = '\0'; hasTable = ht; } void TableTennisPlayer::Name() const { std::cout << lastname << ", " << firstname; } // RatedPlayer methods RatedPlayer::RatedPlayer(unsigned int r, const char * fn, const char * ln, bool ht) : TableTennisPlayer(fn, ln, ht) { rating = r; } RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp): TableTennisPlayer(tp), rating(r) { } // tabtenn1.cpp -- base-class methods and derived-class methods #include "tabtenn1.h" #include <iostream> #include <cstring> // TableTennisPlayer methods TableTennisPlayer::TableTennisPlayer (const char * fn, const char * ln, bool ht) { std::strncpy(firstname, fn, LIM - 1); firstname[LIM - 1] = '\0'; std::strncpy(lastname, ln, LIM - 1); lastname[LIM - 1] = '\0'; hasTable = ht; } void TableTennisPlayer::Name() const { std::cout << lastname << ", " << firstname; } // RatedPlayer methods RatedPlayer::RatedPlayer(unsigned int r, const char * fn, const char * ln, bool ht) : TableTennisPlayer(fn, ln, ht) { rating = r; } RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp): TableTennisPlayer(tp), rating(r) { } view plaincopy to clipboardprint? // usett1.cpp -- using base class and derived class #include <iostream> #include "tabtenn1.h" int main ( void ) { using std::cout; using std::endl; TableTennisPlayer player1("Tara", "Boomdea", false); RatedPlayer rplayer1(1140, "Mallory", "Duck", true); rplayer1.Name(); // derived object uses base method if (rplayer1.HasTable()) cout << ": has a table.\n"; else cout << ": hasn't a table.\n"; player1.Name(); // base object uses base method if (player1.HasTable()) cout << ": has a table"; else cout << ": hasn't a table.\n"; cout << "Name: "; rplayer1.Name(); cout << "; Rating: " << rplayer1.Rating() << endl; RatedPlayer rplayer2(1212, player1); cout << "Name: "; rplayer2.Name(); cout << "; Rating: " << rplayer2.Rating() << endl; return 0; } // usett1.cpp -- using base class and derived class #include <iostream> #include "tabtenn1.h" int main ( void ) { using std::cout; using std::endl; TableTennisPlayer player1("Tara", "Boomdea", false); RatedPlayer rplayer1(1140, "Mallory", "Duck", true); rplayer1.Name(); // derived object uses base method if (rplayer1.HasTable()) cout << ": has a table.\n"; else cout << ": hasn't a table.\n"; player1.Name(); // base object uses base method if (player1.HasTable()) cout << ": has a table"; else cout << ": hasn't a table.\n"; cout << "Name: "; rplayer1.Name(); cout << "; Rating: " << rplayer1.Rating() << endl; RatedPlayer rplayer2(1212, player1); cout << "Name: "; rplayer2.Name(); cout << "; Rating: " << rplayer2.Rating() << endl; return 0; }
2.派生类的构造函数
派生类不能直接访问基类的私有成员,必须通过基类方法进行访问。所以派生类构造函数只能通过基类构造函数来初始化属于基类的数据成员。创建派生类对象时,程序将首先创建基类对象。这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表句法来完成这种工作。
view plaincopy to clipboardprint? RatedPlayer::RatedPlayer(unsigned int r, const char * fn, const char * ln, bool ht) : TableTennisPlayer(fn, ln, ht) { rating = r; } 对派生类成员也可以用初始化列表句法: RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp): TableTennisPlayer(tp),rating(r) { } RatedPlayer::RatedPlayer(unsigned int r, const char * fn, const char * ln, bool ht) : TableTennisPlayer(fn, ln, ht) { rating = r; }
对派生类成员也可以用初始化列表句法:
RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp): TableTennisPlayer(tp),rating(r) { }
如果省略成员初始化列表,即如果不调用基类构造函数,程序将使用默认的基类构造函数。所以除非要使用默认构造函数,否则应该显式调用正确的基类构造函数。在派生类构造函数中,也有使用复制构造函数的时候,如上面第2个函数。如果需要使用复制构造函数,而又没有定义,编译器将自动生成一个。
派生类构造函数可以使用初始化器列表机制将值传递给基类构造函数。除虚拟基类外,类只能将值传递回相邻的基类,但后者可以使用相同的机制将信息传递给相邻的基类,依次类推。成员初始化列表只能用于构造函数。
3.派生类和基类的一些关系
1)派生类对象可以使用基类的方法,条件是方法不是私有的。
2)基类指针或引用可以在不进行显式类型转换的情况下指向派生类对象。这一规则不是可逆的,即不可以将基类对象和地址赋给派生类引用和指针。如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不使用为引用或指针类型定义的方法。这称为动态联编或晚期联编。
view plaincopy to clipboardprint? RatedPlayer rplayer(...); TableTennisPlayer & rt = rplayer; TableTennisPlayer * pt = & rplayer; TableTennisPlayer player(...); RatedPlayer & rr = player; //not allowed RatedPlayer * pr = & player; //not allowed RatedPlayer rplayer(...); TableTennisPlayer & rt = rplayer; TableTennisPlayer * pt = & rplayer; TableTennisPlayer player(...); RatedPlayer & rr = player; //not allowed RatedPlayer * pr = & player; //not allowed
3)编写的函数中,参数列表有基类引用,可指向基类对象或派生类对象。
view plaincopy to clipboardprint? void Show(const TableTennisPlayer &) { ... } TableTennisPlayer player(...); RatedPlayer rplayer(...); Show(player); //基类对象 Show(rplayer); //派生类对象 void Show(const TableTennisPlayer &) { ... } TableTennisPlayer player(...); RatedPlayer rplayer(...); Show(player); //基类对象 Show(rplayer); //派生类对象
4)编写的函数中,参数列表有基类指针,可指向基类对象或派生类对象。
view plaincopy to clipboardprint? void Show(const TableTennisPlayer *) { ... } TableTennisPlayer player(...); RatedPlayer rplayer(...); Show(&player); //基类对象 Show(&rplayer); //派生类对象 void Show(const TableTennisPlayer *) { ... } TableTennisPlayer player(...); RatedPlayer rplayer(...); Show(&player); //基类对象 Show(&rplayer); //派生类对象
5)引用兼容性属性将基类对象初始化为派生类对象。
view plaincopy to clipboardprint? RatedPlayer rplayer(...); TableTennisPlayer player(rplayer); RatedPlayer rplayer(...); TableTennisPlayer player(rplayer);
要初始化player,匹配的构造函数原型为:TableTennisPlayer(const RatedPlayer &); 类定义中没有这样的构造函数,但存在隐式复制构造函数: TableTennisPlayer (const TableTennisPlayer &); 形参是基类引用,前面说过,它可以引用派生类。因此,将player初始化为rplayer时,将要使用该构造函数。
6)将派生对象赋给基类对象。
view plaincopy to clipboardprint? RatedPlayer rplayer(...); TableTennisPlayer player; player = rplayer; RatedPlayer rplayer(...); TableTennisPlayer player; player = rplayer;
要初始化player,程序将使用隐式重载赋值操作符:TableTennisPlayer & operator= (const TableTennisPlayer &) const; 形参也是基类引用,它可以引用派生类。因此,将player初始化为rplayer时,将要使用该赋值操作符。
7)另外,如果派生类包含了这样的构造函数,即对将基类对象转换为派生类对象进行了定义,则可以将基类对象赋给派生对象。如果派生类定义了用于将基类对象赋给派生对象的赋值操作符,则也可以这么做。否则,只能进行显式强制类型转换后才能将基类对象赋给派生类对象。
view plaincopy to clipboardprint? Brass gp(...); BrassPlus temp; temp=gp; //possible? Brass gp(...); BrassPlus temp; temp=gp; //possible?
上述语句将转化为:temp.operator=(gp); 因为temp是BrassPlus对象,它调用函数为BrassPlus::operator=(const BrassPlus &)函数。因此有定义以下两种函数之一才能解决此问题:
BrassPlus(const BrassPlus & ba, double m1=500, doublle r-0.1) { ... } BrassPlus & BrassPlus::operator=(const BrassPlus &) { ... }
8)将派生类引用或指针转换为基类引用或指针被称为向上强制转换(upcasting),这使公有继承不需要进行显式类型转换。如5和6所提到的。但是相反的过程,----将基类指针或引用转换为派生类指针或引用----称为向下强制转换(downcasting),是不允许的,除非使用显式类型转换,尽管可能带来不安全的操作。
view plaincopy to clipboardprint? TableTennisPlyaer player; RaterPlayer rplayer; ... TableTennisPlyaer * pt = & rplayer; //允许向上隐式类型转换 RatedPlayer * pr = (RatedPlayer *) & player; //必须向下显式类型转换 ... pt->Show(); //安全,Show()是基类的方法,当然可用于派生类对象 pr->Special();//不安全,Special()不是基类的方法,用于基类对象会发生错误 TableTennisPlyaer player; RaterPlayer rplayer; ... TableTennisPlyaer * pt = & rplayer; //允许向上隐式类型转换 RatedPlayer * pr = (RatedPlayer *) & player; //必须向下显式类型转换 ... pt->Show(); //安全,Show()是基类的方法,当然可用于派生类对象 pr->Special();//不安全,Special()不是基类的方法,用于基类对象会发生错误