C++类提供了更高层次的重用性。目前,很多厂商提供了类库,类库由类声明和实现构成。因为类组合了数据表示和类方法,因此提供了比函数库更加完整的程序包。
1、一个简单的基类
从一个类派生出另一个类时,原始类成为基类,继承类称为派生类。
tabtenn0.h
#ifndef TABTENN0_H_ #define TABTENN0_H_ #include <string> class TableTennisPlayer { private: std::string fristname; std::string lastname; bool hasTable; public: TableTennisPlayer(const std::string& fn = "none", const std::string& ln = "none", bool ht = false); void Name() const; bool HasTable() const { return hasTable; } void ResetTable(bool v) { hasTable = v; } }; #endif
tabtenn0.cpp
#include "tabtenn0.h" #include <iostream> //构造函数 TableTennisPlayer::TableTennisPlayer(const std::string& fn, const std::string& ln, bool ht) :firstname(fn), lastname(ln), hasTable(ht) {} void TableTennisPlayer::Name() const { std::cout << lastname << ", " << firstname << std::endl; }
派生一个类,包含成员在比赛中的比分。
class RatePlayer:public TableTennisPlayer { private: unsigned int rating; //比分 public: //派生类的构造函数 RatePlayer(unsigned int r = 0, const std::string& fn = "none", const std::string& ln = "none", bool ht = false); RatePlayer(unsigned int r, const TableTennisPlayer& tp); //添加派生类的方法 unsigned int Rating() const { return rating; } void ResetRating(unsigned int r) { rating = r; } };
构造函数必须给新成员和继承的成员提供数据。
派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。派生类构造函数必须使用基类构造函数。
创建派生类对象时,程序首先创建基类对象。从概念上说,这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表语法来完成这种工作。
RatedPlayer::RatedPlayer(unsigned int r, const string& fn, const string& ln, bool ht): TableTennisPlayer(fn, ln, ht) { rating=r; }
其中:TableTennisPlayer(fn,ln,ht)是成员初始化列表。他是可执行的代码,调用TableTennisPlayer构造函数。
有关派生类构造函数的要点如下:
创建派生类对象时,程序首先调用基类的构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承类的数据成员;派生类构造函数主要用于初始化新增的数据成员。派生类的构造函数总是调用一个基类的构造函数。可以使用初始化器列表语法指明要使用的基类构造函数,否则将使用默认的基类构造函数。
派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类构造函数。
1.1、派生类和基类之间的特殊关系
派生类和基类之间有一些特殊关系,其中之一是派生类对象可以使用基类的方法,条件是方法不是私有的;同时基类指针可以在不进行显示类型转换的情况下指向派生类对象。然而基类指针或引用只能用于调用基类方法。
2、多态公有继承
派生类对象使用基类的方法,而未做任何修改。然而,应该会遇到这样的情况,即希望同一个方法在派生类和基类中的行为是不同的。换句话来说,方法的行为应该取决于调用该方法的对象。这种较复杂的行为称为多态——具有多种形态。有两种重要的机制可用于实现多态公有继承:
另一个例子。银行开发两个类,一个类用于表示基本支票账户——Brass Account,另一个类用于表示Brass Plus支票账户,它添加了透支保护特性。
#ifndef BRASS_H_ #define BRASS_H_ #include <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); 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
程序需要说明一下几点:
第二点介绍了声明如何指出方法在派生类的行为的不同。两个ViewAcct()原型表明将有2个独立的方法定义。基类版本的限定名为Brass::ViewAcct(),派生类版本的限定名为BrassPlus::ViewAcct()。程序将使用对象类型来确定使用哪个版本:
Brass dom("Dominic Banker", 11224, 4183.45);
BrassPlus dot("Dorothy Banker", 12118, 2592.00);
dom.ViewAcct(); //使用Brass::ViewAcct()方法
dot.ViewAcct(); //使用BrassPlus::ViewAcct()方法
第三点使用virtual要复杂一点。如果方法是通过引用或指针而不是对象调用的,它将确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或者指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。
方法在基类中被声明为虚的后,他在派生类中将自动成为虚方法。然而在派生类声明中使用关键字virtual来指出哪些函数是虚函数也不失为一个好办法。
如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的。这样程序将根据对象类型而不是引用或指针的类型来选择方法版本。
2.1、为何需要虚析构函数
使用delete释放由new分配的对象的代码说明了为何基类应包括一个虚析构函数,虽然有时候好像并不需要析构函数。如果虚构函数不是虚的,则只调用对应于指针类型的析构函数。如果析构函数是虚的,将调用响应对象类型的析构函数。因此,如果指针指向的是BrassPlus对象,将调用BrassPlus的析构函数,然后自动调用基类的析构函数。
3、访问控制符:protected
关键字protected和private相似,在类外只能用于共有类成员来访问protected部分中的类成员。private和protected之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说,保护成员的行为与私有成员相似;但对于派生类来说,保护成员与公有成员相似。