C++学习笔记11-面向对象2

 1.  只能初始化直接基类

一个类只能初始化自己的直接基类。直接就是在派生列表中指定的类。如果类C 从类B 派生,类B 从类A 派生,则B 是C 的直接基类。虽然每个C 类对象包含一个A 类部分,但C 的构造函数不能直接初始化A 部分。相反,需要类C 初始化类B,而类B 的构造函数再初始化类A。这一限制的原因是,类B 的作者已经指定了怎样构造和初始化B 类型的对象。像类B 的任何用户一样,类C 的作者无权改变这个规约。

2. 重构

将Disc_item 加到Item_base 层次是重构(refactoring)的一个例子。重构包括重新定义类层次,将操作和/或数据从一个类移到另一个类。为了适应应用程序的需要而重新设计类以便增加新函数或处理其他改变时,最有可能需要进行重构。

重构常见在面向对象应用程序中非常常见。值得注意的是,虽然改变了继承层次,使用Bulk_item 类或Item_base 类的代码不需要改变。然而,对类进行重构,或以任意其他方式改变类,使用这些类的任意代码都必须重新编译。

3. 尊重基类接口

构造函数只能初始化其直接基类的原因是每个类都定义了自己的接口。定义Disc_item 时,通过定义它的构造函数指定了怎样初始化Disc_item 对象。一旦类定义了自己的接口,与该类对象的所有交互都应该通过该接口,即使对象是派生类对象的一部分也不例外。同样,派生类构造函数不能初始化基类的成员且不应该对基类成员赋值。如果那些成员为public 或protected,派生构造函数可以在构造函数函数体中给基类成员赋值,但是,这样做会违反基类的接口。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函

 class Derived: publicBase {
 public:
 // Base::~Baseinvoked automatically

数函数体中对这些成员赋值。

4. 派生类析构函数

析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员:

5. 虚析构函数

如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数:

 class Item_base {
 public:
 // no work, butvirtual destructor needed
 // if base pointer thatpoints to a derived object is ever deleted
 virtual ~Item_base(){ }
 };

如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同:

 Item_base *itemP =new Item_base; // same static and dynamic type
 delete itemP;                    // ok:destructor for Item_base called
 itemP = newBulk_item;            // ok: static and dynamic types differ
 delete itemP;<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

6. 作用域与成员函数

在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽:

 struct Base {
 int memfcn();
 };
 struct Derived : Base{
 int memfcn(int);                  // hides memfcn in the base
 };
 Derived d; Base b;
 b.memfcn();                       // calls Base::memfcn
 d.memfcn(10);                     // calls Derived::memfcn
 d.memfcn();                       // error: memfcn with noarguments is hidden
 d.Base::memfcn();                 // ok: calls Base::memfcn

7.  虚函数与作用域

还记得吗,要获得动态绑定,必须通过基类的引用或指针调用虚成员。当我们这样做时,编译器器将在基类中查找函数。假定找到了名字,编译器就检查实参是否与形参匹配。

现在可以理解虚函数为什么必须在基类和派生类中拥有同一原型了。如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。考虑如下(人为的)为集合:

class Base {
 public:
 virtual int fcn();
 };
 class D1 : public Base{
 public:
 // hides fcn in thebase; this fcn is not virtual
 int fcn(int);   //parameter list differs from fcn in Base
 // D1 inheritsdefinition of Base::fcn()
 };
 class D2 : public D1{
 public:
 int fcn(int);   //nonvirtual function hides D1::fcn(int)
 int fcn();      //redefines virtual fcn from Base
 };

D1 中的fcn 版本没有重定义Base 的虚函数fcn,相反,它屏蔽了基类的fcn。结果D1 有两个名为fcn 的函数:类从Base 继承了一个名为fcn 的虚函数,类又定义了自己的名为fcn 的非虚成员函数,该函数接受一个int 形参。但是,从Base 继承的虚函数不能通过D1 对象(或D1 的引用或指针)调用,因为该函数被fcn(int) 的定义屏蔽了。

类D2 重定义了它继承的两个函数,它重定义了Base 中定义的fcn 的原始版本并重定义了D1 中定义的非虚版本

8. 通过基类调用被屏蔽的虚函数

通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:

 Base bobj; D1 d1obj;D2 d2obj;
 Base *bp1 =&bobj, *bp2 = &d1obj, *bp3 = &d2obj;
 bp1->fcn();    // ok:virtual call, will call Base::fcnat run time
 bp2->fcn();    // ok:virtual call, will call Base::fcnat run time
 bp3->fcn();    // ok:virtual call, will call D2::fcnat run time

9. 名字查找与继承

理解C++ 中继承层次的关键在于理解如何确定函数调用。确定函数调用遵循以下四个步骤:

1. 首先确定进行函数调用的对象、引用或指针的静态类型。

2. 在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。

3. 一旦找到了该名字,就进行常规类型检查查看如果给定找到的定义,该函数调用是否合法。

4. 假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。

将派生类对象复制到基类对象时,派生类对象将被切掉

10. 句柄类与继承

C++ 中面向对象编程的一个颇具讽刺意味的地方是,不能使用对象支持面向对象编程,相反,必须使用指针或引用。例如,下面的代码段中:

 voidget_prices(Item_base object,
 const Item_base*pointer,
 const Item_base&reference)
 {
 // which version ofnet_price is called is determined at run time
 cout <<pointer->net_price(1) << endl;
 cout <<reference.net_price(1) << endl;

 // always invokesItem_base::net_price
 cout <<object.net_price(1) << endl;
 }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

通过pointer 和reference 进行的调用在运行时根据它们所绑定对象的动态类型而确定。

时间: 2024-10-20 18:09:49

C++学习笔记11-面向对象2的相关文章

PHP学习笔记——11.面向对象2

1.静态属性/方法 ①定义 在属性/方法前加static修饰,就称之为静态属性/方法 普通属性/方法是依赖于对象的,必须在实例化对象后, 才会出现在内存中,进行访问及操作 而静态属性/方法不依赖于对象,当类声明后,即可进行访问操作 因此,其只会有一份,不会根据对象的增加而增加 ②调用及访问 调用本类 self::$静态属性; self::静态方法(); 调用父类 parent::$静态属性; parent::静态方法(); 外部访问 类名::$静态属性; 类名::静态方法(); ③延迟绑定 父类

C++ Primer 学习笔记_73_面向对象编程 --再谈文本查询示例

面向对象编程 --再谈文本查询示例 引言: 扩展第10.6节的文本查询应用程序,使我们的系统可以支持更复杂的查询. 为了说明问题,将用下面的简单小说来运行查询: Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, like a fiery bird in flight. A beautiful fiery bird, he

C++ Primer 学习笔记_74_面向对象编程 --再谈文本查询示例[续/习题]

面向对象编程 --再谈文本查询示例[续/习题] //P522 习题15.41 //1 in TextQuery.h #ifndef TEXTQUERY_H_INCLUDED #define TEXTQUERY_H_INCLUDED #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <set> #include <map&g

《C++ Primer Plus》学习笔记11

<C++ Primer Plus>学习笔记11 第17章 输入.输出和文件 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

cocos2dx游戏开发学习笔记3-lua面向对象分析

在lua中,可以通过元表来实现类.对象.继承等.与元表相关的方法有setmetatable().__index.getmetatable().__newindex. 具体什么是元表在这里就不细说了,网上很多介绍,这里主要讲与cocos2dx相关联的部分. 在lua-binding库中extern.lua里,有如下方法: --Create an class. function class(classname, super) local superType = type(super) local c

C++ Primer 学习笔记_66_面向对象编程 --定义基类和派生类[续]

算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进.最后,再考虑时间和空间复杂度的问题. 了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maxim

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数、抽象类、虚析构函数、动态创建对象

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数.抽象类.虚析构函数.动态创建对象 一.纯虚函数 1.虚函数是实现多态性的前提 需要在基类中定义共同的接口 接口要定义为虚函数 2.如果基类的接口没办法实现怎么办? 如形状类Shape 解决方法 将这些接口定义为纯虚函数 3.在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做 4.定义纯虚函数: class <类名> { virtual <类型> <函

sqlite学习笔记11:C语言中使用sqlite之删除记录

最后一节,这里记录下如何删除数据. 前面所有的代码都继承在这里了,在Ubuntu14.04和Mac10.9上亲测通过. #include <stdio.h> #include <stdlib.h> #include "sqlite/sqlite3.h" #define DB_NANE "sqlite/test.db" sqlite3 *db = NULL; char* sql = NULL; char *zErrMsg = NULL; con

C++ Primer 学习笔记_65_面向对象编程 --概述、定义基类和派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

lua学习笔记11:lua中的小技巧

lua中的小技巧,即基础lua语言本身的特种,进行一个些简化的操作 一 巧用or x = x or v 等价于: if not x then x = v end 如果x为nil或false,就给他赋值为 二 三元运算符实现 a and b or c 类似C语言: a ? b : c and 的运算由优先级高于or lua学习笔记11:lua中的小技巧,布布扣,bubuko.com