【C++】多态 ① ( 类型兼容性原则与函数重写 | “ 多态 “ 引入 | 函数重写 )

2023-10-28 10:40:44 浏览数 (2)

一、类型兼容性原则与函数重写

1、" 多态 " 引入

在面向对象中 , " 多态 " 是 设计模式 的基础 , 是 软件框架 的基础 ;

面向对象的 三大特征 是逐步递进的 , 封装 -> 继承 -> 多态 ;

  • 封装 : 将 成员变量 和 成员方法 封装到 类中 , 是一切的基础 ; 拿到类对象后 , 就可以调用其中的 成员变量 和 成员方法 ;
  • 继承 : 类在 封装 的基础上 , 可以进行继承操作 , 子类 继承 父类的 成员 , 可以复用之前写的代码 ;
  • 多态 : 在 继承 的基础上 , 才能讨论 多态 的概念 ;

多态 与 继承 正好相反 ,

  • 继承 是 复用 之前写的代码 ;
  • 多态 是 复用 之后写的代码 ;

2、函数重写

函数重写 : 同时 在 子类 和 父类 中 , 定义 函数原型 相同 的 函数 , 就是 " 函数重写 " , 子类 重写 父类 中的 函数 ;

父类 中 被子类 重写的 函数 , 仍然被 子类 所继承 ;

在 默认的情况下 , 子类 会 隐藏 父类中 被重写的函数 ,

如果想要 显示调用 父类 的 被重写的函数 , 可以使用 域作用符 父类名称 :: 被重写的函数() 的方式进行调用 ;

3、类型兼容性原则的几类情况

被重写的 函数 , 遇到 类型兼容性原则 时 , 调用的 函数 是 子类重写的函数 , 还是 父类的原有函数 ;

下面根据如下几种情况进行讨论 :

  • 父类对象 和 子类对象 调用 重写的函数 ;
  • 父类指针 指向 父类对象 / 子类对象 调用 重写函数 的执行效果 ;
  • 父类引用 指向 父类对象 / 子类对象 调用 重写函数 的执行效果 ;
  • 父类指针 作为函数参数 , 分别传入 父类对象 / 子类对象 地址 , 查看调用 重写函数 的执行效果 ;
  • 父类引用 作为函数参数 , 分别传入 父类对象 / 子类对象 , 查看调用 重写函数 的执行效果 ;

4、父类与子类示例

在 父类 和 子类 中 , 都定义了 print 函数 , 子类 重写 父类的 该函数 ;

代码语言:javascript复制
// 父类
class Parent {
public:
	Parent(int a)
	{
		x = a;
		cout << "调用父类构造函数" << endl;
	}

	void print()
	{
		cout << "父类 : x = " << x << endl;
	}
public:
	int x;
};

// 子类
class Child : public Parent {
public:
	// 在子类构造函数初始化列表中 调用 父类构造函数
	Child(int a, int b) : Parent(a)
	{
		y = b;
		cout << "调用子类构造函数" << endl;
	}

	// 子类重写父类的 print 函数
	void print()
	{
		cout << "子类 : x = " << x << " , y = " << y << endl;
	}
public:
	int y;
};

5、父类指针 指向 父类对象 / 子类对象

父类 指针 指向 父类对象 , 执行 被子类重写的函数 , 调用的是 父类的 函数 ;

父类 指针 指向 子类对象 , 执行 被子类重写的函数 , 调用的 仍然是 父类的 函数 ;

指针的类型是什么类型 , 调用的就是什么类型的函数 ,

指针类型是 父类 类型 , 那么即使指向子类对象 , 最后调用的也是 父类的成员 ;

代码示例 :

代码语言:javascript复制
	// 定义父类指针
	Parent* p = NULL;

	// 定义 父类 和 子类对象
	Parent parent(1);
	Child child(1, 2);

	// 3. 将 p 指针指向 父类对象
	// 通过 p 指针 调用指向对象的 print 函数
	// 结果 - `父类 : x = 1`
	p = &parent;
	p->print();

	// 4. 将 p 指针指向 子类对象
	// 通过 p 指针 调用指向对象的 print 函数
	// 结果 - `父类 : x = 1`
	// 虽然将 子类对象 地址赋值给了 p 指针 
	// 但是 调用的 函数仍然是 父类的 print 函数
	// 这是 类型兼容性原则 导致的结果
	p = &child;
	p->print();

