下面出现的例子中,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()不是基类的方法,用于基类对象会发生错误 4.多态 如果没有使用关键字virtual,程序根据引用类型或指针类型选择方法;如果用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。 view plaincopy to clipboardprint? Brass dom(...); BrassPlus dot(...); Brass & b1 = dom; Brass & b2 = dot; //behavior with non-virtual ViewAcct() b1.ViewAcct(); // use Brass::ViewAcct() b2.ViewAcct(); // use Brass::ViewAcct() //behavior with virtual ViewAcct() b1.ViewAcct(); // use Brass::ViewAcct() b2.ViewAcct(); // use BrassPlus::ViewAcct() Brass dom(...); BrassPlus dot(...); Brass & b1 = dom; Brass & b2 = dot; //behavior with non-virtual ViewAcct() b1.ViewAcct(); // use Brass::ViewAcct() b2.ViewAcct(); // use Brass::ViewAcct() //behavior with virtual ViewAcct() b1.ViewAcct(); // use Brass::ViewAcct() b2.ViewAcct(); // use BrassPlus::ViewAcct() 非构造函数不能使用成员初始化列表句法,但派生类方法可以调用公有的基类方法。在派生类方法中,标准的技术是使用作用域解析操作符来调用基类方法。如: view plaincopy to clipboardprint? void BrassPlus::ViewAcct() const { ... Brass::ViewAcct(); ... } void BrassPlus::ViewAcct() const { ... Brass::ViewAcct(); ... } 如果写成void BrassPlus::ViewAcct() const{ ... ViewAcct(); ...}将创建一个不会终止的递归函数。不过如果派生类没有重新定义某基类的方法,则代码不必对该方法使用作用域解析操作符。 假设要同时管理Brass和BrassPlus账户,如果能使用同一个数组来保存Brass和BrassPlus对象,将很有帮助,但这是不可能的,数组中所有元素的类型必须相同。不过,可以创建指向Brass的指针数组,这样,每个元素的类型都相同,但由于是公有继承模型,因此Brass指针既可以指向Brass对象,也可以指向BrassPlus对象。因此,可以使用一个数组来表示多种类型的对象,这就是多态性。 下面是一个多态的例子: view plaincopy to clipboardprint? // brass.h -- bank account classes #ifndef BRASS_H_ #define BRASS_H_ // Brass Account Class class Brass { private: enum {MAX = 35}; char fullName[MAX]; long acctNum; double balance; public: Brass(const char *s = "Nullbody", long an = -1, double bal = 0.0); void Deposit(double amt); virtual void Withdraw(double amt); double Balance() const; virtual void ViewAcct() const; virtual ~Brass() {} }; //Brass Plus Account Class class BrassPlus : public Brass { private: double maxLoan; double rate; double owesBank; public: BrassPlus(const char *s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.10); BrassPlus(const Brass & ba, double ml = 500, double r = 0.1); virtual void ViewAcct()const; virtual void Withdraw(double amt); void ResetMax(double m) { maxLoan = m; } void ResetRate(double r) { rate = r; }; void ResetOwes() { owesBank = 0; } }; #endif // brass.h -- bank account classes #ifndef BRASS_H_ #define BRASS_H_ // Brass Account Class class Brass { private: enum {MAX = 35}; char fullName[MAX]; long acctNum; double balance; public: Brass(const char *s = "Nullbody", long an = -1, double bal = 0.0); void Deposit(double amt); virtual void Withdraw(double amt); double Balance() const; virtual void ViewAcct() const; virtual ~Brass() {} }; //Brass Plus Account Class class BrassPlus : public Brass { private: double maxLoan; double rate; double owesBank; public: BrassPlus(const char *s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.10); BrassPlus(const Brass & ba, double ml = 500, double r = 0.1); virtual void ViewAcct()const; virtual void Withdraw(double amt); void ResetMax(double m) { maxLoan = m; } void ResetRate(double r) { rate = r; }; void ResetOwes() { owesBank = 0; } }; #endif view plaincopy to clipboardprint? // brass.cpp -- bank account class methods #include <iostream> #include <cstring> #include "brass.h" using std::cout; using std::ios_base; using std::endl; // Brass methods Brass::Brass(const char *s, long an, double bal) { std::strncpy(fullName, s, MAX - 1); fullName[MAX - 1] = '\0'; acctNum = an; balance = bal; } void Brass::Deposit(double amt) { if (amt < 0) cout << "Negative deposit not allowed; " << "deposit is cancelled.\n"; else balance += amt; } void Brass::Withdraw(double amt) { if (amt < 0) cout << "Withdrawal amount must be positive; " << "withdrawal canceled.\n"; else if (amt <= balance) balance -= amt; else cout << "Withdrawal amount of $" << amt << " exceeds your balance.\n" << "Withdrawal canceled.\n"; } double Brass::Balance() const { return balance; } void Brass::ViewAcct() const { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); cout << "Client: " << fullName << endl; cout << "Account Number: " << acctNum << endl; cout << "Balance: $" << balance << endl; cout.setf(initialState); // restore original format } // BrassPlus Methods BrassPlus::BrassPlus(const char *s, long an, double bal, double ml, double r) : Brass(s, an, bal) { maxLoan = ml; owesBank = 0.0; rate = r; } BrassPlus::BrassPlus(const Brass & ba, double ml, double r) : Brass(ba) // uses implicit copy constructor { maxLoan = ml; owesBank = 0.0; rate = r; } // redefine how ViewAcct() works void BrassPlus::ViewAcct() const { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); Brass::ViewAcct(); // display base portion cout << "Maximum loan: $" << maxLoan << endl; cout << "Owed to bank: $" << owesBank << endl; cout << "Loan Rate: " << 100 * rate << "%\n"; cout.setf(initialState); } // redefine how Withdraw() works void BrassPlus::Withdraw(double amt) { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); double bal = Balance(); if (amt <= bal) Brass::Withdraw(amt); else if ( amt <= bal + maxLoan - owesBank) { double advance = amt - bal; owesBank += advance * (1.0 + rate); cout << "Bank advance: $" << advance << endl; cout << "Finance charge: $" << advance * rate << endl; Deposit(advance); Brass::Withdraw(amt); } else cout << "Credit limit exceeded. Transaction cancelled.\n"; cout.setf(initialState); } // brass.cpp -- bank account class methods #include <iostream> #include <cstring> #include "brass.h" using std::cout; using std::ios_base; using std::endl; // Brass methods Brass::Brass(const char *s, long an, double bal) { std::strncpy(fullName, s, MAX - 1); fullName[MAX - 1] = '\0'; acctNum = an; balance = bal; } void Brass::Deposit(double amt) { if (amt < 0) cout << "Negative deposit not allowed; " << "deposit is cancelled.\n"; else balance += amt; } void Brass::Withdraw(double amt) { if (amt < 0) cout << "Withdrawal amount must be positive; " << "withdrawal canceled.\n"; else if (amt <= balance) balance -= amt; else cout << "Withdrawal amount of $" << amt << " exceeds your balance.\n" << "Withdrawal canceled.\n"; } double Brass::Balance() const { return balance; } void Brass::ViewAcct() const { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); cout << "Client: " << fullName << endl; cout << "Account Number: " << acctNum << endl; cout << "Balance: $" << balance << endl; cout.setf(initialState); // restore original format } // BrassPlus Methods BrassPlus::BrassPlus(const char *s, long an, double bal, double ml, double r) : Brass(s, an, bal) { maxLoan = ml; owesBank = 0.0; rate = r; } BrassPlus::BrassPlus(const Brass & ba, double ml, double r) : Brass(ba) // uses implicit copy constructor { maxLoan = ml; owesBank = 0.0; rate = r; } // redefine how ViewAcct() works void BrassPlus::ViewAcct() const { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); Brass::ViewAcct(); // display base portion cout << "Maximum loan: $" << maxLoan << endl; cout << "Owed to bank: $" << owesBank << endl; cout << "Loan Rate: " << 100 * rate << "%\n"; cout.setf(initialState); } // redefine how Withdraw() works void BrassPlus::Withdraw(double amt) { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); double bal = Balance(); if (amt <= bal) Brass::Withdraw(amt); else if ( amt <= bal + maxLoan - owesBank) { double advance = amt - bal; owesBank += advance * (1.0 + rate); cout << "Bank advance: $" << advance << endl; cout << "Finance charge: $" << advance * rate << endl; Deposit(advance); Brass::Withdraw(amt); } else cout << "Credit limit exceeded. Transaction cancelled.\n"; cout.setf(initialState); } view plaincopy to clipboardprint? // usebrass2.cpp -- polymorphic example // compile with brass.cpp #include <iostream> #include "brass.h" const int CLIENTS = 4; const int LEN = 40; int main() { using std::cin; using std::cout; using std::endl; Brass * p_clients[CLIENTS]; int i; for (i = 0; i < CLIENTS; i++) { char temp[LEN]; long tempnum; double tempbal; char kind; cout << "Enter client's name: "; cin.getline(temp, LEN); cout << "Enter client's account number: "; cin >> tempnum; cout << "Enter opening balance: $"; cin >> tempbal; cout << "Enter 1 for Brass Account or " << "2 for BrassPlus Account: "; while (cin >> kind && (kind != '1' && kind != '2')) cout <<"Enter either 1 or 2: "; if (kind == '1') p_clients = new Brass(temp, tempnum, tempbal); else { double tmax, trate; cout << "Enter the overdraft limit: $"; cin >> tmax; cout << "Enter the interest rate " << "as a decimal fraction: "; cin >> trate; p_clients = new BrassPlus(temp, tempnum, tempbal, tmax, trate); } while (cin.get() != '\n') continue; } cout << endl; for (i = 0; i < CLIENTS; i++) { p_clients ->ViewAcct(); cout << endl; } for (i = 0; i < CLIENTS; i++) { delete p_clients ; // free memory } cout << "Done.\n"; return 0; } // usebrass2.cpp -- polymorphic example // compile with brass.cpp #include <iostream> #include "brass.h" const int CLIENTS = 4; const int LEN = 40; int main() { using std::cin; using std::cout; using std::endl; Brass * p_clients[CLIENTS]; int i; for (i = 0; i < CLIENTS; i++) { char temp[LEN]; long tempnum; double tempbal; char kind; cout << "Enter client's name: "; cin.getline(temp, LEN); cout << "Enter client's account number: "; cin >> tempnum; cout << "Enter opening balance: $"; cin >> tempbal; cout << "Enter 1 for Brass Account or " << "2 for BrassPlus Account: "; while (cin >> kind && (kind != '1' && kind != '2')) cout <<"Enter either 1 or 2: "; if (kind == '1') p_clients = new Brass(temp, tempnum, tempbal); else { double tmax, trate; cout << "Enter the overdraft limit: $"; cin >> tmax; cout << "Enter the interest rate " << "as a decimal fraction: "; cin >> trate; p_clients = new BrassPlus(temp, tempnum, tempbal, tmax, trate); } while (cin.get() != '\n') continue; } cout << endl; for (i = 0; i < CLIENTS; i++) { p_clients ->ViewAcct(); cout << endl; } for (i = 0; i < CLIENTS; i++) { delete p_clients ; // free memory } cout << "Done.\n"; return 0; } 要使用虚拟析构函数: 不适当的代码将组织动态联编: 5.虚函数的工作原理 6.重新定义隐藏方法(虚方法参数列表不匹配时情况) 如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本,例外的情况是基类版本是隐藏的。 7.protected成员与单设计模式 protected与private相似,在类外只能用公有类成员来访问protected部分中的类成员。他们之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说,保护成员的行为与私有成员相似;但对于派生类来说,保护成员的行为与公有成员相似。 关于单设计模式: 8.抽象基类 abstract base class,ABC。当类声明中包含纯虚函数时, 则不能创建该类的对象。纯虚函数声明的结尾处为=0。包含纯虚函数的类只用作基类,要成为真正的ABC,必须至少包含一个纯虚函数。 view plaincopy to clipboardprint? // acctabc.h -- bank account classes #ifndef ACCTABC_H_ #define ACCTABC_H_ // Abstract Base Class class AcctABC { private: enum {MAX = 35}; char fullName[MAX]; long acctNum; double balance; protected: const char * FullName() const {return fullName;} long AcctNum() const {return acctNum;} std::ios_base::fmtflags SetFormat() const; public: AcctABC(const char *s = "Nullbody", long an = -1, double bal = 0.0); void Deposit(double amt) ; virtual void Withdraw(double amt) = 0; // pure virtual function double Balance() const {return balance;}; virtual void ViewAcct() const = 0; // pure virtual function virtual ~AcctABC() {} }; // Brass Account Class class Brass :public AcctABC { public: Brass(const char *s = "Nullbody", long an = -1, double bal = 0.0) : AcctABC(s, an, bal) { } virtual void Withdraw(double amt); virtual void ViewAcct() const; virtual ~Brass() {} }; //Brass Plus Account Class class BrassPlus : public AcctABC { private: double maxLoan; double rate; double owesBank; public: BrassPlus(const char *s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.10); BrassPlus(const Brass & ba, double ml = 500, double r = 0.1); virtual void ViewAcct()const; virtual void Withdraw(double amt); void ResetMax(double m) { maxLoan = m; } void ResetRate(double r) { rate = r; }; void ResetOwes() { owesBank = 0; } }; #endif // acctabc.h -- bank account classes #ifndef ACCTABC_H_ #define ACCTABC_H_ // Abstract Base Class class AcctABC { private: enum {MAX = 35}; char fullName[MAX]; long acctNum; double balance; protected: const char * FullName() const {return fullName;} long AcctNum() const {return acctNum;} std::ios_base::fmtflags SetFormat() const; public: AcctABC(const char *s = "Nullbody", long an = -1, double bal = 0.0); void Deposit(double amt) ; virtual void Withdraw(double amt) = 0; // pure virtual function double Balance() const {return balance;}; virtual void ViewAcct() const = 0; // pure virtual function virtual ~AcctABC() {} }; // Brass Account Class class Brass :public AcctABC { public: Brass(const char *s = "Nullbody", long an = -1, double bal = 0.0) : AcctABC(s, an, bal) { } virtual void Withdraw(double amt); virtual void ViewAcct() const; virtual ~Brass() {} }; //Brass Plus Account Class class BrassPlus : public AcctABC { private: double maxLoan; double rate; double owesBank; public: BrassPlus(const char *s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.10); BrassPlus(const Brass & ba, double ml = 500, double r = 0.1); virtual void ViewAcct()const; virtual void Withdraw(double amt); void ResetMax(double m) { maxLoan = m; } void ResetRate(double r) { rate = r; }; void ResetOwes() { owesBank = 0; } }; #endif view plaincopy to clipboardprint? // acctabc.cpp -- bank account class methods #include <iostream> #include <cstring> using std::cout; using std::ios_base; using std::endl; #include "acctabc.h" // Abstract Base Class AcctABC::AcctABC(const char *s, long an, double bal) { std::strncpy(fullName, s, MAX - 1); fullName[MAX - 1] = '\0'; acctNum = an; balance = bal; } void AcctABC::Deposit(double amt) { if (amt < 0) cout << "Negative deposit not allowed; " << "deposit is cancelled.\n"; else balance += amt; } void AcctABC::Withdraw(double amt) { balance -= amt; } // protected method ios_base::fmtflags AcctABC::SetFormat() const { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); return initialState; } // Brass methods void Brass::Withdraw(double amt) { if (amt < 0) cout << "Withdrawal amount must be positive; " << "withdrawal canceled.\n"; else if (amt <= Balance()) AcctABC::Withdraw(amt); else cout << "Withdrawal amount of $" << amt << " exceeds your balance.\n" << "Withdrawal canceled.\n"; } void Brass::ViewAcct() const { ios_base::fmtflags initialState = SetFormat(); cout << "Brass Client: " << FullName() << endl; cout << "Account Number: " << AcctNum() << endl; cout << "Balance: $" << Balance() << endl; cout.setf(initialState); } // BrassPlus Methods BrassPlus::BrassPlus(const char *s, long an, double bal, double ml, double r) : AcctABC(s, an, bal) { maxLoan = ml; owesBank = 0.0; rate = r; } BrassPlus::BrassPlus(const Brass & ba, double ml, double r) : AcctABC(ba) // uses implicit copy constructor { maxLoan = ml; owesBank = 0.0; rate = r; } void BrassPlus::ViewAcct() const { ios_base::fmtflags initialState = SetFormat(); cout << "BrassPlus Client: " << FullName() << endl; cout << "Account Number: " << AcctNum() << endl; cout << "Balance: $" << Balance() << endl; cout << "Maximum loan: $" << maxLoan << endl; cout << "Owed to bank: $" << owesBank << endl; cout << "Loan Rate: " << 100 * rate << "%\n"; cout.setf(initialState); } void BrassPlus::Withdraw(double amt) { ios_base::fmtflags initialState = SetFormat(); double bal = Balance(); if (amt <= bal) AcctABC::Withdraw(amt); else if ( amt <= bal + maxLoan - owesBank) { double advance = amt - bal; owesBank += advance * (1.0 + rate); cout << "Bank advance: $" << advance << endl; cout << "Finance charge: $" << advance * rate << endl; Deposit(advance); AcctABC::Withdraw(amt); } else cout << "Credit limit exceeded. Transaction cancelled.\n"; cout.setf(initialState); } // acctabc.cpp -- bank account class methods #include <iostream> #include <cstring> using std::cout; using std::ios_base; using std::endl; #include "acctabc.h" // Abstract Base Class AcctABC::AcctABC(const char *s, long an, double bal) { std::strncpy(fullName, s, MAX - 1); fullName[MAX - 1] = '\0'; acctNum = an; balance = bal; } void AcctABC::Deposit(double amt) { if (amt < 0) cout << "Negative deposit not allowed; " << "deposit is cancelled.\n"; else balance += amt; } void AcctABC::Withdraw(double amt) { balance -= amt; } // protected method ios_base::fmtflags AcctABC::SetFormat() const { // set up ###.## format ios_base::fmtflags initialState = cout.setf(ios_base::fixed, ios_base::floatfield); cout.setf(ios_base::showpoint); cout.precision(2); return initialState; } // Brass methods void Brass::Withdraw(double amt) { if (amt < 0) cout << "Withdrawal amount must be positive; " << "withdrawal canceled.\n"; else if (amt <= Balance()) AcctABC::Withdraw(amt); else cout << "Withdrawal amount of $" << amt << " exceeds your balance.\n" << "Withdrawal canceled.\n"; } void Brass::ViewAcct() const { ios_base::fmtflags initialState = SetFormat(); cout << "Brass Client: " << FullName() << endl; cout << "Account Number: " << AcctNum() << endl; cout << "Balance: $" << Balance() << endl; cout.setf(initialState); } // BrassPlus Methods BrassPlus::BrassPlus(const char *s, long an, double bal, double ml, double r) : AcctABC(s, an, bal) { maxLoan = ml; owesBank = 0.0; rate = r; } BrassPlus::BrassPlus(const Brass & ba, double ml, double r) : AcctABC(ba) // uses implicit copy constructor { maxLoan = ml; owesBank = 0.0; rate = r; } void BrassPlus::ViewAcct() const { ios_base::fmtflags initialState = SetFormat(); cout << "BrassPlus Client: " << FullName() << endl; cout << "Account Number: " << AcctNum() << endl; cout << "Balance: $" << Balance() << endl; cout << "Maximum loan: $" << maxLoan << endl; cout << "Owed to bank: $" << owesBank << endl; cout << "Loan Rate: " << 100 * rate << "%\n"; cout.setf(initialState); } void BrassPlus::Withdraw(double amt) { ios_base::fmtflags initialState = SetFormat(); double bal = Balance(); if (amt <= bal) AcctABC::Withdraw(amt); else if ( amt <= bal + maxLoan - owesBank) { double advance = amt - bal; owesBank += advance * (1.0 + rate); cout << "Bank advance: $" << advance << endl; cout << "Finance charge: $" << advance * rate << endl; Deposit(advance); AcctABC::Withdraw(amt); } else cout << "Credit limit exceeded. Transaction cancelled.\n"; cout.setf(initialState); } 9.继承和动态内存分配 view plaincopy to clipboardprint? //基类 class baseDMA { private: char * label; int rating; public: baseDMA(const char * l = "null", int r = 0); baseDMA(const baseDMA & rs); virtual ~baseDMA(); baseDMA & operator=(const baseDMA & rs); }; //基类 class baseDMA { private: char * label; int rating; public: baseDMA(const char * l = "null", int r = 0); baseDMA(const baseDMA & rs); virtual ~baseDMA(); baseDMA & operator=(const baseDMA & rs); }; 如果基类使用动态内存分配,那么这将怎样影响派生类的实现呢。当派生类不使用new时,不需要为派生类定义显式析构函数,复制构造函数和赋值操作符。但是,当基类和派生类都采用动态内存分配时,派生类的析构函数,复制构造函数和赋值操作符都必须使用相应的基类方法来处理基类元素,这种要求是通过3种不同的方式来满足的。 view plaincopy to clipboardprint? //派生类 class hasDMA :public baseDMA { private: char * style; public: ... }; //派生类 class hasDMA :public baseDMA { private: char * style; public: ... }; 1)对于析构函数,这是自动完成的。派生类析构函数自动调用基类的析构函数,故其自身的职责是对派生类构造函数执行工作的进行清理。 view plaincopy to clipboardprint? baseDMA::~baseDMA() { delete [] label; } hasDMA::~hasDMA() { delete [] style; } baseDMA::~baseDMA() { delete [] label; } hasDMA::~hasDMA() { delete [] style; } 2)对于构造函数,这是通过在初始化成员列表中调用基类的复制构造函数来完成的,如果不这样做,将自动调用基类的默认构造函数。 view plaincopy to clipboardprint? hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) // invoke base class copy constructor { style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); } hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) // invoke base class copy constructor { style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); } hasDMA复制构造函数只能访问hasDMA的数据,因此它必须调用baseDMA复制构造函数来处理共享的baseDMA数据。没有参数类型为hasDMA引用的baseDMA构造函数,也不需要这样的构造函数。因为基类引用可以指向派生类型。 3)对于赋值操作符,这是通过使用作用域解析操作符显式地调用基类的赋值操作符来完成的。 view plaincopy to clipboardprint? baseDMA & baseDMA::operator=(const baseDMA & rs) { if (this == &rs) return *this; delete [] label; label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; return *this; } hasDMA & hasDMA::operator=(const hasDMA & hs) { if (this == &hs) return *this; baseDMA::operator=(hs); // copy base portion style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); return *this; } baseDMA & baseDMA::operator=(const baseDMA & rs) { if (this == &rs) return *this; delete [] label; label = new char[std::strlen(rs.label) + 1]; std::strcpy(label, rs.label); rating = rs.rating; return *this; } hasDMA & hasDMA::operator=(const hasDMA & hs) { if (this == &hs) return *this; baseDMA::operator=(hs); // copy base portion style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); return *this; } baseDMA::operator=(hs); 是函数表示法,相当于: *this=hs; 10.派生类友元如何访问基类的友元 view plaincopy to clipboardprint? class baseDMA { private: ... public: ... friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs); }; class hasDMA :public baseDMA { private: ... public: ... friend std::ostream & operator<<(std::ostream & os,const hasDMA & rs); }; class baseDMA { private: ... public: ... friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs); }; class hasDMA :public baseDMA { private: ... public: ... friend std::ostream & operator<<(std::ostream & os,const hasDMA & rs); }; view plaincopy to clipboardprint? std::ostream & operator<<(std::ostream & os,const baseDMA & rs) { os << "Label: " << rs.label << std::endl; os << "Rating: " << rs.rating << std::endl; return os; } std::ostream & operator<<(std::ostream & os,const hasDMA & hs) { os << (const baseDMA &) hs; os << "Style: " << hs.style << std::endl; return os; } std::ostream & operator<<(std::ostream & os,const baseDMA & rs) { os << "Label: " << rs.label << std::endl; os << "Rating: " << rs.rating << std::endl; return os; } std::ostream & operator<<(std::ostream & os,const hasDMA & hs) { os << (const baseDMA &) hs; os << "Style: " << hs.style << std::endl; return os; } 因为友元不是成员函数,所以不能使用作用域解析操作符来指出要使用哪个函数。解决方法是使用强制类型转换,以便匹配原型时能够选择正确的函数。 11. 类方法返回对象还是返回引用 在编码方面,直接返回对象与返回引用之间唯一的区别在于函数原型和函数头: view plaincopy to clipboardprint? Star nova1(const Star &); //returns a Star object Star & nova2(const Star &); //returns a reference to a Star Star nova1(const Star &); //returns a Star object Star & nova2(const Star &); //returns a reference to a Star 直接返回对象与按值传递对象相似:它们都生成临时拷贝。同样,返回引用与按引用传递对象相似:调用和被调用的函数对同一个对象进行操作。大部分情况下,返回引用可节省时间和内存,不过并不可以总是返回引用,函数不能返回在函数中创建的临时对象的引用。通用的规则是:如果函数返回在函数中创建的临时对象,则不要使用引用。如果函数返回的是通过引用或指针传递给它的对象,则应按引用返回对象。 12.类函数小结 本文来自CSDN博客,转载请标明出处: 本文来自CSDN博客,转载请标明出处: 本文来自CSDN博客,转载请标明出处: