重学C++ (十一) OOP面向对象编程(2)
转换与继承
本节主要须要区分的是:
基类和派生类的转换;
引用(指针)的转换和对象的转换。
1.每一个派生类对象包括一个基类部分。因此。能够像使用基类对象一样在派生类对象上执行操作。
基于这一点,能够将派生类对象的引用(指针)转换为基类子对象的引用(指针),且存在自己主动转换。
反之,基类到派生类的自己主动转换是不存在的,因此基类不包括派生类型的成员。另外,将基类指针或引用绑定到派生类对象时也存在限制,由于编译器编译时无法知道该转换是安全的(编译器确定转换是否合法,仅仅看指针或引用的静态类型)。假设我们确定基类到派生类的转换是安全的,能够使用static_cast强制编译器进行转换,也能够用dynamic_cast申请在执行时进行检查。
2.引用转换不同于对象转换。
在引用转换中。对象本身未被复制,转换不会在不论什么方面改变派生类型对象。
在对象转换中(不是引用或指针),形參类型是固定的。将派生类对象传给基类对象形參时,该派生类对象的基类部分被【复制】到形參,这里是对基类对象进行初始化或赋值:初始化时调用构造函数,赋值时调用赋值操作符。(其实,一般基类的复制构造函数和赋值操作符的形參是基类类型的const引用,由于存在从派生类引用到基类引用的转换,故这两个成员函数可用于从派生类对象到基类对象进行初始化或赋值)。
构造函数和复制控制
构造函数
样例1:
class Bulk_item: public Item_base
{
public:
Bulk_item():min_qty(0), discount(0.0) {}
};
该构造函数隐式调用基类的默认构造函数初始化对象的基类部分:首先使用Item_base的默认构造函数初始化Item_base部分,之后再初始化Bulk_item部分的成员,并执行构造函数的函数体。
样例2:
class Bulk_item: public Item_base
{
public:
Bulk_item(const std::string& book, double sales_price, std::size_t qty=0, double disc_rate=0.0):
Item_base(book, sales_price),min_qty(qty), discount(disc_rate) {}
};
该构造函数使用有两个形參的Item_base构造函数初始化基类子对象。
*首先初始化基类。然后依据【声明次序】初始化派生类的成员。
*一个类仅仅能初始化自己的【直接】基类。
复制控制和继承
*仅仅包括类类型或内置类型数据成员,不含指针的类一般能够使用合成操作。复制、复制或撤销这种成员不须要特殊控制。
假设派生类定义了自己的复制构造函数,该复制构造函数一般应【显式】使用基类复制构造函数初始化对象的基类部分:
class Base {/* …… */};
class Derived: public Base
{
public:
//Base::Base(const Base&) not invoked automatically
Derived(const Dervied& d):
Base(d) /* other member initalization*/
{/* …… */}
};
Base(d)将派生类对象d转换为它的基类部分的引用,并调用基类复制构造函数。
*假设忽略Base(d)。则将执行Base的【默认】构造函数初始化对象的基类部分——并不符合我们【复制】的本意。
*对于复制操作符。必须防止自身赋值。
Derived &Derived::oprator=(const Derived &rhs)
{
if (this != &rhs)
{
Base::operator=(rhs); //assign the base part
//assign the derived part
}
return *this;
}
派生类析构函数不负责撤销基类对象的成员。【编译器】总是【显式】调用派生类对象基类部分的析构函数。每一个析构函数仅仅负责清楚自己的成员:
class Derived: public Base
{
public:
//Base::~Base() invoked automatically
~Derived() {/* 清理派生类部分的成员 */}
};
*对象的撤销顺序与构造顺序相反:先执行派生类析构函数。然后按继承层次依次向上调用个基类析构函数。
虚析构函数
要保证执行适当的析构函数。基类中的析构函数必须为虚函数:
class Item_base
{
virtual ~Item_base() {}
};
Item_base *itemP = new Item_base;
delete itemP; //调用Item_base的析构函数
itemP = new Bulk_item; //指针的静态类型和动态类型不同
delete itemP; //调用Bulk_item的析构函数
在上述情况中。假设我们不定义虚析构函数。则第二个delete调用的将是Item_base的析构函数,这对于派生类型是没有定义的行为。
*构造函数不能定义为虚函数。赋值操作符设为虚函数则可能会令人混淆,并且不会有什么用处。
构造函数和析构函数中的虚函数
首先须要明白:
构造派生类对象时先执行基类构造函数,此时对象的派生类部分是未初始化的。
撤销派生类对象时先撤销派生类部分。
在这两种情况下,对象都是不完整的。
因此,在基类构造函数或析构函数中,编译器将派生类对象【以基类类型对象对待】。
假设在构造函数或析构函数中调用虚函数。执行的是构造函数或析构函数自身类型定义的版本号。
理由:
假设从基类构造函数中调用虚函数的派生类版本号,则派生类版本号可能会调用派生类成员。而此时对象的派生部分成员还未初始化!
该訪问将可能导致程序崩溃。
继承情况下的类作用域
在继承情况下,派生类的作用域嵌套在基类作用域中。
假设不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。
与基类成员同名的派生类成员将屏蔽对基类成员的直接訪问(可通过作用域操作符訪问被屏蔽的基类成员)。
注意。对于函数。即使函数原型不同。仅仅要名字相同。基类成员就会被屏蔽。
局部作用域中声明的函数不会重载全局作用域中定义的函数,相同,派生类中定义的函数也不重载基类中定义的成员。
因此,假设基类有下面函数:
int memfcn();
而派生类有下面函数:
int memfcn(int);
则对派生类调用memfcn()将报错。因此不存在对基类函数的重载,基类中的memfcn()被屏蔽。
这也解释了虚函数为什么必须在基类和派生类中拥有同一原型。
看下面代码:
class Base
{
public:
virtual int fcn();
};
class D1: public Base
{
public:
int fcn(int); //屏蔽基类的fcn()
};
class D2: public D1
{
public:
int fcn(int); //非虚函数,屏蔽D1的fcn(int)
int fcn(); //虚函数,重写基类的fcn()
};
Base bobj;
D1 d1obj;
D2 d2obj;
Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;
bp1->fcn(); //call Base::fcn at run time
bp2->fcn(); //call Base::fcn at run time
bp3->fcn(); //call D2::fcn at run time
三个指针都是基类类型的指针,故通过在Base中查找fcn确定这三个调用。
由于fcn是虚函数,所以编译器会生成代码,在执行时基于引用或指针所绑定的实际类型进行调用。
纯虚函数
在函数形參表后面写上=0指定纯虚函数:
double fcn(std::size_t) const = 0;
含有一个或多个纯虚函数的类是抽象基类,除了作为抽象基类的派生类的对象的组成部分。不能创建抽象类型的对象。
容器与继承
假设一个容器存放的是基类类型的对象,当插入一个派生类对象时,会将派生类对象的基类部分【复制】到基类对象并保存在容器中(因此派生类部分将被切掉。也就是说容器里的仅仅能是基类对象,而不是派生类对象)。
一个解决方式是,使用容器保存对象的指针。
重学C++ (十一) OOP面向对象编程(2)的更多相关文章
- 重学前端--js是面向对象还是基于对象?
重学前端-面向对象 跟着winter老师一起,重新认识前端的知识框架 js面向对象或基于对象编程 以前感觉这两个在本质上没有什么区别,面向对象和基于对象都是对一个抽象的对象拥有一系列的行为和状态,本质 ...
- Java实现OOP(面向对象编程)
一.对象的综述 面向对象编程(OOP)具有多方面的吸引力.对管理人员,它实现了更快和更廉价的开发与维护过程.对分析与设计人员,建模处理变得更加简单,能生成清晰.易于维护的设计方案.对程序员,对象模型显 ...
- 用C实现OOP面向对象编程(1)
如摘要所说,C语言不支持OOP(面向对象的编程).并这不意味着我们就不能对C进行面向对象的开发,只是过程要复杂许多.原来以C++的许多工作,在C语言中需我们手动去完成. 博主将与大家一起研究一下如下用 ...
- 重学js之JavaScript 面向对象的程序设计(创建对象)
注意: 本文章为 <重学js之JavaScript高级程序设计>系列第五章[JavaScript引用类型]. 关于<重学js之JavaScript高级程序设计>是重新回顾js基 ...
- 一百零六、SAP的OOP面向对象编程,OO-ALV的简介
面向对象编程,如图 基本概念: 1.对象(Object)是一个现实实体的抽象.一个对象可被认为是一个把数据(属性)和程序(方法)封装在一起的实体,这个程序产生该对象的动作或对它接受到的外界信号的反应. ...
- Swift -POP( 面向协议编程)与OOP(面向对象编程)
面向协议编程(Protocol Oriented Programming,简称POP),是Swift的一种编程范式,Apple于2015年WWDC提出的,如果大家看Swift的标准库,就会看到大量PO ...
- (转)OOP(面向对象编程)的几大原则
文章转载自:http://blog.csdn.net/anders_zhuo/article/details/8949566 设计模式遵循的一般原则: 1.开-闭原则(Open-Closed Prin ...
- OOP(面向对象编程)的一些特性
接口:接口是把公共实例(非静态)方法和属性结合起来,以封装特定功能的一个集合.一旦定义了接口,就可以在类中实现它.接口注意事项:接口不能单独存在.不能像实例化一个类那样实例化接口.另外,接口不能包含实 ...
- OOP面向对象编程(下)
我们怎么去模拟重载,在javasceipr中我们可以通过参数的类型区别或者数量的区别,来去让同样一个函数名字,可以根据不同的参数列表的情况来去调用相应的函数. javascript中函数类型是不确定的 ...
随机推荐
- 【洛谷1654/BZOJ4318】OSU!(期望DP)
题目: 洛谷1654 分析: 本人数学菜得要命,这题看了一整天才看明白-- 先说说什么是"期望".不太严谨地说,若离散型随机变量(可以看作"事件")\(X\)取 ...
- 12.Nodes
Nodes(节点) Animation(动画) KeyframeAnimation 逐帧动画,该节点中包含了所有绑定属性的动画逻辑 Animation Group 逐帧动画分组 S ...
- CSS——tab导航demo
问题总结: 1.ul要比外套div宽度的值大一点 2.ul需要往左移动1px 3.外套的div设置overflow隐藏 解决抖动: 1.li宽度设置98px,padding左右值1px,hover之后 ...
- linux杀掉某个进程的脚本
https://www.cnblogs.com/zeng1994/p/13a2c5a28e55dd3abc2c75a4fb80371a.html awk的说明: https://www.cnblogs ...
- [入门指南]-技术学习-Ebean
占坑 官网
- Python----DFS---骑士周游问题
这篇文章将会将一个数据结构与算法中一个很经典很重要的概念——深度优先搜索(Depth-First-Search:DFS).........(你他喵不是在标题里说了吗?) 好吧,DFS的精髓我其实也还没 ...
- Python学习【第5篇】:Python之函数(自定义函数,内置函数,装饰器,迭代器,生成器、模块)
一.为什么要使用函数? 1.避免代码重用 2.提高代码的可读性 二.函数的定义与调用 1. def 函数名(参数1,参数2): ''' 函数注释''' print('函数体') return 返回值 ...
- 数据结构与算法(6) -- heap
binary heap就是一种complete binary tree(完全二叉树).也就是说,整棵binary tree除了最底层的叶节点之外,都是满的.而最底层的叶节点由左至右又不得有空隙. 以上 ...
- 20180429NOIP提高组精英班Day1测试
- [luogu4728 HNOI2009] 双递增序列 (dp)
传送门 Solution 前几天刚做了类似题,这种将一个序列拆分为两个单调序列的题一般都是设\(dp[i]\)表示i为一个单调序列的末尾时,另一个序列的末尾是多少 然后应用贪心的思想,在这道题中就是让 ...