C++primer第十三章继承类

it2023-02-04  46

1.RatedPlayer

main

#include "tabtenn0.hpp" int main() { using std::cout; using std::endl; TableTennisPlayer player1("Jay", "Chou", false); RatedPlayer rplayer1(1140, "Jiang", "Huan", true); rplayer1.Name(); if(rplayer1.HasTable()) cout << ": has a table.\n"; else cout << ": hasn't a table.\n"; player1.Name(); if(player1.HasTable()) cout << ": has a table.\n"; 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; }

.hpp

#ifndef tabtenn0_hpp #define tabtenn0_hpp #include <stdio.h> #include <string> #include <iostream> using std::string; using std::cout; class TableTennisPlayer { private: string firstname; string lastname; bool hasTable; public: TableTennisPlayer(const string & fn = "none", const string & ln = "none", bool ht = false); void Name() const {cout << lastname << ", " << firstname;} bool HasTable() const {return hasTable;} void ResetTable(bool v) { hasTable = v;} }; class RatedPlayer : public TableTennisPlayer { private: unsigned int rating; public: RatedPlayer(unsigned int r = 0, const string & fn = "none", const string & 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 /* tabtenn0_hpp */

.cpp

#include "tabtenn0.hpp" TableTennisPlayer::TableTennisPlayer(const string & fn, const string & ln, bool ht) { firstname = fn; lastname = ln; hasTable = ht; } RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht):TableTennisPlayer(fn, ln, ht) { rating = r; } RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp):rating(r),TableTennisPlayer(tp) { // 调用基类复制构造函数 }

2.引用与指针在派生类和基类之间的关系

TableTennisPlayer player1("Jay", "Chou", false); TableTennisPlayer & rt = rplayer1; TableTennisPlayer *pt = &rplayer1; rt.Name(); pt->Name();

可以看到 可以用基类的引用或者指针引用或指向派生类对象。反之,则不可以。也就是派生类不能指向基类。

包括函数里面的形参如果是基类引用或者指针,它可以兼容派生类

void Show(const TableTennisPlayer &rt) { using std::cout; cout << "name: "; rt.Name(); cout<< " table: "; if(rt.HasTable()) cout << "yes\n"; else cout << "No\n"; } ...... TableTennisPlayer player1("Jay", "Chou", false); RatedPlayer rplayer1(1140, "Jiang", "Huan", true); Show(player1); Show(rplayer1); // 指针 void Show(const TableTennisPlayer *rt) { using std::cout; cout << "name: "; rt->Name(); cout<< " table: "; if(rt->HasTable()) cout << "yes\n"; else cout << "No\n"; } TableTennisPlayer player1("Jay", "Chou", false); RatedPlayer rplayer1(1140, "Jiang", "Huan", true); Show(&player1); Show(&rplayer1); TableTennisPlayer player2(rplayer1); TableTennisPlayer player3 = rplayer1;

这也可以,因为TableTennisPlayer默认复制构造函数:TableTennisPlayer(const TableTennisPlayer &);赋值构造函数:TableTennisPlayer & operator=(const TableTennisPlayer &)const; 所以都是基类是可以兼容派生类的,但反之当然不行。

3.Brass和BrassPlus

main.cpp

