002-4 类继承(完)

概述

  1. 类继承: 从已有的类中派生出新的类,派生类继承了基类的特征,包括方法
特征公有继承保护继承私有继承
公有成员变成派生类的公有成员派生类的保护成员派生类的私有成员
保护成员变成派生类的保护成员派生类的保护成员派生类的私有成员
私有成员变成只能通过基类接口访问只能通过基类接口访问只能通过基类接口访问
能否隐式向上转换是(但只能在派生类中)
  1. 无论何种继承方式,基类中的私有成员只能通过基类接口(公有或保护的基类成员函数)访问。
  2. 派生类内,不管哪种继承方式,一定能访问基类中的公有和保护成员,永远不能访问基类中私有成员。这里的访问指的是直接获取或修改对象的值,而不是通过基类中的接口。
  3. 不同继承方式只改变基类中公有成员和保护成员在派生类中的类型
  4. 私有继承是默认的继承方式
  • 重新定义访问权限:
  1. 使用保护派生私有派生时,基类的公有成员将成为保护成员私有成员
  2. 使基类的方法在派生类外可用:
    (1)定义一个基类方法的派生类方法
    (2)将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员
  3. using声明只使用成员名,没有圆括号,函数特征标、返回类型
  4. using声明只适用于继承,不适用于包含

在这里插入图片描述
在这里插入图片描述

  • 派生类的创建:
  1. 派生类不能直接访问基类的私有成员,必须通过基类方法进行访问,派生类构造函数必须使用基类构造函数
  2. 创建派生类时,程序首先创建基类对象,即基类对象应当在程序进入派生类构造函数之前被创建
  3. 如果不调用基类构造函数,将使用基类的默认构造函数
  4. 释放对象的顺序与创建对象的顺序相反,即首先执行派生类的析构函数,然后自动调用基类的析构函数

在这里插入图片描述

  • 类继承与指针:
  1. 基类指针可以在不进行显式类型转换的情况下指向派生类对象
  2. 基类引用可以在不进行显式类型转换的情况下引用派生类对象
  3. 基类指针基类引用只能用于调用基类方法,不能调用派生类方法
  4. 不可以将基类对象地址赋给派生类引用或指针
  5. 基类引用定义的函数或指针参数可用于基类对象派生类对象
  6. 基类对象可以初始化为派生类对象,也可以将派生类对象赋给基类对象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多态公有继承

  1. 公有继承是最常见的继承方式,它建立一种is-a关系,派生对象也是一个基类对象
  2. 多态就是多种形态,也就是同一个方法的行为随上下文而异
  • 虚方法:
  1. 虚方法: 使用关联字virtual的成员函数
  2. 如果方法是通过引用指针而不是对象调用的
    (1)如果没有使用关键字virtual,程序将根据引用类型指针类型选择方法
    (2)如果使用关键字virtual,程序将根据引用指针指向的对象的类型选择方法
  3. 如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的
  4. 构造函数不能是虚函数,派生类不继承基类的构造函数
  5. 友元函数不是类成员,因此友元函数不能是虚函数
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. 虚析构函数
    (1)从下图测试用例中可以看出,基类的析构函数前使用virtual关键字修饰,成为虚析构函数,虚析构函数只会影响使用基类指针指向子类对象的情况,其他情况不受影响,即子类析构的时候会首先调用自己的析构函数然后自动调用父类的析构函数
    (2)当使用类似Brass *pB = new BrassPluse;然后delete pB;时,如果析构函数不是虚函数,只调用对应于指针类型析构函数,即Brass的析构函数,不会调用BrassPluse的析构函数,如果析构函数是虚函数则首先调用BrassPluse的析构函数,然后再调用Brass的析构函数
    在这里插入图片描述

虚函数与缺省参数

  1. 绝不重新定义继承而来的缺省参数值
  2. 虚函数是动态绑定的,而缺省参数值却是静态绑定
  3. 当使用对象调用此函数,一定要指定参数值静态绑定下函数不从其base继承缺省参数值
  4. 若以指针(或引用)调用此函数,可以不指定参数值,动态绑定下这个函数会从其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();

在这里插入图片描述

  1. 令基类中的一个public non-virtual函数调用private virtual函数
  2. private virtual函数可被派生类重新定义
  3. 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)

  1. 抽象基类:是包含纯虚函数的类
  2. 抽象基类不能声明对象,但其派生类可以声明对象,但要在派生类中完全实现基类中所有的纯虚函数
  3. 包含纯虚函数的类只能用作基类
class BaseEllipse
{
private:
	double x;
	double y;
public:
	virtual double Area() const = 0;  //纯虚函数
}

对象成员

  1. 组合(包含): 使类的某些类成员本身是另一个类的对象,是一种实现has-a关系的途径
  2. 使用组合,将对象成员声明为privateprotected时, 类可以获得实现,但不能获得接口
    (1)Student类的成员函数可以使用string类的公有接口来修改name对象
    (2)在Student类外类外不能这么做,只能通过Student类的公有接口访问name
  3. 被包含对象的接口不是公有的,但可以在类中使用它

在这里插入图片描述

  • 初始化被包含的对象:
  1. 对于成员对象,使用成员名进行初始化,此时初始化列表中的每一项都调用与之匹配的构造函数
  2. C++要求在构造对象的其他部分之前,先构造对象的成员对象
  3. 如果省略初始化列表,C++将使用成员对象所属类的默认构造函数
  4. 初始化的顺序: 当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序

