我是靠谱客的博主 害怕月饼,这篇文章主要介绍类的继承与多态前言一、类的继承与派生二、多态性,现在分享给大家,希望可以做个参考。

文章目录

  • 前言
  • 一、类的继承与派生
    • 1.概念
    • 2.单继承
      • (1)语法格式:
      • (2)基类成员在不同派生类中的引用权限
      • (3)派生类与基类同名成员的访问方式
      • (4)赋值兼容规则
      • (5)单继承的构造与析构
    • 3.多继承
      • (1)语法格式:
      • (2)多继承派生类的构造和析构:
      • (3)多继承中的二义性问题
  • 二、多态性
    • 1.运算符的重载
    • 2.虚函数
    • 3.虚函数的实现机制
    • 4.纯虚函数及抽象类


前言

此博客主要讲述类的继承与派生的相关内容,视频请参考
MOOC第九章


提示:以下是本篇文章正文内容,下面案例可供参考

一、类的继承与派生

1.概念

继承——子类具有父类的性质,即在已存在的类上建立另一个新的类。(已存在的类叫做基类或父类,新建立的类叫做子类或派生类)
派生——子类拥有父类没有的性质。

派生类的功能

  1. 吸收基类成员
  2. 改造基类成员
  3. 添加新成员

2.单继承

派生类只有一个直接基类

(1)语法格式:

class 派生类名 : <继承方式> 基类名{…//新成员和修改的基类成员}
继承方式包括public,private,protected.

补充:保护成员特性:

