10 继承
10.1 继承
在任何情况下,派生类内部无法访问基类的私有成员,基类成员的初始化要通过基类的构造函数,而且,它要在派生类数据之前初始化,所以基类构造函数在派生类构造函数的初始化列表中调用。
派生类生成过程:
- 吸收基类成员
- 改造基类成员
- 添加新的成员
10.1.2 继承的层次性
一般来说,派生类是基类的具体化,基类抽取了派生类中的共同特征,而派生类则是对基类添加约束,使之更为具体,面向更专的领域。
在派生类中可以对基类中的某些成员进行访问,这是由派生方式和成员在基类中的访问权限决定的。·
10.2 派生类
class 派生类名:派生方式 基类名
{
private:
新增私有成员列表;
public:
新增公开成员列表;
};
派生类可以从多个基类继承,也就是多重继承。派生类自动得到了除基类私有成员以外的其他所有数据成员和成员函数,在派生类中可以直接访问。
class 派生类名:派生方式 基类名,派生方式 基类名
{
private:
新增私有成员列表;
public:
新增公开成员列表;
};
10.2.2 protected成员与protected派生
-
在派生类中可以访问protected成员,在外部或者其他类中,protected成员和private成员一样,无法被访问。
-
private派生使得基类中的非private成员都成为派生类中的private成员,在外部和其他类中无法访问。
-
protected
基类的指针可以指向派生类对象,但是不能通过基类指针访问派生类成员。
派生类指针不可指向基类对象
10.3 多基派生
10.3.2 多基派生的二义性
#include<iostream>
using namespace std;
class A
{
public:
void print()
{
cout<<"A"<<endl;
}
};
class B
{
public:
void print()
{
cout<<"B"<<endl;
}
};
class C:public A,public B
{
public:
void disp()
{
cout<<"C"<<endl;
}
};
int main()
{
C hh;
hh.A::print();
hh.B::print();
return 0;
}
10.4 虚基类
共同基类和多基派生共同作用,使得派生类中会出现多个共同基类的复制
解决方法:使用关键字virtual将共同基类A声明为虚基类。
基类“虚拟派生”只维护基类成员的一个版本
- 关键字virtual和派生方式顺序无关
- 必须将共同基类的直接派生类都定义为virtual,以保证共同基类的成员在派生类中只有一个备份
#include<iostream>
using namespace std;
class A
{
protected:
int x;
public:
A(int xp=0)
{
x=xp;
}
void setX(int sx)
{
x=sx;
}
void print()
{
cout<<x<<" in A"<<endl;
}
};
class B:virtual public A
{};
class C:virtual public A
{};
class D:public B,public C
{};
int main()
{
D d;
d.setX(6);
/*以下四个函数对应一个内存地址*/
d.print();
d.A::print();
d.B::print();
d.C::print();
return 0;
}
10.4.3 虚基派生二义性和多基派生二义性的不同
虚基派生二义性是共同基类成员的多重复制带来的存储二义性,多基派生二义性是成员名带来的二义性,使用virtual派生解决。
10.5 派生类的构造函数和析构函数
派生时,构造函数和析构函数是不能继承的,必须重新定义。
创建派生类对象,首先通过派生类的构造函数来调用基类的构造函数,完成基类成员的初始化,而后对派生类中新增的成员进行初始化。
10.5.1 派生类的构造函数
实现顺序
- 完成对象所占整块内存的开辟,由系统调用构造函数时自动完成
- 调用基类构造函数完成基类成员的初始化、
- 若派生类中含有对象成员、const成员或引用成员,则必须在初始化表中完成其初始化
- 派生类构造函数体执行
如果基类中采用的是系统提供的默认构造函数,或定义了无参构造函数(包含所有参数都有默认值的构造函数),基类的构造函数调用可省略,系统自动调用默认的基类构造函数完成基类成员的初始化
- 派生类的构造函数调用的是 基类的构造函数,初始化表达式中是构造函数表达式,而对象成员在初始化时,初始化表达式中是对象名及初始化参数。
10.5.4 虚基派生类的构造函数和析构函数
规定在建立对象时指定的类为最派生类
只有用于创建对象的那个派生类的构造函数才能真正调用虚基类的构造函数其他被忽略。
C++规定虚基的构造函数优于非虚基的构造函数执行
10.6 分清继承和组合
继承:a kind of
组合 composition
class Eye
{
public:
void Look();
};
class Nose
{
public:
void Smell();
};
class Mouth
{
public:
void Eat();
};
class Ear
{
public:
void Hear();
};
class Head
{
Eye m_eye;
Nose m_nose;
Mouth m_mouth;
Ear m_ear;
public:
void Look()
{
m_eye.Look();
}
void Smell()
{
m_nose.Smell();
}
void Eat()
{
m_mouth.Eat();
}
void Hear()
{
m_ear.Hear();
}
};
操作对象明确
10.7 基类和派生类对象的相互转换
10.7.1 类型适应
派生类适应于基类。若一个函数可用于某类型的对象,则它也可作用于该类的所有的派生类对象,不必再为派生类对象重载该对象。
形参要求时基类对象,实参可为派生类对象
1. 赋值转换
可用派生类对象给基类对象赋值,用派生类对象初始化基类对象的指针,派生类对象可以理解包含一个基类对象和一个附加子对象。
2.指针转换
可以用派生类对象的地址初始化基类对象的指针,因为派生类对象和其基类分量具有相同的地址。将派生类指针赋值给指向基类的指针时,可通过此指向基类的指针访问派生类对象中从基类继承下来的成员。
不能将指向虚基类的指针置回指向派生类。