//#include <iostream> //#include "brass.hpp" //int main(int argc, const char * argv[]) { // using std::cout; // using std::endl; // // Brass Piggy("Porcelot Pigg", 381299, 4000.00); // BrassPlus Hoggy("Horatio Hogg", 382288, 3000.00); // Piggy.ViewAcct(); // cout << endl; // Hoggy.ViewAcct(); // cout << endl; // cout << "Depositing $1000 into the Hogg Account:\n"; // Hoggy.Deposit(1000.0); // cout << "New balance: $" << Hoggy.Balance() << endl; // cout << "Withdrawing $4200 from the Pigg Account:\n"; // Piggy.Withdraw(4200.00); // cout << "Pigg account balance: $" << Piggy.Balance() << endl; // cout << "Withdrawing $4200 from the Hogg Account:\n"; // Hoggy.Withdraw(4200.00); // Hoggy.ViewAcct(); // return 0; //} #include <iostream> #include <string> #include "brass.hpp" const int CLIENTS = 4; int main() { using namespace std; Brass * p_clients[CLIENTS]; string temp; long tempnum; double tempbal; char kind; for (int i = 0; i < CLIENTS; i++) { cout << "Enter client's name: "; getline(cin, temp); 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')) { // 如果输入正常且kind不等于1和2则重新输入 cout << "Enter either 1 or 2: "; } if (kind == '1') { p_clients[i] = new Brass(temp, tempnum, tempbal); } else { double tmax, trate; cout << "Enter the overdraft limit: $"; cin >> tmax; cout << "Enter the interst rate as a decimal fraction: "; cin >> trate; p_clients[i] = new BrassPlus(temp, tempnum, tempnum, tmax, trate); } while (cin.get()!='\n') continue; cout << endl; } for (int i = 0; i < CLIENTS; i++) { p_clients[i]->ViewAcct(); cout << endl; } for (int i = 0; i < CLIENTS; i++) { delete p_clients[i]; } cout << "Done.\n"; return 0; }

.hpp

#ifndef brass_hpp #define brass_hpp #include <stdio.h> #include <string> using std::string; class Brass { private: std::string fullName; // 客户姓名 long acctNum; // 账号 double balance; // 当前结余 public: Brass(const std::string & 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(){} }; class BrassPlus : public Brass { private: double maxLoan; // 透支上限 double rate; // 透支贷款率 double owesBank; // 当前透支额度 public: BrassPlus(const std::string & s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.11125); BrassPlus(const Brass &ba, double ml = 500, double r = 0.11125); 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_hpp */

.cpp

#include "brass.hpp" #include <iostream> using std::cout; using std::endl; using std::string; // 格式化输出 typedef std::ios_base::fmtflags format; typedef std::streamsize precis; format setFormat() { // 17章会学习 先熟悉 // set up ###.## return cout.setf(std::ios_base::fixed, std::ios_base::floatfield); } void restore(format f, precis p) { cout.setf(f, std::ios_base::floatfield); cout.precision(p); } Brass::Brass(const string & s, long an, double bal) { fullName = s; acctNum = an; balance = bal; } void Brass::Deposit(double amt) { // 存款 if(amt < 0) cout << "Negative deposit not alloweed; " << "desposit is cancelled.\n"; else balance += amt; } void Brass::Withdraw(double amt) { format initialState = setFormat(); precis prec = cout.precision(2); if(amt < 0) cout << "Withdrawal amout 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"; restore(initialState, prec); } double Brass::Balance() const { return balance; } void Brass::ViewAcct() const { format initialState = setFormat(); precis prec = cout.precision(2); cout << "Client:" << fullName << endl; cout << "Account Number: " << acctNum << endl; cout << "Balance: $" << balance << endl; restore(initialState, prec); } BrassPlus::BrassPlus(const std::string & 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) { maxLoan = ml; owesBank = 0.0; rate = r; } void BrassPlus::ViewAcct() const { format initialState = setFormat(); precis prec = cout.precision(2); Brass::ViewAcct(); cout << "Maximum loan: $" << maxLoan << endl; cout << "Owed to bank: $" << owesBank << endl; cout.precision(3); cout << "Loan Rate: " << 100*rate << "%\n"; restore(initialState, prec); } void BrassPlus::Withdraw(double amt) { format initialState = setFormat(); precis prec = 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 + rate); cout << "Bank advance: $" << advance << endl; cout << "Finance charge: $" << advance * rate << endl; Deposit(advance); Brass::Withdraw(amt); } else cout << "Credit limit exceeded."; restore(initialState, prec); }