  1. 能够被基类和派生类访问
  2. 和私有成员一样,内外不能访问

(2)基类成员在不同派生类中的引用权限

基类公有成员私有成员保护成员
公有派生类公有成员不可访问成员保护成员
私有派生类私有成员不可访问成员私有成员
保护派生类保护成员不可访问成员保护成员

(3)派生类与基类同名成员的访问方式

即基类成员名与派生类成员名相同

同名访问规则:即如何区分该成员是基类还是派生类成员

在派生类中使用基类的同名成员:语法格式:基类名::成员

若使用派生类成员,则直接使用。派生类成员覆盖基类成员

对象使用基类的成员:对象名.基类名::成员名。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Base { protected: int v1; public: int v2; Base(int a = 0,int b = 0) { v1 = a; v2 = b; } }; class Devrid :public Base { int v2; public: int v3; Devrid(int a = 0, int b = 0) { v2 = a; v3 = b; } void func() { int sum1 = v1 + v2 + v3;//使用派生类成员v2 int sum2 = v1 + Base::v2 + v3;//使用基类成员v2 } }; int main() { Devrid obj(5, 6); obj.Base::v2 = 8;//基类成员的v2,可以访问 //obj.v2 = 7;//派生类成员的v2,不可访问 return 0; }

(4)赋值兼容规则

提出:数据类型可以相互转化,但不同类型的类对象不可以相互转换,赋值兼容规则就是将子类对象转化为基类对象

内容:
在公有派生方式下:派生类对象可以作为基类对象类使用,具体方法如下:

  1. 派生类对象直接赋值给基类对象
  2. 派生类对象赋值给基类对象的引用
  3. 基类对象的指针指向派生类对象

只能将派生类对象赋值给基类对象

(5)单继承的构造与析构

单继承派生类的构造函数
语法格式:
派生类构造函数(参数表):基类构造函数(参数表),对象成员名1(参数表), … 对象成员名n(参数表),
{
… //初始化自定义数据成员
}

如果基类使用缺省的构造函数或不带参的构造函数,则可以在初始化列表中省略,如果没有对象成员,也可以省略。
例子:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Cirle { //Point center; float radios; public: Cirle(float x, float y, float r) :center(x, y)//给point对象初始化 { radios = r; } }; class ColorCirle :public Cirle { int color; public: ColorCirle(float x, float y, float r, int color) :Cirle(x, y, r)//先给基类对象初始化 { this->color = color; } };

构造函数的调用顺序:

  1. 先调用基类构造函数
  2. 再调用对象成员所属类的构造函数
  3. 最后调用派生类构造函数

析构函数的调用顺序:

  1. 先调用派生类的析构函数
  2. 再调用对象成员所属类的析构函数
  3. 最后调用派生类析构函数

3.多继承

派生类只有多个直接基类

(1)语法格式:

class 派生类名 : <继承方式1> 基类名1,
<继承方式2> 基类名2,
<继承方式3> 基类名3,

{…//新成员和修改的基类成员}
继承方式包括public,private,protected.

(2)多继承派生类的构造和析构:

多继承派生类的构造函数
语法格式:
派生类构造函数(参数表):基类名1(参数表1),
基类名2(参数表2),
对象成员名1(参数表), … 对象成员名n(参数表),
{
… //派生类新添加的成员
}

派生类构造函数与析构函数执行顺序与单继承一致
基类对象的调用顺序与声明继承有关,对象成员的调用顺序按照类中的调用顺序。

(3)多继承中的二义性问题

可能出现的情况有:

  • 访问不同基类的具有相同名字的成员时可能出现二义性
    解决办法是用类名对成员加以限定,如c1.A::f()

  • 访问共同基类的成员可能出现二义性
    解决办法是使用虚基类

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class A { public: int a; void g(){} }; class B1 :virtual public A { int b1; }; class B2 :virtual public A { int b2; }; class C :public B1, public B2 { int c; public: int f(){} }; int main() { C obj; obj.a = 8;//通过B1->A来的,因为先调用B1的基类构造函数,而调用B2的基类构造函数时,发现这个基类是虚基类,因此直接引用 obj.g(); return 0; }

虚基类调用构造函数的次序

  • 虚基类调用构造函数在非虚基类之前
  • 在同一层次包含多个虚基类,按照说明次序调用
  • 若虚基类是非虚基类的派生,则先调用基类构造函数,后调用派生类构造函数

二、多态性

1.运算符的重载

多态性的分类:

编译时的多态:
函数重载
运算符重载
运行时的多态
虚函数

运算符重载:重新定义运算符作用在类类型上的含义

例子:复数的运算符重载

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream> using namespace std; class Complex { double re, im; public: Complex(double i=0.0,double r=0.0):re(r),im(i){} friend Complex operator+(Complex c1, Complex c2); friend ostream& operator<<(ostream& out, Complex& obj); }; Complex operator+(Complex c1, Complex c2) { Complex t; t.re = c1.re + c2.re; t.im = c1.im + c2.im; return t; } ostream& operator<<(ostream& out, Complex& obj) { out << obj.re << "+" << obj.im << "i"; return out; } int main() { Complex c1(1, 2), c2(3, 4); Complex c3 = c1 + c2; cout << c3; return 0; }

2.虚函数

在该函数前加上virtual关键字,该函数即为虚函数

一般情况下,派生类拥有从基类继承的成员,因此相对于已经定义的基类对象,如果将此对象重新定义为派生类对象,派生类从基类继承的成员对这个对象是不可见的,也就是说不能使用。

为达到以上目的,应该使用虚函数。

虚函数的特性:可以在一个或多个派生类中被重新定义,但要求在重定义时虚函数的原型(包括返回值类型,函数名,参数列表)必须完全相同。

基类中的函数具有虚特性的条件:

  • 在基类中用virtual将函数说明为虚函数
  • 公有派生类中原型一致的重载该函数
  • 定义基类引用或指针,使其引用或指向派生类对象。当通过该引用或指针调用虚函数时,该函数将体现虚特性来

多态如何实现

基类必须指出希望被派生类重定义的那些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的不能定义为虚函数

3.虚函数的实现机制

实现机制是通过函数指针来实现的

虚函数表和虚指针:

  • 在编译时,为每个有虚函数的类建立一张虚函数表VTABLE,表中存放的是每一个虚函数的指针;同时用一个虚指针VRTR指向这张表的入口
  • 访问某个虚函数时,不是直接找到那个函数的地址,而是通过VRTR间接查到它的地址。

VRTR由构造函数初始化

对虚函数的要求:

  • 虚函数必须是类的非静态成员
  • 不能将虚函数说明为全局函数
  • 不能将虚函数说明为静态成员函数
  • 不能将虚函数说明为友元函数

期望将析构函数定义为虚函数,这样容易处理基类和派生类的空间,而构造函数不能定义为虚函数

4.纯虚函数及抽象类

基类中的公共接口只需要有说明而不需要有实现,即为纯虚函数。具体的实现由派生类定义。

语法形式:
virtual 函数类型 函数名(参数列表)=0
试例:

复制代码
1
2
3
4
5
6
class Shap//抽象类 { virtual float Perimeter() = 0;//纯虚函数定义 virtual float Area() = 0; };

相关概念:

  • 将一个函数说明为纯虚函数,就要求任何派生类都定义自己的实现。
  • 拥有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为基类被使用。
  • 抽象类的派生类需要实现纯虚函数,若有一个没实现,则该类也为抽象类。
  • 当抽象类的所有函数成员都是纯虚函数时,这个类被称为接口类

最后

以上就是害怕月饼最近收集整理的关于类的继承与多态前言一、类的继承与派生二、多态性的全部内容,更多相关内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部