一、类型兼容性原则与函数重写
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
请按任意键继续. . .