在Brass类中可以看到析构函数也是虚函数,这样就可以使BrassPlus指针,在析构的时候,可以调用基类的析构函数进行析构。 在派生类中调用基类同样的函数时,加上作用域解析符号::

4.静态联编和动态联编

BrassPlus b1; Brass *bp; bp = &b1; bp->ViewAcct();

用上面的例子来说,如果Brass类中没有声明虚函数ViewAcct(),那么在编译阶段其实就能确定, bp->ViewAcct()调用的是Brass::ViewAcct()。这个就是静态联编, 而如果申明了虚函数,那么对象类型为BrassPlus,通常需要在运行阶段才能确定类型,并把bp->ViewAcct()关联成BrassPlus::ViewAcct()。对虚方法就是用的动态联编。

5.虚函数的工作原理

给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,这个函数就叫虚函数表。 也就是有个表会记录你的虚构函数,比如你Brass类好几个虚函数,他会记录下你虚函数的地址,如果你的BrassPlus有对基类Brass的虚函数有更新,那么地址也会更新,没有更新的虚函数,就保存这原来基类虚函数的地址,当然BrassPlus有新的虚函数,也是会新增到地址上去。 就比如说 第三点中代码中有虚析构函数,BrassPlus类没有写析构函数,那么他会按照基类的析构函数地址去执行。

6.抽象基类

还是以brass为例 main.cpp

#include <iostream> #include <string> #include "acctabc.hpp" const int CLIENTS = 4; int main() { using namespace std; AcctABC * p_clients[CLIENTS]; string temp; long tempnum; double tempbal; char kind; for (int i = 0; i < CLIENTS; i++) { cout << "Enter client's name: "; getline(cin, temp); 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')) { // 如果输入正常且kind不等于1和2则重新输入 cout << "Enter either 1 or 2: "; } if (kind == '1') { p_clients[i] = new Brass(temp, tempnum, tempbal); } else { double tmax, trate; cout << "Enter the overdraft limit: $"; cin >> tmax; cout << "Enter the interst rate as a decimal fraction: "; cin >> trate; p_clients[i] = new BrassPlus(temp, tempnum, tempnum, tmax, trate); } while (cin.get()!='\n') continue; cout << endl; } for (int i = 0; i < CLIENTS; i++) { p_clients[i]->ViewAcct(); cout << endl; } for (int i = 0; i < CLIENTS; i++) { delete p_clients[i]; } cout << "Done.\n"; return 0; }

.hpp

#ifndef acctabc_hpp #define acctabc_hpp #include <stdio.h> #include <iostream> #include <string> using std::string; class AcctABC { private: string fullName; // 名字 long acctNum; // 账号 double balance; // 余额 protected: struct Formatting { // 存储两项格式的设置 std::ios_base::fmtflags flag; std::streamsize pr; }; const string & FullName() const {return fullName;} // 返回名字 long AcctNum() const {return acctNum;} // 返回账号 Formatting setFormat() const; // 设置输出格式 ###.## void Restore(Formatting & f) const; // 恢复输出格式 public: AcctABC(const string &s = "Nullbody", long an = -1, double bal = 0.0); double Balance() const{return balance;} // 返回余额 void Deposit(double amt); // 存款 virtual void Withdraw(double amt) = 0; // 纯虚函数 取钱 实现了最基础的取款 在两个派生类进一步实现 virtual void ViewAcct() const = 0; // 纯虚函数 也可以不做任何实现 在派生类中实现 virtual ~AcctABC(){} }; class Brass:public AcctABC { public: Brass(const string &s = "Nullbody", long an = -1, double bal = 0.0):AcctABC(s, an ,bal){} virtual void Withdraw(double amt); // 重写 virtual void ViewAcct() const; // 重写 virtual ~Brass(){} }; class BrassPlus:public AcctABC { private: double maxLoan; double rate; double owesBank; public: BrassPlus(const string &s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.5); BrassPlus(const Brass & ba, double ml = 500, double r = 0.5); virtual void ViewAcct() const; // 重写 virtual void Withdraw(double amt); // 重写 void ResetMl(double m) { maxLoan = m;} void ResetRate(double r) {rate = r;} void ResetOwes() {owesBank = 0;} }; #endif /* acctabc_hpp */

