类的继承与多态

文章目录

  • 前言
  • 一、类的继承与派生
    • 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)派生类与基类同名成员的访问方式

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

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

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

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

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

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(参数表),
{
… //初始化自定义数据成员
}

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

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()

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

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.运算符的重载

多态性的分类:

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

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

例子:复数的运算符重载

#include
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
试例:

class Shap//抽象类
{
	virtual float Perimeter() = 0;//纯虚函数定义
	virtual float Area() = 0;
};

相关概念:

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

你可能感兴趣的:(c++,继承,多态,抽象类)