本篇开始学习C++类的继承。
C++的类继承用于描述一种is的关系。is关系,比如橘子是水果,猴子是动物。
类S是另一个类F的衍生,通过类继承的关系,就能够在F类的基础上,增量修改得到类S。类F称为基类,S称为派生类。
派生类继承了基类的数据和方法,并且能够添加数据和方法。
类与类的关系决定了是否适合继承。
b is a关系:橘子是水果。
b has a关系:晚餐有水果。
b uses a关系:空调使用遥控。
b is like a关系:空调类似洗衣机(都是家具)。
上面的关系中,is关系适合使用b类继承a类;has关系适合在类b中使用a作为数据成员;uses关系适合将类b作为a的友元;is like关系适合定义一个包含a,b共有特征的类c,然后考虑c与a,b的关系。
被继承的类是基类,可以按照之前定义类的方法,提供数据成员和方法。这里直接提供一个简单的基类Base:
class Base{
private:
int a_;
double b_;
public:
Base();
Base(int, double);
void print();
};
Base::Base():a_(1), b_(1.){
std::cout << "Base default constructor\n";
}
Base::Base(int a, double b): a_(a), b_(b){
std::cout << "Base constructor\n";
}
void Base::print(){
std::cout << a_ << " " << b_ << std::endl;
}
继承声明为:
class 派生类名 : 继承方式 基类名 {};
,如class Derived : public Base {};
继承方式限定了派生类对基类成员的访问权限。public
继承,基类的公有成员能够被派生类访问,但基类的私有成员只能被基类的公有方法和保护方法访问。
派生类将继承基类的所有数据成员以及成员函数。派生类还可以添加额外的成员
派生类虽然继承了基类的构造函数,但需要定义自己的构造函数。
下面从Base类派生一个Derived类:
class Derived : public Base{
private:
int a_;
double c_;
public:
Derived();
Derived(int, double, int, double);
void print();
};
Derived::Derived(): Base(), a_(1), c_(1.){
std::cout <<"Derived default constructor\n";
}
Derived::Derived(int a0, double b, int a1, double c)
:Base(a0, b), a_(a1), c_(c) {
std::cout <<"Derived constructor\n";
}
void Derived::print(){
Base::print();
std::cout <<a_ << " " << c_ << std::endl;
}
创建派生类对象时将调用派生类的构造函数,因此必须为派生类提供构造函数。派生类的构造函数应当为添加的数据成员和继承的数据成员初始化。
后面访问权限中会提到,派生类不能直接访问基类的私有成员,必须通过基类方法来访问。派生类的构造函数必须调用基类构造函数。
构造函数Derived::Derived()
是手动提供的默认构造函数,在成员初始化列表中调用了基类的构造函数Base()
。
创建派生类对象时,程序会首先创建基类对象,然后再创建派生类对象:
Derived son1(2, 2.2, 3, 3.3);
/*
Base constructor
Derived constructor
2 2.2
3 3.3
Derived destructor
Base destructor
*/
上面的语句将参数传入派生类构造函数Derived(int a0, double b, int a1, double c)
,然后Derived
函数调用基类构造函数Base(int, double)
,创建了一个嵌套的Base
对象,存储基类的数据Base::a_, Base::b
;然后再回到派生类构造函数,创建一个Derived
对象,存储派生类的数据Derived::a_, Derived::c_
。
在派生类对象过期时,程序首先调用派生类析构函数,再调用基类析构函数。
派生类构造函数的成员初始化列表如果没有显式调用基类构造函数,创建对象时将调用基类的默认构造函数:
Derived son2;
/*
Base default constructor
Derived default constructor
Derived destructor
Base destructor
*/
注意:如果创建派生类对象时,要指定继承成员的值,则必须在派生类构造函数的成员初始化列表中调用基类构造函数。派生类构造函数一定会调用一个基类构造函数。
小结一下派生类的构造函数:
编译器必须在声明派生类前知道基类的结构。因此,通常把基类声明和派生类声明放在同一个头文件中,把基类和派生类方法定义放在同一个源文件中。
类继承中的一大难点在于,继承后派生类对基类成员的访问权限。
继承方式包括public
,protected
,private
三类。不过绝大多数情况,都使用public
继承。
对于类自身的成员而言:
成员访问权限 | 成员可见性 |
---|---|
public | 能直接被外部访问 |
protected | 能被该类和子类的方法访问 |
private | 仅能被该类的方法访问 |
当派生类继承基类时,根据不同的继承方式,派生类对基类成员的访问权限将发生变化:
基类成员访问权限 \ 继承方式 | public 继承 | protected 继承 | private 继承 |
---|---|---|---|
public 权限 | public | protected | private |
protected 权限 | protected | protected | private |
private 权限 | private | private | private |
protected
权限是与继承密切相关的访问权限。protected
成员不能被外部直接访问,但可以被该类以及子类的成员函数访问。protected
使得派生类能够使用公众不能使用的内部成员。
注意:如果希望限制数据的可见性,则最好使用private
访问权限而不是protected
,并且提供派生类能够调用的基类方法来访问基类的数据。
下篇学习类的继承,虚函数与多态。