.cpp

#include <iostream> #include "acctabc.hpp" using std::cout; using std::ios_base; using std::endl; using std::string; AcctABC::AcctABC(const string &s, long an, double bal) { fullName = s; acctNum = an; balance = bal; } void AcctABC::Deposit(double amt) { if(amt < 0) cout << "Negative deposit not alloweed; " << "desposit is cancelled.\n"; else balance += amt; } void AcctABC::Withdraw(double amt) { balance -= amt; } AcctABC::Formatting AcctABC::setFormat() const { Formatting f; f.flag = cout.setf(ios_base::fixed, ios_base::floatfield); f.pr = cout.precision(2); return f; } void AcctABC::Restore(Formatting & f) const { cout.setf(f.flag, ios_base::floatfield); cout.precision(f.pr); } void Brass::Withdraw(double amt) { if(amt < 0) cout << "Withdrawal amout must be positive; " << "withdrawal canceled.\n"; else if((Balance() - amt) >= 0) AcctABC::Withdraw(amt); else { cout << "Withdrawal amout of $" << amt << " exceeds your balance.\n" << "Withdrawal canceled.\n"; } } void Brass::ViewAcct() const { Formatting f = setFormat(); cout << "Brass Clinet: " << FullName() << endl; // protected cout << "Account Number: " << AcctNum() << endl; // protected cout << "Balance: $" << Balance() <<endl; // public Restore(f); } BrassPlus::BrassPlus(const string &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) { maxLoan = ml; owesBank = 0.0; rate = r; } void BrassPlus::ViewAcct() const { Formatting f = setFormat(); cout << "Brass Clinet: " << FullName() << endl; // protected cout << "Account Number: " << AcctNum() << endl; // protected cout << "Balance: $" << Balance() <<endl; // public cout << "Maximum loan: $" << maxLoan << endl; cout << "Owed to bank: $" << owesBank << endl; cout.precision(3); cout << "Loan Rate: " << 100 * rate << "%\n"; Restore(f); } void BrassPlus::Withdraw(double amt) { Formatting f = setFormat(); double bal = Balance(); if (amt <= bal) { AcctABC::Withdraw(amt); } else if(amt <= bal + maxLoan - owesBank) { double advance = amt - bal; owesBank += advance * (1 + rate); cout << "Bank advance: $" << advance << endl; cout << "Finance charge: $" << advance * rate << endl; Deposit(advance); AcctABC::Withdraw(amt); } else cout << "Credit limit exceeded.\n"; Restore(f); }

7.派生类的复制构造函数和赋值运算符

提出问题那么在派生类如果要用new delete怎么办?答案如下: main.cpp

#include<iostream> #include "dma.hpp" int main() { using std::cout; using std::endl; baseDMA shirt("Portablly", 8); lacksDMA balloon("red", "Blimpo", 4); hasDMA map("Mercator", "Buffalo Keys", 5); cout << "baseDMA object :\n"; cout << shirt << endl; cout << "baseDMA object :\n"; cout << balloon << endl; cout << "baseDMA object :\n"; cout << map << endl; lacksDMA balloon2(balloon); // 字符数组的复制构造函数 cout << "reslut of lacksDMA copy :\n"; cout << balloon2 << endl; hasDMA map2; map2 = map; // 赋值运算符重载 cout << "reslut of hasDMA :\n"; cout << map2 << endl; hasDMA map3(map); // 字符指针的复制构造函数 cout << "reslut of hasDMA :\n"; cout << map2 << endl; }

.hpp

