CPP 13. 类继承 2020-11-19 浏览量 733 暂无评论 # 第13章 类继承 🐸小知识: 1. 以前一直以为继承跟直接复制再添加没啥区别,而且就算使用继承,也得先了解基类的特性,熟悉基类,现在有一点让我对继承有了新的看法:它甚至不需要访问源代码就可以派生出类,可以在不公开实现的情况下将自己的类分发给其他人,不过对个人来讲,这没啥用; 2. 派生类需要自己的构造函数; 3. 基类指针可以在不进行显式类型转换的情况下指向派生类对象,基类引用可以在不进行显式类型转换的情况下引用派生类对象,但是只能调用基类方法; 4. C++继承的3种方式:公有继承、保护继承、私有继承; 5. 方法在基类中被声明为虚的后,它在派生类中将自动成为虚方法,在派生类声明中使用关键字virtual来指出哪些函数是虚函数也不失为一个好办法; 6. 通常为基类声明一个虚析构函数,这样做是**为了确保释放派生对象时,按正确的顺序调用析构函数(怎么确保呢?)**; 7. 关键字virtual只用于类声明的方法原型中,而不用在定义中; ## 13.1 简单基类 ```cpp // tabtenn0.h #ifndef TABTENN0_H_ #define TABTENN0_H_ #include using std::string; 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; bool HasTable() const {return hasTable;}; void ResetTable(bool v) {hasTable = v;}; }; // tabtenn0.cpp #include "tabtenn0.h" #include TableTennisPlayer::TableTennisPlayer (const string & fn, const string & ln, bool ht) : firstname(fn), lastname(ln), hasTable(ht) {} void TableTennisPlayer::Name() const { std::cout << lastname << ", " << firstname; } ``` **派生一个类** ```cpp // 公有派生:基类的公有成员将成为派生类的公有成员, // 基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问 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() const {return rating;} void ResetRating (unsigned int r) {rating = r;} }; // 创建派生类对象时,程序首先创建基类对象, // 这意味着基类对象应当在程序进入派生类构造函数之前被创建 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) : TableTennisPlayer(tp) { rating = r; } // 如果不调用基类构造函数,程序将使用默认的基类构造函数 RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht) { rating = r; } 等价于 RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht) : TableTennisPlayer() { rating = r; } ``` **基类指针与引用** ```cpp RatedPlayer rplayer(1140, "Jineng", "Han", true); TableTennisPlayer & rt = rplayer; TableTennisPlayer * pt = &rplayer; rt.Name(); pt->Name(); rt.Rating(); // invalid ``` ## 13.2 继承:Is-a关系 C++继承的3种方式:公有继承、保护继承、私有继承。 公有继承建立一种is-a-kind-of关系(is-a,香蕉是一种水果),不建立has-a关系(午餐有水果),不能建立is-like-a关系(我像小太阳),不建立is-implemented-as-a关系(用数组实现栈),不建立uses-a关系(计算机使用打印机)。 ## 13.3 多态公有继承 它是为了处理这样的情况的:希望同一个方法在派生类和基类中的行为不同。 可以使用两种机制实现多态公有继承: 1. 在派生类中重新定义基类的方法; 2. 使用虚方法(经常在基类中将派生类会重新定义的方法声明为虚方法); ```cpp // brass.h #ifndef BRASS_H_ #define BRASS_H_ #include // 基类中也要加关键字virtual?修改不方便啊,谁知道写基类的时候哪个方法要被重用呢 class Brass { private: std::string fullName; long accNum; double balance; public: Brass(const std::string & s = "Nullbody", long an = -1, double bal = 0.0); void Deposit(double amt); virtual void Withgraw(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 Withgraw(double amt); void ResetMax(double m) {maxLoan = m;} void ResetRate(double r) {rate = r;} void ResetOwes() {owesBank = 0;} }; #endif ``` 如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法,例如: ```cpp 如果ViewAcct()不是虚的: Brass dom("jhan", 123421, 12435.65); BrassPlus dot("han"m 12434, 1243.54); Brass & b1_ref = dom; Brass & b2_ref = dot; b1_ref.ViewAcct(); // use Brass::ViewAcct() b2_ref.ViewAcct(); // use Brass::ViewAcct() 如果ViewAcct()是虚的: b1_ref.ViewAcct(); // use Brass::ViewAcct() b2_ref.ViewAcct(); // use BrassPlus::ViewAcct() ``` ## 13.4 静态联编和动态联编 隐式向上强制转换使基类指针或引用可以指向基类对象或派生类对象。 **虚函数的工作原理:** 实现虚函数功能的是一个保存了指向函数地址数组的指针的隐藏成员(虚函数表),若派生类重新定义了基类的虚函数,则派生类的虚函数表存储的地址替换为新的虚函数地址,否则地址仍为基类虚函数的地址,调用函数时,到对应的虚函数表中查找地址。 **注意点:** 1. 构造函数不能是虚函数; 2. 析构函数应当是虚函数,除非类不用做基类,要不然指向基类的指针就不能调用派生类的析构函数来释放内存了; 3. 友元不能是虚函数,因为友元不是类成员; 4. 如果派生类没有重新定义函数,将使用该函数的基类版本,如果派生类位于派生链中,则将使用最新的虚函数版本(递进原则); 5. 派生类的虚函数将隐藏基类的; ## 13.5 访问控制:protected protected与private的共同点:在类外只能用公有类成员来访问protected部分中的类成员; protected与private的区别:派生类的成员可以直接访问基类的保护成员,但不能访问基类的私有成员。 **小提示:** 尽量对类数据成员采用私有访问控制。 ## 13.6 抽象基类 用来处理两个类有很多共同点,但实际继承时又有许多麻烦的情况,这时,可以从这两个类中抽象出来它们的共性,将这些特性放到一个ABC中。通过纯虚函数来实现ABC。 🐸小知识: 1. 当类声明中包含纯虚函数时,不能创建该类的对象,包含纯虚函数的类只用作基类; 2. 纯虚函数的结尾处为=0,`virtual double Area() const = 0;`; 3. 如果所有共性的基类方法都一样,可以通过将原型声明为虚的来使基类成为抽象的`void Move(int nx, ny) = 0;`; ## 13.7 继承和动态内存分配 如果基类使用动态内存分配,并重新定义赋值和复制构造函数,派生类的实现会受到怎样的影响? 1. 派生类不使用new 不需要为派生类定义显式析构函数、复制构造函数和赋值运算符,默认的就能满足了; 2. 派生类使用new 必须为派生类定义显式析构函数、复制构造函数和赋值运算符。 1. 析构函数:依赖于基类的析构函数释放基类中的指针数据成员,并释放派生类指针数据成员管理的内存。 ```cpp baseDMA::~baseDMA() { delete [] label; } hasDMA::~hasDMA() { delete [] style; } ``` 2. 复制构造函数 复制构造函数baseDMA有一个baseDMA引用参数,基类引用可以指向派生类型,因此,baseDMA复制构造函数将使用hasDMA参数的baseDMA部分来构造新对象的baseDMA部分。 ```cpp hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) { style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); } ``` 3. 赋值运算符 显式调用基类赋值运算符`baseDMA::operator=(hs)`。 **派生类如何使用基类的友元?** 使用基类的;强制类型转换 赞赏 微信支付 支付宝支付