28、不一样的C++系列--继承与多态
来源:互联网 发布:php推广链接代码 编辑:程序博客网 时间:2024/06/04 21:02
继承与多态
父子间的同名冲突
首先来看一段代码:
#include <iostream>#include <string>using namespace std;class Parent{public: int mi;};class Child : public Parent{public: int mi;};int main(){ Child c; //这里的mi是Parent中的还是Child中的呢? c.mi = 100; return 0;}
编译通过,说明子类可以定义和父类相同的同名成员。
- 子类可以定义父类中的同名成员
- 子类中的成员将隐藏父类中的同名成员
- 父类中的同名成员依然存在于子类中
- 通过作用域分辨符( : : )访问父类中的同名成员
- 访问父类中的同名成员
Child c;//子类中的mic.mi = 100;//父类中的mic.Parent::mi = 1000;
再来看一个例子:
#include <iostream>#include <string>using namespace std;//定义一个命名空间Anamespace A{ int g_i = 0;}//定义一个命名空间Bnamespace B{ int g_i = 1;}class Parent{public: int mi; Parent() { cout << "Parent() : " << "&mi = " << &mi << endl; }};class Child : public Parent{public: int mi; Child() { cout << "Child() : " << "&mi = " << &mi << endl; }};int main(){ Child c; //向子类的mi成员赋值100 c.mi = 100; //通过作用域向父类的mi成员赋值1000 c.Parent::mi = 1000; //打印子类中mi的地址 cout << "&c.mi = " << &c.mi << endl; //打印子类中mi的内容 cout << "c.mi = " << c.mi << endl; //打印父类中mi的地址 cout << "&c.Parent::mi = " << &c.Parent::mi << endl; //打印父类中mi的内容 cout << "c.Parent::mi = " << c.Parent::mi << endl; return 0;}
输出结果为:
Parent() : &mi = 0x7fff57f57a90Child() : &mi = 0x7fff57f57a94&c.mi = 0x7fff57f57a94c.mi = 100&c.Parent::mi = 0x7fff57f57a90c.Parent::mi = 1000
- 类中的成员函数可以进行重载
- 重载函数的本质为多个不同的函数
- 函数名和参数列表是唯一的标识
- 函数重载必须发生在同一个作用域中
- 所以父子之间的同名成员不构成重载
比如像这样:
class Parent{public: int mi; void add(int v) { mi += v; } void add(int a, int b) { mi += (a + b); }};class Child : public Parent{public: int mi; void add(int v) { mi += v; } void add(int a, int b) { mi += (a + b); } void add(int x, int y, int z) { mi += (x + y + z); }};
代码 Parent类 和 Child类 中有同名的函数add ,但是两个类之间不构成重载,只有Parent类中多个add函数构成重载。
- 子类中的函数将隐藏父类的同名函数
- 子类无法重载父类中的成员函数
- 使用作用域分辨符访问父类中的同名函数
- 子类可以定义父类中完全相同的成员函数
父子间的赋值兼容
- 子类对象可以当作父类对象使用(兼容性)
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
举个例子:
#include <iostream>#include <string>using namespace std;class Parent{public: int mi; void add(int i) { mi += i; } void add(int a, int b) { mi += (a + b); }};class Child : public Parent{public: int mv; void add(int x, int y, int z) { mv += (x + y + z); }};int main(){ Parent p; Child c; //子类对象可以直接赋值给父类对象 p = c; //子类对象可以直接初始化父类对象 Parent p1(c); //父类引用可以直接引用子类对象 Parent& rp = c; //父类指针可以直接指向子类对象 Parent* pp = &c; return 0;}
在main函数中进行上述几条的操作都没有出现编译出错。现在进行这样操作:
rp.mi = 100;rp.add(5); rp.add(10, 10);
发现可以编译通过,并没有出现同名覆盖的问题。但是如果这样操作:
pp->mv = 1000;pp->add(1, 10, 100);
运行以后就会报错,报错信息如下:
48-1.cpp:51:10: error: no member named 'mv' in 'Parent' pp->mv = 1000; ~~ ^48-1.cpp:52:10: error: no matching member function for call to 'add' pp->add(1, 10, 100); ~~~~^~~48-1.cpp:16:10: note: candidate function not viable: requires 2 arguments, but 3 were provided void add(int a, int b) ^48-1.cpp:11:10: note: candidate function not viable: requires single argument 'i', but 3 arguments were provided void add(int i) ^2 errors generated.
信息提示没有找到带有3个参数的add函数。为什么呢?
- 当使用父类指针(引用)指向子类对象时
- 子类对象退化为父类对象
- 只能访问父类中定义的成员
- 可以直接访问被子类覆盖的同名成员
特殊的同名函数
- 子类中可以冲定义父类中已经存在的成员函数
- 这种冲定义发生在继承中,叫做函数重写
- 函数重写是同名覆盖的一种特殊情况
例如:
class Parent{ public: void print() { cout << "I'm Parent." << endl; }};//函数重写class Child : public Parent{ public: void print() { cout << "I'm Child" << endl; }};
假如函数重写和赋值兼容同时出现呢? 就像这样:
#include <iostream>#include <string>using namespace std;class Parent{public: int mi; void add(int i) { mi += i; } void add(int a, int b) { mi += (a + b); } void print() { cout << "I'm Parent." << endl; }};class Child : public Parent{public: int mv; void add(int x, int y, int z) { mv += (x + y + z); } void print() { cout << "I'm Child." << endl; }};void how_to_print(Parent* p){ p->print();}int main(){ Parent p; Child c; how_to_print(&p); // Expected to print: I'm Parent. how_to_print(&c); // Expected to print: I'm Child. return 0;}
预期输出是 I'm Parent.
和 I'm Child.
。但是实际输出:
I'm Parent.I'm Parent.
- 问题分析
- 编译期间,编译器只能根据指针的类型判断所指向的对象
- 根据赋值兼容,编译器认为父类指针指向的是父类对象
- 因此,编译结果只可能是调用父类中定义的同名函数
在编译 void how_to_print(Parent* p)
这个函数时,编译器不可能知道指针p究竟指向了什么,但是编译器没有理由报错。于是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。
多态的概念和意义
- 函数重写回顾
- 父类中被重写的函数依然会继承给子类
- 子类中重写的函数将覆盖父类中的函数
- 通过作用域分辨符( : : )可以访问到父类中的函数
就像这样:
Child c;Parent* p = &c;c.Parent::print(); //从父类中继承c.print(); //从子类中重写p->print(); //父类中定义
虽然程序逻辑是这样,但并不是我们所期望的。面向对象中期望的行为:
- 根据
实际的对象类型
判断如何调用重写函数 父类指针(引用)
指向父类对象
则调用父类
中定义的函数子类对象
则调用子类
中定义的重写函数
这里就引出了面向对象中的 多态
的概念:
- 根据实际的
对象类型决定函数调用
的具体目标 - 同样的
调用语句
在实际运行时有多种不同的表现形态
例如:
p->print();
p指向父类对象时,会执行
void print(){ cout << "I'm Parent" << end;}
p指向子类对象时,会执行
void print(){ cout << "I'm Child" << endl;}
- C++语言直接支持多态的概念
- 通过使用
virtual
关键字对多态进行支持 - 被
virtual
声明的函数被重写后具有多态特性 - 被
virtual
声明的函数叫做虚函数
- 通过使用
举个例子:
#include <iostream>#include <string>using namespace std;class Parent{public: //用 virtual 关键字修饰,则具有多态特性 virtual void print() { cout << "I'm Parent." << endl; }};class Child : public Parent{public: void print() { cout << "I'm Child." << endl; }};void how_to_print(Parent* p){ // 展现多态的行为 p->print(); }int main(){ Parent p; Child c; how_to_print(&p); // Expected to print: I'm Parent. how_to_print(&c); // Expected to print: I'm Child. return 0;}
执行结果为:
I'm Parent.I'm Child.
- 多态的意义
- 在程序运行过程中展现出动态的特性
- 函数重写必须多态实现,否则没有意义
- 多态是面向对象组件化程序设计的基础特性
静态联编和动态联编
- 理论中的概念
- 静态联编
- 在程序的编译期间就能确定具体的函数调用。 如:函数重载
- 动态联编
- 在程序实际运行后才能确定具体的函数调用。如:函数重写
- 静态联编
举个例子:
#include <iostream>#include <string>using namespace std;class Parent{public: //函数重载 并且用 virtual 关键字修饰 virtual void func() { cout << "void func()" << endl; } //函数重载 并且用 virtual 关键字修饰 virtual void func(int i) { cout << "void func(int i) : " << i << endl; } //函数重载 并且用 virtual 关键字修饰 virtual void func(int i, int j) { cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl; }};class Child : public Parent{public: //函数重载 void func(int i, int j) { cout << "void func(int i, int j) : " << i + j << endl; } //函数重载 void func(int i, int j, int k) { cout << "void func(int i, int j, int k) : " << i + j + k << endl; }};void run(Parent* p){ p->func(1, 2); // 展现多态的特性 // 动态联编}int main(){ Parent p; p.func(); // 静态联编 p.func(1); // 静态联编 p.func(1, 2); // 静态联编 cout << endl; Child c; c.func(1, 2); // 静态联编 cout << endl; run(&p); run(&c); return 0;}
运行结果为:
void func()void func(int i) : 1void func(int i, int j) : (1, 2)void func(int i, int j) : 3void func(int i, int j) : (1, 2)void func(int i, int j) : 3
小结
- 子类可以定义父类的
同名成员
,定义时子类中的成员将隐藏
父类中的同名成员
子类和父类
中的函数不能构成重载关系
- 使用
作用域分辨符
可以访问父类中的同名成员
子类对象
可以当做父类对象
使用父类指针
可以正确的指向子类对象
父类引用
可以正确的代表子类对象
- 子类中可以重写父类中的
成员函数
- 函数重写只可能发生在
父类与子类
之间 - 多态是根据
实际对象的类型
确定调用的具体函数
virtual关键字
是C++中支持多态
的唯一方式- 被重写的
虚函数
可表现出多态的特性
阅读全文
0 0
- 28、不一样的C++系列--继承与多态
- 31、不一样的C++系列--多重继承
- 27、不一样的C++系列--继承的构造与析构
- 26、不一样的C++系列--继承的基础知识
- 1、不一样的C++系列--C到C++的升级
- 37、不一样的C++系列--C语言异常处理
- C++--继承与多态
- 9、不一样的C++系列--类与封装
- 33、不一样的C++系列--类模板与特化
- 不一样的C++系列--类模板与特化
- 程序设计基石与实践系列之C中的继承和多态
- C语言下的封装、继承与多态
- C语言模拟实现C++的继承与多态
- C语言模拟实现C++的继承与多态
- C语言实现C++的封装继承与多态
- C语言模拟实现C++的继承与多态
- C中的继承与多态
- C#(二) -继承与多态
- Leetcode Subsets
- ValueAnimation应用
- android面试总结
- 虚拟机上装oracle,cmd窗口输入法有问题,按了U,I,O,P,J,K,L,M这些键为什么不是UIOPJK
- android webview 解决回退重定向的问题
- 28、不一样的C++系列--继承与多态
- Ubuntu下android手机通过usb连接电脑,显示"???????????? no permissions"问题
- 解决reportmachine导出pdf有蓝色底色问题
- SQL 语法概要
- file命令
- STM32F1(Cortex M3内核)存储器映射
- 常用的下载文件方式
- OpenCV实现人脸识别——EigenFace特征脸法
- 用maven 编译 elasticsearch-analysis-ik-5.4.0 报错问题