文章目录


派生类成员的访问

1 类的保护成员

对派生类来说,成员分为两大类:

(1)一类是从基类继承过来的成员;

(2)一类是自己新生成的成员。

如果没有继承,一个类只有两种类型的访问者:类成员和类用户。将类划分为private和public访问级别反映对访问者的访问权限:

  • 类用户只能访问公有成员,
  • 类成员和友元既可以访问公有成员也可以访问私有成员。

有了继承,就有了类的第三种访问者:派生类成员。

派生类通常需要访问(一般为私有的)基类成员,​​为了允许这种访问而仍然禁止外部对基类的一般访问​​​,可以使用protected访问标号。​​类的protected部分仍然不能被类用户访问,但可以被派生类访问​​。

只有基类类成员及其友元可以访问基类的private部分,​​派生类不能访问基类的私有成员​​。

类的保护成员用protected访问标号声明,​​可以认为protected访问标号是private和public的混合​​:

①像私有成员一样,​​保护成员不能被类用户访问​​。

②像公有成员一样,​​保护成员可以被该类的派生类访问​​。

如果基类声明了私有成员,那么任何派生类都是不能访问它们的,若希望在派生类中能访问它们,应当把它们声明为保护成员。

所以如果在一个类中声明了保护成员,就意味着该类可能要用作基类,在它的派生类中会访问这些成员。

【C++】(二十七)派生类成员的访问 |派生类的构造和析构函数_构造函数

2 派生类成员的访问权限

派生类中包含继承来的成员和自己新增的成员,因而产生了这两部分成员的关系和访问属性的问题。

对基类成员和派生类自身的成员是按不同的原则处理的,需要考虑以下6种情形:

(1)基类的成员和友元访问基类成员;

(2)派生类的成员和友元访问派生类自己新增的成员;

对于第1种和第2种的情形,可以按以下规则处理,即:

  • 基类的类成员和友元可以访问基类成员,
  • 派生类的类成员和友元可以访问派生新增的类成员。
  • 私有成员只能被同一类中的类成员访问,公有成员可以被类用户访问。

(3)基类的成员访问派生类新增的成员;

第3种的情形,基类的成员不能直接访问派生类的成员(因为有基类的时候尚未有派生类),但可以通过虚函数间接访问派生类的成员。

(4)类用户访问派生类的成员;

第4种的情形,比较明确,类用户可以访问派生类的公有成员,不能访问派生类任何私有的或保护的成员。

(5)派生类新增的成员访问基类的类成员;

(6)类用户访问派生类的基类成员。

第5种和第6种的情形比较复杂,其访问形式实际是两种形式:

  • ①内部访问:由派生类中新增成员对基类继承来的成员的访问。
  • ②对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问。

不同的继承方式决定了基类成员在派生类中的访问属性。

(1)公有继承(public inheritance)

基类的公有成员和保护成员在派生类中保持原有访问属性,私有成员仍为基类私有。

(2)私有继承(private inheritance)

基类的所有成员在派生类中为私有成员。

(3)保护继承(protected inheritance)

基类的公有成员和保护成员在派生类中成了保护成员,私有成员仍为基类私有。

【C++】(二十七)派生类成员的访问 |派生类的构造和析构函数_派生类_02

无论采用何种继承方式得到的派生类,派生类成员及其友元都不能访问基类的私有成员,派生类外部的用户只能访问公有属性的成员

多级派生的情况下,保护继承和私有继承会进一步地将基类的访问权限隐蔽成不可访问的。

一般地,​​保护继承与私有继承在实际编程中是极少使用的,它们只在技术理论上有意义​​。

【C++】(二十七)派生类成员的访问 |派生类的构造和析构函数_析构函数_03

【C++】(二十七)派生类成员的访问 |派生类的构造和析构函数_派生类_04

3 赋值兼容规则

赋值兼容规则是指​​在需要基类对象的任何地方,都可以使用公有派生类的对象来替代​​。

通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,​​凡是基类能解决的问题,公有派生类都可以解决​​。

赋值兼容规则中所指的替代包括以下的情况:

①派生类的对象可以赋值给基类对象;

②派生类的对象可以初始化基类的引用;

③派生类对象的地址可以赋给指向基类的指针。

例:

class Base { }; //基类
class Derive : public Base { }; //公有派生类
Base b,
*pb; //定义基类对象、指针
Derive d; //定义派生类对象

这时,支持下面三种操作:

b=d; //派生类对象赋值给基类,复制基类继承部分
Base &rb=d; //基类引用到派生类对象
pb=&d; //基类指针指向派生类对象