#ifndef dma_hpp #define dma_hpp #include <stdio.h> #include <iostream> 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); friend std::ostream & operator<<(std::ostream &os,const baseDMA &rs); }; class lacksDMA : public baseDMA { private: static const int COL_LEN = 40; // 在这个类作用域内,所有对象共享这个静态变量 char color[COL_LEN]; public: lacksDMA(const char * c = "blank", const char * l = "null", int r = 0); lacksDMA(const char *c, const baseDMA & rs); friend std::ostream & operator<<(std::ostream &os,const lacksDMA &rs); }; class hasDMA : public baseDMA { private: char * style; public: hasDMA(const char * s = "blank", const char * l = "null", int r = 0); hasDMA(const char * s, const baseDMA & rs); hasDMA(const hasDMA &hs); ~hasDMA(); hasDMA & operator=(const hasDMA &rs); friend std::ostream & operator<<(std::ostream &os,const hasDMA &rs); }; #endif /* dma_hpp */

.cpp

#include "dma.hpp" #include <iostream> #include <cstring> using std::strlen; using std::strcpy; baseDMA::baseDMA(const char * l, int r) { label = new char[strlen(l) + 1]; strcpy(label, l); rating = r; } baseDMA::baseDMA(const baseDMA & rs) { label = new char[strlen(rs.label) + 1]; strcpy(label, rs.label); rating = rs.rating; } baseDMA::~baseDMA() { delete [] label; } baseDMA & baseDMA::operator=(const baseDMA & rs) { if(this == &rs) // 比较两个地址 return *this; delete [] label; label = new char[strlen(rs.label) + 1]; strcpy(label, rs.label); rating = rs.rating; return *this; } std::ostream & operator<<(std::ostream &os,const baseDMA &rs) { os << "Label: " << rs.label << std::endl; os << "Rating: " << rs.rating << std::endl; return os; } lacksDMA::lacksDMA(const char * c, const char * l, int r) : baseDMA(l ,r) { strncpy(color, c, 39); color[39] = '\0'; } lacksDMA::lacksDMA(const char *c, const baseDMA & rs):baseDMA(rs) { strncpy(color, c, 39); color[39] = '\0'; } std::ostream & operator<<(std::ostream &os,const lacksDMA &rs) { os << (const baseDMA &) rs; // 这一手强制转换 你学到了吗? // 强制类型转换 让编译器知道你要用的是baseDMA类中的<< os << "Color: " << rs.color << std::endl; return os; } hasDMA::hasDMA(const char * s, const char * l, int r) : baseDMA(l ,r) { style = new char[strlen(s) + 1]; strcpy(style, s); } hasDMA::hasDMA(const char * s, const baseDMA & rs) : baseDMA(rs) { style = new char[strlen(s) + 1]; strcpy(style, s); } hasDMA::hasDMA(const hasDMA &hs):baseDMA(hs) { style = new char[strlen(hs.style) + 1]; strcpy(style, hs.style); } hasDMA::~hasDMA() { delete [] style; } hasDMA & hasDMA::operator=(const hasDMA &rs) { if(this == &rs) return *this; // 基类的几个属性就直接用基类的赋值运算符重载 baseDMA::operator=(rs); delete [] style; style = new char[strlen(rs.style) + 1]; strcpy(style, rs.style); return *this; } std::ostream & operator<<(std::ostream &os,const hasDMA &rs) { // 强制类型转换 让编译器知道你要用的是baseDMA类中的<< os << (const baseDMA &) rs; os << "Style: " << rs.style << std::endl; return os; }

总结下: a.派生类的复制构造函数,还是赋值运算符重载,都需要对基类的对象复制,或者复制。 在复制构造函数中调用基类的复制构造函数

hasDMA::hasDMA(const hasDMA &hs):baseDMA(hs)

赋值运算符

baseDMA::operator=(rs);

b.但是析构函数的时候,自己管自己的就行,把自己new出来的自己delete掉; c.友元函数因为不是成员函数,不能用作用域运算符::来表示自己是属于哪个类,就要强制类型转换的方式去搞。

最新回复(0)