第7章 继承性和派生类
面向对象程序设计有两个重要机制:数据封装和继承。前几章主要介绍了数据封装的概念和实现方法,本章将深入探讨继承这一机制。
7.1 基类和派生类
本节讨论基类和派生类的基本概念及其定义格式。
7.1.1 派生类的定义
基本概念
在面向对象编程中,通过继承可以在一个已有类的基础上创建一个新的类。这个新类不仅继承了已有类的成员,还可以定义属于自己的新成员。已有的类称为基类,而新定义的类称为派生类或子类。
单继承
在C++中,一个派生类可以从一个基类派生,这种继承称为单继承。单继承的定义格式如下:
class 派生类名 : 继承方式 基类名 {
// 派生类新定义成员
};
- 派生类名:新定义类的名称。
- 继承方式:指定继承类型的关键字,有以下三种:
public
:公有继承,基类的公有和保护成员在派生类中保持其访问权限。private
:私有继承,基类的所有成员在派生类中都变为私有成员。protected
:保护继承,基类的公有和保护成员在派生类中变为保护成员。
例如:
class Base {
public:
int a;
protected:
int b;
private:
int c;
};
class Derived : public Base {
// 派生类的新成员和方法
};
在上述例子中,Derived
类继承自Base
类,并且使用公有继承方式。因此,Derived
类可以直接访问Base
类的公有成员a
和保护成员b
,但不能直接访问私有成员c
。
多继承
一个派生类也可以从多个基类派生,这种继承称为多继承。多继承的定义格式如下:
class 派生类名 : 继承方式1 基类名1, 继承方式2 基类名2, ... {
// 派生类新定义成员
};
例如:
class Base1 {
public:
int a;
};
class Base2 {
public:
int b;
};
class Derived : public Base1, public Base2 {
// 派生类的新成员和方法
};
在这个例子中,Derived
类继承自Base1
和Base2
类,并且使用公有继承方式。因此,Derived
类可以直接访问Base1
类的公有成员a
和Base2
类的公有成员b
。
总结
派生类通过继承基类的成员和方法,实现了代码的重用性和扩展性。通过单继承和多继承,程序设计者可以灵活地构建复杂的类层次结构,满足不同的编程需求。接下来,我们将详细讨论不同的继承方式及其对访问权限的影响。
7.1.2 派生类的三种继承方式
在C++中,派生类可以通过三种继承方式从基类继承:公有继承(public)、私有继承(private)和保护继承(protected)。每种继承方式对基类成员在派生类中的访问权限有不同的影响。
1. 公有继承
公有继承的特点是:基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的访问权限,而基类的私有成员在派生类中是不可见的。
示例代码:
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class Derived : public Base {
// publicMember 是公有成员
// protectedMember 是保护成员
// privateMember 在派生类中不可见
};
在以上示例中,Derived
类可以直接访问Base
类的公有成员publicMember
和保护成员protectedMember
,但不能访问私有成员privateMember
。
2. 私有继承
私有继承的特点是:基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
示例代码:
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class Derived : private Base {
// publicMember 是私有成员
// protectedMember 是私有成员
// privateMember 在派生类中不可见
};
在以上示例中,Derived
类将Base
类的公有成员publicMember
和保护成员protectedMember
都作为私有成员,不能被Derived
的子类访问。
3. 保护继承
保护继承的特点是:基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,而不能为派生类的对象访问。
示例代码:
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
class Derived : protected Base {
// publicMember 是保护成员
// protectedMember 是保护成员
// privateMember 在派生类中不可见
};
在以上示例中,Derived
类将Base
类的公有成员publicMember
和保护成员protectedMember
都作为保护成员,只能被Derived
的成员函数或友元函数访问。
小结
C++提供了三种继承方式:公有继承、私有继承和保护继承,每种继承方式对基类成员在派生类中的访问权限有不同的影响。选择合适的继承方式有助于合理设计类的层次结构,确保数据的封装性和继承关系的合理性。
7.1.3 基类成员在派生类中的访问权限
基类成员在派生类中的访问权限与继承方式有关。具体来说,不同的继承方式对基类成员的访问权限有不同的影响,如下所述:
公有继承
- 基类的公有成员在派生类中仍然是公有成员。
- 基类的保护成员在派生类中仍然是保护成员。
- 基类的私有成员在派生类中不可访问。
私有继承
- 基类的公有成员在派生类中成为私有成员。
- 基类的保护成员在派生类中也成为私有成员。
- 基类的私有成员在派生类中不可访问。
保护继承
- 基类的公有成员在派生类中成为保护成员。
- 基类的保护成员在派生类中仍然是保护成员。
- 基类的私有成员在派生类中不可访问。
表7-1 不同继承方式的基类成员在派生类中的访问权限
继承方式 | 基类特性 | 派生类特性 |
---|---|---|
公有继承 (public) | public | public |
公有继承 (public) | protected | protected |
公有继承 (public) | private | 不可访问 |
私有继承 (private) | public | private |
私有继承 (private) | protected | private |
私有继承 (private) | private | 不可访问 |
保护继承 (protected) | public | protected |
保护继承 (protected) | protected | protected |
保护继承 (protected) | private | 不可访问 |
记忆技巧
为了方便记忆,可以简单地记作:
- 基类中私有成员不可访问。
- 公有继承保持原有的访问权限(公有继承不变)。
- 私有继承将所有成员变为私有(私有继承私有)。
- 保护继承将所有公有和保护成员变为保护(保护继承保护)。
访问权限总结
- 派生类只能访问公有继承方式下基类中的公有成员。
- 派生类的派生类可以访问公有继承和保护继承方式下基类中的公有成员和保护成员。
7.1.4 成员访问权限的控制
前面讲述了在派生类中访问基类成员的权限规定。这里通过几个例子进一步讨论访问权限的具体控制。
[例7.1] 分析下列程序中的访问权限,并回答所提的问题
#include <iostream>
using namespace std;
class A {
public:
void f1();
protected:
int j1;
private:
int i1;
};
class B : public A {
public:
void f2();
protected:
int j2;
private:
int i2;
};
class C : public B {
public:
void f3();
};
请回答下列问题:
- 派生类B中的成员f2()能否访问基类A中的成员:f1(), j1 和 i1?
- 派生类B的对象b能否访问基类A中的成员:f1(), j1 和 i1?
- 派生类C中成员函数f3()能否访问直接基类B中的成员:f2(), j2?能否访问间接基类A中的成员f1(), j1 和 i1?
- 派生类C的对象c能否访问直接基类B中的成员:f2(), j2?能否访问间接基类A中的成员:f1(), j1 和 i1?
- 从对问题(1)~(4)的回答可得出对公有继承的什么结论?
解答:
- 可以访问f1()和j1,但不可以访问i1。
- 可以访问f1(),但不可以访问j1和i1。
- 可以访问直接基类中的f2()和j2,以及间接基类中的f1()和j1,但不可以访问i2和i1。
- 可以访问直接基类中的f2()和间接基类中的f1(),对其他成员都不可访问。
- 在公有继承时,派生类的成员函数可访问基类中的公有成员和保护成员;派生类的对象仅可访问基类中的公有成员。
思考:将程序中的两处继承方式的public
改为private
,又将如何回答上述各个问题?
[例7.2] 分析下列程序,并回答所提的问题
#include <iostream>
using namespace std;
class A {
public:
void f(int i) {
cout << i << endl;
}
void g() {
cout << "g()" << endl;
}
};
class B : A { // 缺省继承方式为 private
public:
void h() {
cout << "h()" << endl;
A::f(6);
}
};
int main() {
B d1;
d1.f(6); // 语句1
d1.g(); // 语句2
d1.h(); // 语句3
}
请回答下列问题:
- 执行该程序时,哪个语句会出现编译错误?为什么?
- 去掉出错语句后,执行该程序后的输出结果如何?
- 程序中派生类B是从基类A继承来的,这种默认的继承方式是哪种继承方式?
- 派生类B中,
A::f
的含义是什么? - 将派生类B的继承改为公有继承方式,该程序将输出什么结果?
解答:
- 程序中,
d1.f(6);
语句会出现编译错误,因为B是以私有继承方式继承类A的,所以B类的对象不可访问A类的成员函数。 - 将程序中,
d1.g();
语句注释后,执行该程序输出如下结果:6
h()
- 使用
class
关键字定义类时,默认的继承方式是private
。 A::f
将基类中的公有成员声明为派生类的公有成员。- 将
class B : A
改为class B : public A
后,执行该程序输出如下结果:6
g()
h()
[例7.3] 分析下列程序,并回答问题
#include <iostream>
#include <cstring>
using namespace std;
class A {
public:
A(const char *nm) {
strcpy(name, nm);
}
private:
char name[80];
};
class B : public A {
public:
B(const char *nm) : A(nm) {}
void PrintName() const;
};
void B::PrintName() const {
cout << "name: " << name << endl;
}
int main() {
B b1("wang li");
b1.PrintName();
}
请回答下列问题:
- 执行该程序将会出现什么编译错误?
- 对出现的编译错误如何在访问权限上进行修改?
- 修改后使该程序通过编译,执行该程序后输出结果是什么?
解答:
- 编译时出错行如下:
cout << "name: " << name << endl;
,错误信息提示name
是私有成员不能访问。 - 在类A中,将
private
改写为protected
,这样就可以通过编译。 - 执行修改后的该程序输出如下结果:
name: wang li