Chapter 15. Object-Oriented Programming

An Overview

The key ideas in object-oriented programming aredata abstraction,inheritance, and dynamic binding
Using data abstraction, we can define classes that separate interface from implementation (Chapter 7). 
Through inheritance, we can define classes that model the relationships among similar types. 
Through dynamic binding(run-time bingding), we can use objects of these types while ignoring the details of how they differ


class Quote
{
public:
    Quote() = default; //price = 0.0,bookNo = ""
    Quote(const std::string& book,const double sales_price)
    :bookNo(book),price(sales_price){}
    std::string isbn() const{return bookNo;}

    virtual double net_price(const std::size_t n) const {return n*price;}
    virtual ~Quote() = default;  //dynamic binding for the destructor
protected:
    double price = 0.0;
private:
    std::string bookNo;
};
void print_total(std::ostream& os,const Quote &item,std::size_t n)
{
    double ret = item.net_price(n);
    os<<"ISBN: "<<item.isbn()//calls Quote::isbn
    <<"# sold: "<<n<<" total due: "<<ret<<std::endl;
}

class Bluk_quote : public Quote
{
public:
    Bluk_quote() = default;
    Bluk_quote(const std::string& book,const double sales_price,const std::size_t& qty,const double& d)
    :Quote(book,sales_price),min_qty(qty),discount(d){}
    virtual double net_price(std::size_t n) const override
    {
        if(n>=min_qty)
            return n*(1-discount)*price;
        else
            return n*price;
    }

private:
    std::size_t min_qty = 0; //minimum purchase for the discount to apply
    double discount = 0.0;   //fractional discount to display
};

15.2. Defining Base and Derived Classes

15.2.1. Defining a Base Class

note
In C++, dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class.
note
Base classes ordinarily should define a virtual destructor. Virtual destructors are needed even if they do no work.
note
Member functions that are not declared as virtual are resolved at compile time, not run time.

In C++, a base class must distinguish the functions it expects its derived classes to override from those that it expects its derived classes to inherit without change

What is a virtual member?

A virtual member in a base class expects its derived class define its own version. In particular base classes ordinarily should define a virtual destructor, even if it does no work.

How does the protected access specifier differ from private?

  • private member: base class itself and friend can access
  • protected members: base class itself, friend and derived classes can access

15.2.2. Defining a Derived Class

Virtual Functions in the Derived Class
     Derived classes frequently, but not always, override the virtual functions that they inherit. If a derived class does not override a virtual from its base, then, like any other member, the derived class inherits the version defined in its base class


Derived-Class Objects and the Derived-to-Base Conversion

A derived object contains multiple parts: a subobject containing the (nonstatic) members defined in the derived class itself, plus subobjects corresponding to each base class from which the derived class inherits

Although the standard does not specify how derived objects are laid out in memory, we can think of a Bulk_quote object as consisting of two parts as represented in Figure 15.1

Because a derived object contains subparts corresponding to its base class(es), we can use an object of a derived type as if it were an object of its base type(s). In particular, we can bind a base-class reference or pointer to the base-class part of a derived object.
Quote item; // object of base type
Bulk_quote bulk; // object of derived type
Quote *p = &item; // p points to a Quote object
p = &bulk; // p points to the Quote part of bulk
Quote &r = bulk; // r bound to the Quote part of bulk


The fact that the derived-to-base conversion is implicit means that we can use an
object of derived type or a reference to a derived type when a reference to the base
type is required. Similarly, we can use a pointer to a derived type where a pointer to
the base type is required

note
The fact that a derived object contains subobjects for its base classes is key
to how inheritance works


Derived-Class Constructors

note
Each class controls how its members are initialized
Bulk_quote(const std::string& book, double p,
std::size_t qty, double disc) :
Quote(book, p), min_qty(qty), discount(disc) { }
// as before
};
As with a data member, unless we say otherwise, the base part of a derived object is default initialized. To use a different base-class constructor, we provide a constructor initializer using the name of the base class, followed (as usual) by a parenthesized list of arguments. Those arguments are used to select which base-class constructor to use to initialize the base-class part of the derived object

The base class is initialized first, and then the members of the derived class are initialized in the order in which they are declared in the classs.

The fact that the derived-to-base conversion is implicit means that we can use an
object of derived type or a reference to a derived type when a reference to the base
type is required. Similarly, we can use a pointer to a derived type where a pointer to
the base type is required

The fact that a derived object contains subobjects for its base classes is key
to how inheritance works

Using Members of the Base Class from the Derived Class

A derived class may access the public and protected members of its base class
Key Concept: Respecting the Base-Class Interface 
It is essential to understand that each class defines its own interface. Interactions with an object of a class-type should use the interface of that class, even if that object is the base-class part of a derived object


Inheritance and static Members
If a base class defines a static member (§7.6, p. 300), there is only one such
member defined for the entire hierarchy. Regardless of the number of classes derived
from a base class, there exists a single instance of each static member
class Base
{
public:
    static void statmem();
};
class Derived : public Base
{
    void f(const Derived&);
};
void Derived::f(const Derived &derived_obj)
{
    Base::statmem(); // ok: Base defines statmem
    Derived::statmem(); // ok: Derived inherits statmem
    // ok: derived objects can be used to access static from base
    derived_obj.statmem(); // accessed through a Derived object
    statmem(); // accessed through this object
}
Declarations of Derived Classes
A derived class is declared like any other class (§7.3.3, p. 278). The declaration contains the class name but does not include its derivation list:
class Bulk_quote : public Quote; // error: derivation list can't appear here
class Bulk_quote; // ok: right way to declare a derived class

Classes Used as a Base Class
A class must be defined, not just declared, before we can use it as a base class:
class Quote; // declared but not defined
// error: Quote must be defined
class Bulk_quote : public Quote { ... };

class Base { /* ... */ } ;
class D1: public Base { /* ... */ };
class D2: public D1 { /* ... */ };

Preventing Inheritance
Under the new standard, we can prevent a class from being used as a base by following the class name with final
class NoDerived final { /* */ }; // NoDerived can't be a base class
class Base { /* */ };
// Last is final; we cannot inherit from Last
class Last final : Base { /* */ }; // Last can't be a base class
class Bad : NoDerived { /* */ }; // error: NoDerived is final
class Bad2 : Last { /* */ }; // error: Last is final
發佈了146 篇原創文章 · 獲贊 240 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章