6、父类引用 指向 父类对象 / 子类对象

父类 引用 指向 父类对象 , 执行 被子类重写的函数 , 调用的是 父类的 函数 ;

父类 引用 指向 子类对象 , 执行 被子类重写的函数 , 调用的 仍然是 父类的 函数 ;

引用的类型是什么类型 , 调用的就是什么类型的函数 ,

引用类型是 父类 类型 , 那么即使指向子类对象 , 最后调用的也是 父类的成员 ;

代码示例 :

代码语言:javascript复制
	// 定义父类指针
	Parent* p = NULL;

	// 定义 父类 和 子类对象
	Parent parent(1);
	Child child(1, 2);

	// 5. 将 Parent 引用 指向 父类对象
	// 结果 - `父类 : x = 1`
	Parent& p2 = parent;
	p2.print();

	// 5. 将 Parent 引用 指向 子类对象
	// 结果 - `父类 : x = 1`
	Parent& p3 = child;
	p3.print();

二、完整代码示例 - 类型兼容性原则与函数重写


1、代码示例

代码语言:javascript复制
#include "iostream"
using namespace std;

// 父类
class Parent {
public:
	Parent(int a)
	{
		x = a;
		cout << "调用父类构造函数" << endl;
	}

	void print()
	{
		cout << "父类 : x = " << x << endl;
	}
public:
	int x;
};

// 子类
class Child : public Parent {
public:
	// 在子类构造函数初始化列表中 调用 父类构造函数
	Child(int a, int b) : Parent(a)
	{
		y = b;
		cout << "调用子类构造函数" << endl;
	}

	// 子类重写父类的 print 函数
	void print()
	{
		cout << "子类 : x = " << x << " , y = " << y << endl;
	}
public:
	int y;
};

// 父类指针作为函数参数
// 分别传入 子类对象 和 父类对象 地址
void fun(Parent* p)
{
	p->print();
}

// 父类引用作为函数参数
// 分别传入 子类对象 和 父类对象 本身
void fun(Parent& p)
{
	p.print();
}


int main() {

	// 定义父类指针
	Parent* p = NULL;

	// 定义 父类 和 子类对象
	Parent parent(1);
	Child child(1, 2);

	// 1. 调用父类对象的 print 函数
	// 结果 - `父类 : x = 1`
	parent.print();

	// 2. 调用子类对象的 print 函数
	// 结果 - `子类 : x = 1 , y = 2`
	child.print();

	// 3. 将 p 指针指向 父类对象
	// 通过 p 指针 调用指向对象的 print 函数
	// 结果 - `父类 : x = 1`
	p = &parent;
	p->print();

	// 4. 将 p 指针指向 子类对象
	// 通过 p 指针 调用指向对象的 print 函数
	// 结果 - `父类 : x = 1`
	// 虽然将 子类对象 地址赋值给了 p 指针 
	// 但是 调用的 函数仍然是 父类的 print 函数
	// 这是 类型兼容性原则 导致的结果
	p = &child;
	p->print();

	// 5. 将 Parent 引用 指向 父类对象
	// 结果 - `父类 : x = 1`
	Parent& p2 = parent;
	p2.print();

	// 5. 将 Parent 引用 指向 子类对象
	// 结果 - `父类 : x = 1`
	Parent& p3 = child;
	p3.print();

	// 6. 父类指针作为函数参数 
	// 传入父类对象地址 , 结果 - `父类 : x = 1`
	fun(&parent);
	// 传入子类对象地址 , 结果 - `父类 : x = 1`
	fun(&child);

	// 7. 父类引用作为函数参数 
	// 传入父类对象本身 , 结果 - `父类 : x = 1`
	fun(parent);
	// 传入子类对象本身 , 结果 - `父类 : x = 1`
	fun(child);
	
	// 控制台暂停 , 按任意键继续向后执行
	system("pause");

	return 0;
}

2、执行结果

执行结果 :

代码语言:javascript复制
调用父类构造函数
调用父类构造函数
调用子类构造函数
父类 : x = 1
子类 : x = 1 , y = 2
父类 : x = 1
父类 : x = 1
父类 : x = 1
父类 : x = 1
父类 : x = 1
父类 : x = 1
父类 : x = 1
父类 : x = 1
请按任意键继续. . .

0 人点赞