在这里插入图片描述

私有继承

  1. 私有继承也是一种实现has-a关系的途径
  2. 使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员
  3. 基类方法不会成为派生类派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们
  4. 私有继承派生类继承基类的实现但不继承基类的接口

在这里插入图片描述

  • 私有继承 vs 组合(包含):
  1. 包含将对象做为一个命名的成员对象添加到类中
  2. 私有继承将对象作为一个未被命名的继承对象添加到类中
  3. 子对象:通过继承包含添加的对象
  4. 应当使用包含来建立has-a关系
  5. 如果需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承
  • 初始化基类组件:
  1. 对于继承类,构造函数使用成员初始化列表语法,并使用类名而不是成员名来标识构造函数

在这里插入图片描述

  • 访问基类的方法:
  1. 使用私有继承时,只能在派生类的方法中使用基类的方法
  2. 使用包含时将使用对象名来调用方法,而使用私有继承时将使用类名作用域解析运算符来调用方法

在这里插入图片描述

  • 访问基类对象:
  1. 可以通过强制类型转换,来访问基类对象本身

在这里插入图片描述

  • 访问基类的友元函数:
  1. 私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用指针

在这里插入图片描述

保护继承

  1. 使用保护继承,基类的公有成员保护成员都将称为派生类的保护成员
  2. 和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的
  3. 当派生类派生出另一个类时,私有继承保护继承的区别:
    (1)使用私有继承,第三代类将不能使用基类的接口,因为基类的公有方法在派生类中将变成私有方法
    (2)使用保护继承,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们

在这里插入图片描述

多重继承

  1. 多重继承(MI): 有多个直接基类的类
  2. 必须使用关键字public来限定每一个基类,除非特别指出,否则编译器将认为时私有派生
  3. 公有MI表示的是is-a关系,私有MI和保护MI表示has-a关系

在这里插入图片描述
在这里插入图片描述

  • 虚基类:
  1. 虚基类: 使多个类(它们的基类相同)派生的对象只继承一个基类对象

在这里插入图片描述

  1. SingerWaiter都继承了一个Worker组件,因此SingingWaiter将包含两个Worker组件
  2. 将派生类对象的地址赋值给基类指针,将出现二义性,ed中有两个Worker对象,应使用类型转换来指定对象

在这里插入图片描述
在这里插入图片描述

  • 虚基类:
  1. 虚基类使得多个类(它们的基类相同)派生出的对象只继承一个基类对象
  2. 基类是虚的时,禁止信息通过中间类自动传递给基类,需要显式调用所需基类的构造函数来构造虚基类,但对于非虚基类,这是非法的
  3. 多重继承可能会导致函数调用的二义性,解决方案:
    (1)使用作用于解析运算符澄清编程者的意图
    (2)在SingingWaiter中重新定义Show(),并指出使用哪个Show()
  4. 通过多条虚途径和非虚途径继承某个特定的基类时,该类包含一个表示所有的虚途径的基类子对象和分别表示各条非虚途径的多个基类子对象

在这里插入图片描述
在这里插入图片描述

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;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 虚基类与支配:
  1. 使用非虚基类时,如果类从不同的类中继承了多个同名成员,则使用该成员名时,若未加类型进行限定,则会导致二义性,但若使用了虚基类这样做并不一定会导致二义性
  2. 在使用虚基类时,如果某个名称优先于其他所有名称,则使用它时,即使不使用限定符,也不会导致二义性
  3. 在使用虚基类时,派生类的名称优先于直接或间接祖先类中的相同名称
  4. C中的q()优先于,类B中的q(),因此F中可以使用q()表示C::q()
  5. 虚二义性规则与访问规则无关,即使E::omg()是私有的,但使用omg()仍然导致二义性

在这里插入图片描述

嵌套类

  1. 嵌套类: 在另一个类中声明的类
  2. 包含类的成员函数可以创建和使用被嵌套类的对象
  3. 仅当声明位于公有部分,才能在包含类的外面使用嵌套类,且必须使用作用域解析运算符
  4. 包含 vs 嵌套: 嵌套 ≠ \neq = 包含
    (1)包含意味着将类对象作为另一个类的成员
    (2)对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效

在这里插入图片描述

  • 嵌套类和访问权限:
  1. 嵌套类声明位置决定了嵌套类的作用域,即它决定了程序的哪些部分可以创建这种类的对象
  2. 嵌套类是在另一个类的私有部分声明的,则只有后者知道它
  3. 嵌套类在另一个类的保护部分声明的,则对后者来说是可见的,但是对于外部世界则是不可见的,然而,派生类将知道嵌套类,并可以直接创建这种类型的对象
  4. 嵌套类是在另一个类的公有部分声明的,则允许后者、后者的派生类以及外部世界使用它,外部使用它时,必须使用类限定符
  5. 嵌套类公有部分保护部分私有部分控制了对类成员的访问,对嵌套类访问权的控制规则对常规类相同

在这里插入图片描述
在这里插入图片描述

  • 类模板的嵌套:
  1. 类模板中也可以进行嵌套,不会因为包含嵌套类而带来麻烦
  2. 包含嵌套类的模板中,模板类型可以在嵌套类内使用

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_46427273

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值