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






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




    1、" 多态 " 引入


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


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

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

    多态 与 继承 正好相反 ,

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

    2、函数重写


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


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

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

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


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


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


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

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

    4、父类与子类示例


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

    // 父类
    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;
    };
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35

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


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

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


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

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


    代码示例 :

    	// 定义父类指针
    	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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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


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

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


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

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


    代码示例 :

    	// 定义父类指针
    	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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16




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




    1、代码示例


    #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;
    }
    
    • 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
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113

    2、执行结果


    执行结果 :

    调用父类构造函数
    调用父类构造函数
    调用子类构造函数
    父类 : x = 1
    子类 : x = 1 , y = 2
    父类 : x = 1
    父类 : x = 1
    父类 : x = 1
    父类 : x = 1
    父类 : x = 1
    父类 : x = 1
    父类 : x = 1
    父类 : x = 1
    请按任意键继续. . .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

  • 相关阅读:
    【python爬虫】10.指挥浏览器自动工作(selenium)
    【目标搜索】基于matlab运动编码粒子群算法优化 (MPSO) 无人机搜索丢失目标【含Matlab源码 2254期】
    毕业季 | 在不断探索中拟合最好的自己
    linux服务器磁盘满了怎么办
    5、Maven创建Web工程
    TEB (Timed Elastic Band)
    【Python项目】Python基于tkinter实现一个笔趣阁小说下载器 | 附源码
    css中5种属性选择器
    CentOS LVM缩容与扩容步骤
    不就是Java吗之数组的定义和使用
  • 原文地址:https://blog.csdn.net/han1202012/article/details/134051740