定义
1.继承与派生是同一过程从不同的角度来看
保持已有类的特性而构造新类的过程称为继承。
在已有类的基础上新增自己的特性而产生新类的过程称为派生
2.被继承的已有类称为基类(或父类)。 派生出的新类称为派生类。
3.继承的目的:实现代码重用。
派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。
派生类的声明
单继承
class Derived: public Base1
{
public:
Derived ();
~Derived ();
};
多继承
class Derived: public Base1, private Base2
{
public:
Derived ();
~Derived ();
};
访问控制
不同继承方式的影响主要体现在:
- 派生类成员对基类成员的访问权限
- 通过派生类对象对基类成员的访问权限
无论采用什么派生的方式,派生类中都不能访问基类的私有成员
公有继承
派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
派生类的对象只能访问基类的public成员
私有继承
派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
通过派生类的对象不能直接访问基类中的任何成员。
保护继承
派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
通过派生类的对象不能直接访问基类中的任何成员
protected 成员的特点与作用:
- 对建立其所在类的对象的模块来说,它与 private 成员的性质相同。对于其派生类来说,它与 public 成员的性质相同。既实现了数据隐藏,又方便继承,实现代码重用。
- 类的对象不能访问,可以通过成员函数访问
- 在类外不可见,在派生类中可见
继承时的构造函数
1.基类的构造函数不被继承,派生类中需要声明自己的构造函数。
2.定义构造函数时,只需要对本类中新增成员进行初始化。继承来的基类成员的初始化,自动调用基类构造函数完成
3.当基类中声明有缺省构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数,也可以不声明,构造派生类的对象时,基类的缺省构造函数将被调用。
4.当需要执行基类中带形参的构造函数来初始化基类数据时,派生类构造函数应在初始化列表中为基类构造函数提供参数。
单一继承时的构造函数
#include<iostream>
using namespace std;
class B {
public:
B(int i) { x=i; }
private:
int x;
};
class C: public B {
public:
C(int i, int j) : B(i), y(j) {
cout << "constructor called." << endl;
}
private:
int y;
};
int main() {
C obj(5, 6);
return 0;
}
多继承且有内嵌对象时的构造函数
class Base1{
public: Base1(int i){cout<<i<<endl;} };
class Base2{
public: Base2(int j){cout<<j<<endl;} };
class C:public Base1,public Base2{
public: C(int i,int j,int m):Base1(i),Base2(j),b1(m)
{cout<<m<<endl; } //初始化列表
Base1 b1;
};
构造函数的执行顺序
- 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
- 调用本类对象成员的构造函数,对本类成员初始化列表中的基本类型成员和对象成员进行初始化,初始化顺序按照它们在类中声明的顺序。对象成员初始化是自动调用对象所属类的构造函数完成的。
- 最后才调用本类的构造函数
#include <iostream> using namespace std; class Base1 { //基类Base1,构造函数有参数 public: Base1(int i) { cout << "Constructing Base1 " << i << endl; } }; class Base2 { //基类Base2,构造函数有参数 public: Base2(int j) { cout << "Constructing Base2 " << j << endl; } }; class Base3 { //基类Base3,构造函数无参数 public: Base3() { cout << "Constructing Base3 *" << endl; } }; // (1) (2) (3) class Derived: public Base2, public Base1, public Base3 { //派生新类Derived,注意基类名的顺序 public: //派生类的公有成员 //(4) (5) Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) { } //注意基类名的个数与顺序,//注意成员对象名的个数与顺序 private: //派生类的私有成员对象 Base1 member1; Base2 member2; Base3 member3; }; int main() { Derived obj(1, 2, 3, 4); return 0; } //运行结果: constructing Base2 2 constructing Base1 1 constructing Base3 * constructing Base1 3 constructing Base2 4 constructing Base3 *
析构函数的调用次序 与调用构造函数的顺序 相反
继承机制下的构造与析构函数
如果基类中只定义了有参的构造函数,而没有无参或全部参数都缺省的构造函数,则派生类必须定义构造函数,并显式地调用基类的某个构造函数。 (即使本身无需完成任何工作(函数体为空))
错误示范:
#include <iostream.h>
class BC{
public:
BC(int a){ x=a; }//有参构造函数
private:
int x;
};
class DC:public BC{
public:
DC() { } //错误
private:
int y;
};
void main()
{ DC d; }
//DC 是继承自 BC 的。当创建 DC 类的对象时,它包含了一个 BC 类对象。
//因此,DC 对象的构造过程中,BC 类的构造函数需要被调用来初始化 BC 类中的成员变量。
//如果没有在 “DC 类的构造函数中显式调用 BC 类的构造函数来初始化基类的成员变量,”
//会导致基类部分处于未定义的状态,可能引发错误或不确定的行为。
解决方案: 1 BC增加一个无参的构造函数
2 “在DC的构造函数中显式地调用BC的某个构造函数”
class DC:public BC{
public:
DC() :BC(5) { }//初始化列表
private: //进入构造函数的函数体之前,基类的部分已经被正确地初始化了
int y;
};
复制构造函数
若建立派生类对象时没有编写复制构造函数,编译器会生成一个隐含的复制构造函数,该函数先调用基类的复制构造函数,再为派生类新增的成员对象执行拷贝。
若编写派生类的复制构造函数,则需要为基类相应的复制构造函数传递参数。
例如: C::C(const C &c1): B(c1) {…}
析构函数
析构函数也不被继承,派生类自行声明。析构函数的调用次序与构造函数相反。
二义性问题
两个基类具有相同名字的成员时,在派生类中这个名字会产生二义性,即编译程序无法确定调用哪一个基类。