赋值兼容规则是C++多态性的重要基础之一。

派生类的构造和析构函数

1 派生类的构造函数

在定义派生类时,​​派生类并没有把基类的构造函数和析构函数继承下来​​。因此,对继承的基类成员初始化的工作要由派生类的构造函数承担,同时基类的析构函数也要被派生类的析构函数来调用。

1.1 派生类构造函数的定义

在执行派生类的构造函数时,使派生类的数据成员和基类的数据成员同时都被初始化。其定义形式如下:

派生类名(形式参数列表) : 基类名(基类构造函数实参列表),派生类初始化列表
{
派生类初始化函数体
}

“基类名(基类构造函数实参列表)”即是调用基类构造函数,而​​派生类新增加的数据成员可以在“派生类初始化列表”(尽量在此)初始化​​,也可以在“派生类初始化函数体”中初始化。

派生类构造函数的调用顺序是:

①​​调用基类构造函数​​如下面例子的Point(a,b);

②执行派生类初始化列表;

③执行派生类初始化函数体;

例如:

class Point { int x,y;
public: Point(int a,int b):x(a),y(b) { } //构造函数
};
class Rect : public Point { int h,w;
public: Rect(int a,int b,int c,int d):Point(a,b),h(c),w(d) { } //派生类构造函数
};

1.2 组合关系的派生类的构造函数

假定派生类A和类B的关系是组合关系,类A中有类B的子对象。​​如果类B有默认构造函数,或者参数全是默认参数的构造函数,或者有无参数的构造函数,那么类A的构造函数中可以不用显式初始化子对象​​。编译器总是会自动调用B的构造函数进行初始化。

可以在一个类的构造函数中显式地初始化其子对象,初始化式只能在构造函数初始化列表中,形式为:

类名(形式参数列表) : 子对象名(子对象构造函数实参列表),类初始化列表
{
类初始化函数体
}

调用顺序为:

①调用基类构造函数;

②调用子对象构造函数,各个子对象时按其声明的次序先后调用;

③执行派生类初始化列表;

④执行派生类初始化函数体;

说明:

(1)如果在基类和子对象所属类的定义中都没有定义带参数的构造函数,而且也不需要对派生类自己的数据成员初始化,那么可以不必显式地定义派生类构造函数。派生类会合成一个默认构造函数,并在调用派生类构造函数时,会自动先调用基类的默认构造函数和子对象所属类的默认构造函数。

例如:

class A { }; //合成默认构造函数
class B { }; //合成默认构造函数
class D: public B { A a; }; //派生类合成默认构造函数

(2)如果在基类中没有定义构造函数,或定义了没有参数的构造函数,那么,在定义派生类构造函数时可以不显式地调用基类构造函数。在调用派生类构造函数时,系统会自动先调用基类的无参数构造函数或默认构造函数。

例如:

class B { public: B() { } }; //无参数构造函数
class D: public B {
D() { } //派生类构造函数不必显式调用基类构造函数
};

(3)如果在基类或子对象所属类的定义中定义了带参数的构造函数,那么就必须显式地定义派生类构造函数,并在派生类构造函数中显式地调用基类或子对象所属类的构造函数。

例如:

class A { public: A(int) { } }; //有参数构造函数
class B { public: B(int) { } }; //有参数构造函数
class D: public B {
D(int x) : a(x),B(x) { } //显式调用基类或子对象构造函数
A a;
};

(4)如果在基类中既定义了无参数的构造函数,又定义了有参的构造函数(构造函数重载),则在定义派生类构造函数时,既可以显式调用基类构造函数,也可以不调用基类构造函数。

在调用派生类构造函数时,根据构造函数的内容决定调用基类的有参数的构造函数还是无参数的构造函数

可以根据派生类的需要决定采用哪一种方式。

2 派生类的析构函数

在派生时,派生类是不能继承基类的析构函数的,也​​需要通过派生类的析构函数去调用基类的析构函数​​。

派生类中可以根据需要定义自己的析构函数,用来​​对派生类中所增加的成员进行清理工作​​。基类的清理工作仍然由基类的析构函数负责。

在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。

析构函数调用的顺序与构造函数正好相反:先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。

【C++】(二十七)派生类成员的访问 |派生类的构造和析构函数_派生类_05

【C++】(二十七)派生类成员的访问 |派生类的构造和析构函数_构造函数_06

【C++】(二十七)派生类成员的访问 |派生类的构造和析构函数_构造函数_07


C++程序设计-西北工业大学-魏英、姜学锋、刘君瑞