C++ Primer 学习笔记_70_面向对象编程 --纯虚函数、容器与继承

面向对象编程

--纯虚函数、容器与继承

I.纯虚函数

在函数形参后面写上 =0 以指定纯虚函数:

class Disc_item : public Item_base
{
public:
    double net_price(size_t) const = 0;	//指定纯虚函数
};

将函数定义为纯虚函数能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类的版本绝不会调用。重要的是,用户将不能创建Disc_item类型的对象。

    Disc_item discount; //Error
    Bulk_item bulk;     //OK
    //因为Bulk_item类继承自 Disc_item 类,并且重写了 net_price 函数

【注解】

含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象组成部分,不能创建抽象类型的对象。

//P503 习题15.26/27
class Disc_item : public Item_base
{
public:
    Disc_item(const std::string &book = "",
              double sales_price = 0.0,
              std::size_t qty = 0,
              double disc_rate = 0.0):
        Item_base(book,sales_price),quantity(qty),discount(disc_rate) {}

    double net_price(size_t) const = 0;

protected:
    std::size_t quantity;
    double discount;
};

//...
int main()
{
    Disc_item item;
}

错误信息:

II.容器与继承

我们希望使用容器(或内置数组)保存因继承而相关联的对象。但是,对象不是多态的,这一事实对将容器用于继承层次中的类型有影响。

如果定义multiset保存基类类型的对象:

    multiset<Item_base> basket;
    Item_base base;
    Bulk_item bulk;

    basket.insert(base);    //OK
    basket.insert(bulk);    //OK:但是,bulk的派生类部分将被无情的“切割”掉

则加入派生类型的对象时,只将对象的基类部分保存在容器中。谨记:将派生类对象复制到基类对象时,派生类对象将被切掉。因此,一旦放入了multiset,他就不再是派生类对象了。

【小心地雷】

因为派生类对象在赋值给基类对象时会被“切掉”,所以容器与通过继承相关的类型不能很好的融合。

不能通过定义容器保存派生类对象来解决这个问题。在这种情况下,不能将Item_base对象放入容器—— 没有从基类类型到派生类型的标准转换。可以显式地将基类对象强制转换为派生类对象并将结果对象加入容器,但是,如果这样做,当试图使用这样的元素时,会产生大问题:在这种情况下,元素可以当作派生类对象对待,但派生类部分的成员将是未初始化的。

唯一可行的选择可能是使用容器保存对象的指针。这个策略可行,但代价是需要用户面对管理对象和指针的问题,用户必须保证只要容器存在,被指向的对象就存在。如果对象是动态分配的,用户必须保证在容器消失时适当地释放对象。

//P504 习题15.28
int main()
{
    vector<Item_base> itemVec;
    for (size_t i = 0; i != 10; ++i)
    {
        Bulk_item item("C++ Primer",6,5,0.5);
        itemVec.push_back(item);
    }

    double sum = 0;
    for (vector<Item_base>::iterator iter = itemVec.begin();
            iter != itemVec.end(); ++iter)
    {
        sum += iter -> net_price(10);   //调用Item_base:: net_price
    }

    cout << sum << endl;    //output 600
}

//习题15.29
int main()
{
    vector<Item_base *> itemVec;
    for (size_t i = 0; i != 10; ++i)
    {
        Bulk_item *item = new Bulk_item("C++ Primer",6,5,0.5);
        itemVec.push_back(item);
    }

    double sum = 0;
    for (vector<Item_base *>::iterator iter = itemVec.begin();
            iter != itemVec.end(); ++iter)
    {
        sum += (*iter) -> net_price(10);
    }

    cout << sum << endl;    //output 300

    //显式释放动态分配的内存
    for (vector<Item_base *>::iterator iter = itemVec.begin();
            iter != itemVec.end(); ++iter)
    {
        delete *iter;
    }
}

C++ Primer 学习笔记_70_面向对象编程 --纯虚函数、容器与继承,布布扣,bubuko.com

时间: 2024-10-25 04:28:29

C++ Primer 学习笔记_70_面向对象编程 --纯虚函数、容器与继承的相关文章

C++ Primer 学习笔记_70_面向对象编程 -纯虚函数、器皿与继承

面向对象编程 --纯虚函数.容器与继承 I.纯虚函数 在函数形参后面写上 =0 以指定纯虚函数: class Disc_item : public Item_base { public: double net_price(size_t) const = 0; //指定纯虚函数 }; 将函数定义为纯虚函数能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类的版本绝不会调用.重要的是,用户将不能创建Disc_item类型的对象. Disc_item discount; //Error Bulk

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

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

C++ Primer 学习笔记33_面向对象编程(4)--虚函数与多态(一):多态、派生类重定义、虚函数的访问、 . 和-&gt;的区别、虚析构函数、object slicing与虚函数

C++ Primer学习笔记33_面向对象编程(4)--虚函数与多态(一):多态.派生类重定义.虚函数的访问. . 和->的区别.虚析构函数.object slicing与虚函数 一.多态 多态可以简单地概括为"一个接口,多种方法",前面讲过的重载就是一种简单的多态,一个函数名(调用接口)对应着几个不同的函数原型(方法). 更通俗的说,多态行是指同一个操作作用于不同的对象就会产生不同的响应.或者说,多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态行分

C++ Primer 学习笔记_35_面向对象编程(6)--虚函数与多态(三):虚函数表指针(vptr)及虚基类表指针(bptr)、C++对象模型

C++ Primer 学习笔记_35_面向对象编程(6)--虚函数与多态(三):虚函数表指针(vptr)及虚基类表指针(bptr).C++对象模型 一.虚函数表指针(vptr)及虚基类表指针(bptr) C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括: virtual function机制:用以支持一个有效率的"执行期绑定": virtual base class:用以实现多次在继承体系中的基类,有一个单一而被共享的实体. 1.虚函数表指针 C++中,有两种数据

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 学习笔记_69_面向对象编程 --继承情况下的类作用域

面向对象编程 --继承情况下的类作用域 引言: 在继承情况下,派生类的作用域嵌套在基类作用域中:如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义. 正是这种类作用域的层次嵌套使我们能够直接访问基类的成员,就好像这些成员是派生类成员一样: Bulk_item bulk; cout << bulk.book() << endl; 名字book的使用将这样确定[先派生->后基类]: 1)bulk是Bulk_item类对象,在Bulk_item类中查找,找不到名

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 学习笔记_66_面向对象编程 --定义基类和派生类[续]

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

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

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