概述
类继承:
从已有的类中派生出新的类,派生类
继承了基类
的特征,包括方法
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
- 无论何种继承方式,基类中的私有成员只能通过基类接口(公有或保护的基类成员函数)访问。
- 派生类内,不管哪种继承方式,一定能访问基类中的公有和保护成员,永远不能访问基类中私有成员。
这里的访问指的是直接获取或修改对象的值,而不是通过基类中的接口。
- 不同继承方式只改变基类中公有成员和保护成员在派生类中的类型
私有继承
是默认的继承方式
- 重新定义访问权限:
- 使用
保护派生
或私有派生
时,基类的公有成员将成为保护成员
或私有成员
使基类的方法在派生类外可用:
(1)定义一个基类方法的派生类方法
(2)将函数调用包装在另一个函数调用中,即使用一个using声明
来指出派生类可以使用特定的基类成员using声明
只使用成员名,没有圆括号,函数特征标、返回类型using声明
只适用于继承
,不适用于包含
- 派生类的创建:
派生类
不能直接访问基类
的私有成员,必须通过基类方法进行访问,派生类构造函数必须使用基类构造函数- 创建派生类时,程序首先创建基类对象,即基类对象应当在程序进入派生类构造函数之前被创建
- 如果不调用基类构造函数,将使用基类的默认构造函数
- 释放对象的顺序与创建对象的顺序
相反
,即首先执行派生类的析构函数
,然后自动调用基类的析构函数
- 类继承与指针:
基类指针
可以在不进行显式类型转换的情况下指向派生类对象
基类引用
可以在不进行显式类型转换的情况下引用派生类对象
基类指针
或基类引用
只能用于调用基类方法,不能调用派生类方法- 不可以将
基类对象
和地址
赋给派生类引用或指针- 基类引用定义的函数或指针参数可用于
基类对象
或派生类对象
基类对象
可以初始化为派生类对象
,也可以将派生类对象赋给基类对象
多态公有继承
公有继承
是最常见的继承方式,它建立一种is-a
关系,派生对象
也是一个基类对象
多态
就是多种形态,也就是同一个方法的行为随上下文而异
- 虚方法:
虚方法:
使用关联字virtual
的成员函数- 如果方法是通过
引用
或指针
而不是对象调用的
(1)如果没有使用关键字virtual
,程序将根据引用类型
或指针类型
选择方法
(2)如果使用关键字virtual
,程序将根据引用
或指针
指向的对象的类型
选择方法- 如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的
- 构造函数不能是虚函数,派生类不继承基类的构造函数
- 友元函数不是类成员,因此友元函数不能是虚函数
class Brass
{
public:
virtual void Withdraw(double amt)
{
cout << "Brass::Withdraw " << endl;
}
~Brass()
{
cout << "~Brass()" << endl;
}
};
class BrassPluse : public Brass
{
public:
virtual void Withdraw(double amt)
{
cout << "BrassPlus::Withdraw " << endl;
}
~BrassPluse()
{
cout << "~BrassPlus()" << endl;
}
};
- 虚析构函数
(1)从下图测试用例中可以看出,基类的析构函数前使用virtual
关键字修饰,成为虚析构函数,虚析构函数只会影响使用基类指针指向子类对象的情况,其他情况不受影响,即子类析构的时候会首先调用自己的析构函数然后自动调用父类的析构函数
(2)当使用类似Brass *pB = new BrassPluse;
然后delete pB;
时,如果析构函数不是虚函数,只调用对应于指针类型
的析构函数
,即Brass
的析构函数,不会调用BrassPluse
的析构函数,如果析构函数是虚函数则首先调用BrassPluse
的析构函数,然后再调用Brass
的析构函数
虚函数与缺省参数
- 绝不重新定义继承而来的
缺省参数值
- 虚函数是
动态绑定
的,而缺省参数值却是静态绑定
的- 当使用
对象调用
此函数,一定要指定参数值
,静态绑定下
函数不从其base
继承缺省参数值- 若以
指针(或引用)
调用此函数,可以不指定参数值,动态绑定下这个函数会从其base
继承缺省参数值,即使派生类重新指定缺省值
,使用的缺省参数值仍然是基类中的值
class Shape {
public:
enum ShapeColor { Red, Green, Blue};
virtual void draw(ShapeColor color = Red) const = 0;
};
class Rectangle : public Shape
{
public :
virtual void draw(ShapeColor color = Green) const;
};
void Rectangle::draw(ShapeColor color) const
{
cout << "Rectangle" << endl;
cout << color << endl;
}
class Circle : public Shape
{
public:
virtual void draw(ShapeColor color) const;
};
void Circle::draw(ShapeColor color) const
{
cout << "Circle" << endl;
cout << color << endl;
}
//使用对象调用:
Circle c1;
Rectangle r1;
//c1.draw(); 为指定默认参数,无法调用
r1.draw();
//使用指针/引用调用:
Shape* pc = new Circle;
Shape* pr = new Rectangle;
pc->draw();
pr->draw();
- 令基类中的一个
public non-virtual
函数调用private virtual
函数private virtual
函数可被派生类重新定义- 让
non-virtual
函数指定缺省参数,而private virtual
函数负责真正的工作
class Shape{
public:
enum ShapeColor {Red, Green, Blue};
void draw(ShapeColor color = Red) const //如今是non-virtual函数
{
doDraw(color); //调用一个virtual函数
}
private:
virtual void doDraw(ShapeColor color) const = 0;
};
class Rectangle: public Shape
{
private:
virtual void doDraw(ShapeColor color) const
{
cout<<"Rectangle"<<endl;
cout<<color<<endl;
}
}
class Circle: public Shape
{
private:
virtual void doDraw(ShapeColor color) const
{
cout<<"Circle"<<endl;
cout<<color<<endl;
}
}
抽象基类(ABC)
抽象基类:
是包含纯虚函数
的类- 抽象基类不能
声明对象
,但其派生类
可以声明对象,但要在派生类中完全实现基类中所有的纯虚函数- 包含纯虚函数的类只能用作基类
class BaseEllipse
{
private:
double x;
double y;
public:
virtual double Area() const = 0; //纯虚函数
}
对象成员
组合(包含):
使类的某些类成员本身是另一个类的对象,是一种实现has-a
关系的途径- 使用
组合
,将对象成员声明为private
或protected
时, 类可以获得实现
,但不能获得接口
(1)Student
类的成员函数可以使用string
类的公有接口来修改name
对象
(2)在Student
类外类外不能这么做,只能通过Student
类的公有接口访问name
- 被包含对象的接口不是公有的,但可以在类中使用它
- 初始化被包含的对象:
- 对于成员对象,使用成员名进行
初始化
,此时初始化列表中的每一项都调用与之匹配的构造函数
- C++要求在构造对象的其他部分之前,先构造对象的成员对象
- 如果省略初始化列表,C++将使用成员对象所属类的
默认构造函数
- 初始化的顺序: 当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被
声明的顺序
,而不是它们在初始化列表中的顺序
私有继承
私有继承
也是一种实现has-a
关系的途径- 使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员
- 基类方法不会成为派生类派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们
私有继承
派生类继承基类的实现
但不继承基类的接口
- 私有继承 vs 组合(包含):
包含
将对象做为一个命名的成员对象添加到类中私有继承
将对象作为一个未被命名的继承对象添加到类中子对象:
通过继承
或包含
添加的对象- 应当使用
包含
来建立has-a
关系- 如果需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承
- 初始化基类组件:
- 对于继承类,构造函数使用成员初始化列表语法,并使用
类名
而不是成员名
来标识构造函数
- 访问基类的方法:
- 使用私有继承时,只能在派生类的方法中使用基类的方法
- 使用
包含
时将使用对象名来调用方法,而使用私有继承
时将使用类名
和作用域解析运算符
来调用方法
- 访问基类对象:
- 可以通过
强制类型转换
,来访问基类对象本身
- 访问基类的友元函数:
- 在
私有继承
中,未进行显式类型转换
的派生类引用或指针,无法赋值给基类的引用
或指针
保护继承
- 使用保护继承,基类的
公有成员
和保护成员
都将称为派生类的保护成员- 和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的
- 当派生类派生出另一个类时,
私有继承
和保护继承
的区别:
(1)使用私有继承,第三代类将不能使用基类的接口,因为基类的公有方法在派生类中将变成私有方法
(2)使用保护继承,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们
多重继承
多重继承(MI):
有多个直接基类的类- 必须使用关键字
public
来限定每一个基类,除非特别指出,否则编译器将认为时私有派生- 公有
MI
表示的是is-a
关系,私有MI
和保护MI
表示has-a
关系
- 虚基类:
虚基类:
使多个类(它们的基类相同)派生的对象只继承一个基类对象
Singer
和Waiter
都继承了一个Worker
组件,因此SingingWaiter
将包含两个Worker
组件- 将派生类对象的地址赋值给基类指针,将出现二义性,
ed
中有两个Worker
对象,应使用类型转换来指定对象
- 虚基类:
虚基类
使得多个类(它们的基类相同)派生出的对象只继承一个基类对象- 在
基类
是虚的时,禁止信息通过中间类自动传递给基类,需要显式调用所需基类的构造函数来构造虚基类
,但对于非虚基类,这是非法的
- 多重继承可能会导致函数调用的
二义性
,解决方案:
(1)使用作用于解析运算符澄清编程者的意图
(2)在SingingWaiter
中重新定义Show()
,并指出使用哪个Show()
- 通过多条虚途径和非虚途径继承某个特定的基类时,该类包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象
void Work::show() const
{
cout<<"Name: "<<fullname<<endl;
cout<<"Employee ID:"<<id<<endl;
}
void Waiter::show() const
{
cout<<"Category: waiter<<endl;
Worker::Show();
cout<<"Panache rating:"<<panache<<endl;
}
void Singer::show() const
{
cout<<"Category: singer<<endl;
Worker::Show();
cout<<"Vocal range:"<<pv[voice]<<endl;
}
- 虚基类与支配:
- 使用
非虚基类
时,如果类从不同的类中继承了多个同名成员,则使用该成员名时,若未加类型进行限定,则会导致二义性
,但若使用了虚基类
这样做并不一定会导致二义性
- 在使用虚基类时,如果某个名称优先于其他所有名称,则使用它时,即使不使用限定符,也不会导致二义性
- 在使用虚基类时,派生类的名称优先于直接或间接祖先类中的相同名称
- 类
C
中的q()
优先于,类B
中的q()
,因此F
中可以使用q()
表示C::q()
虚二义性
规则与访问规则无关,即使E::omg()
是私有的,但使用omg()
仍然导致二义性
嵌套类
嵌套类:
在另一个类中声明的类- 包含类的成员函数可以创建和使用被嵌套类的对象
- 仅当声明位于
公有部分
,才能在包含类的外面使用嵌套类,且必须使用作用域解析运算符
包含 vs 嵌套:
嵌套
≠ \neq =包含
(1)包含
意味着将类对象作为另一个类的成员
(2)对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效
- 嵌套类和访问权限:
嵌套类
的声明位置
决定了嵌套类的作用域,即它决定了程序的哪些部分可以创建这种类的对象- 若
嵌套类
是在另一个类的私有部分
声明的,则只有后者知道它- 若
嵌套类
在另一个类的保护部分
声明的,则对后者来说是可见的,但是对于外部世界则是不可见的,然而,派生类将知道嵌套类,并可以直接创建这种类型的对象- 若
嵌套类
是在另一个类的公有部分
声明的,则允许后者、后者的派生类以及外部世界使用它,外部使用它时,必须使用类限定符嵌套类
的公有部分
、保护部分
和私有部分
控制了对类成员的访问,对嵌套类访问权的控制规则对常规类相同
- 类模板的嵌套:
类模板
中也可以进行嵌套,不会因为包含嵌套类而带来麻烦- 包含嵌套类的模板中,
模板类型
可以在嵌套类内使用