Defining Base and Derived Classes

本文介绍了 C++ 中的基类与派生类定义、成员函数与继承、访问控制与继承、派生类构造函数等内容,并探讨了虚函数在基类与派生类中的作用及其实现机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Defining Base and Derived Classes

Defining a Base Class

class Quote {
public:
Quote() = default; // = default see § 7.1.4 (p. 264)
Quote(const std::string &book, double sales_price):
bookNo(book), price(sales_price) { }
std::string isbn() const { return bookNo; }
// returns the total sales price for the specified number of items
// derived classes will override and apply different discount algorithms
virtual double net_price(std::size_t n) const
{ return n * price; }
virtual ~Quote() = default; // dynamic binding for the destructor
private:
std::string bookNo; // ISBN number of this item
protected:
double price = 0.0; // normal, undiscounted price
};

The new parts in this class are the use of virtual on the net_price function and the destructor, and the protected access specifier.
Note: Base classes ordinarily should define a virtual destructor. Virtual destructors are needed even if they do no work.

Member Functions and Inheritance

Because the operation net_price is type dependent, the derived class needs to override the definition it inherits from the base class, by providing its own definition.
The base class defines as virtual those functions it expects its derived classes to override. When we call a virtual function through a pointer or reference, the call will be dynamically bound.
Note:
1. Any nonstatic member function, other than a constructor, may be virtual.
2. The virtual keyword appears only on the declaration inside the class and may not be used on a function definition that appears outside the class body.
3. A function that is declared as virtual in the base class is implicitly virtual in the derived classes as well.

Access Control and Inheritance

A derived class may access the public members of its base class but may not access the private members. A base class specify members after a protected access specifier, so that lets its derived classes use while still prohibiting access to those same members by other users.

Defining a Derived Class

A derived class must specify from which class(es) it inherits. It does so in its class derivation list, which is a colon followed by a comma-separated list of names of previously defined classes. Each base class name may be preceded by an optional access specifier, which is one of public, protected or private.

class Bulk_quote : public Quote { // Bulk_quote inherits from Quote
Bulk_quote() = default;
Bulk_quote(const std::string&, double, std::size_t,
double);
// overrides the base version in order to implement the bulk purchase discount policy
double net_price(std::size_t) const override;
private:
std::size_t min_qty = 0; // minimum purchase for the discount to apply
double discount = 0.0; // fractional discount to apply
};

Virtual Functions in the Derived Class

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.
A derived class may include the virtual keyword on the functions it overrides, but it is not required to do so.
The new standards lets a derived class explicitly note that it intends a member function to override a virtual that it inherits. It do so by specifying override after the parameter list, or after the const or reference qualifier(s) if the member is a const or reference function.

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

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

This conversion is often referred to as the derived-to-base conversion.

Derived-Class Constructor

A derived class must use a base-class constructor to initialize its base-class part.
A derived-class constructor uses its constructor initializer list to pass arguments to a base-class constructor.
The Bulk_quote constructor with four parameters:

Bulk_quote(const std::string& book, double p,
std::size_t qty, double disc) :
Quote(book, p), min_qty(qty), discount(disc) { }
// as before
};

passes its first two parameters(representing the ISBN and price ) to the Quote constructor. That Quote constructor initializes the Bulk_quote’ s base-class part. Next the direct members, min_qty and discount, are initialized.
Note: The base class is initialized first, and then the members of the derived class are initialized in the the order in which they are declared in the class.

Using Members of the Base Class from the Derived Class

// if the specified number of items are purchased, use the discounted price
double Bulk_quote::net_price(size_t cnt) const
{
if (cnt >= min_qty)
return cnt * (1 - discount) * price;
else
return cnt * price;
}

The scope of a derived class is nested inside the scope of its base class. As a result, there is no distinction between how a member of the derived class uses members defined in its own class and how it uses members defined in its base.

Inheritance and static Members

If a base class defines a static member, 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&);
};

Note: static members obey normal access control. If the member is private in the base class, then derived classes have no access to it.

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

Note: The declaration contains the class name but does not include its derivation list:

class Bulk_quote : public Quote; // error: derivation list can't appearhere
class Bulk_quote; // ok: right way to declare a derived class

The derivation list, and all other details of the definition, must appear together in the class body.

Classes Used as a Base Class

A class must be defined, not just be 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 { ... };

The reason for this is, to use these members from base class, the derived class must know what they are. One implication of this rule is that it is impossible to derive a class from itself.

Preventing Inheritance

If we define a class that we don’t want others to inherit from, 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

Conversions and Inheritance

We can bind a pointer or reference to a base-class type to an object of a type derived from that base class.

Static Type and Dynamic Type

The static type of an expression is always known at compile time. The dynamic type may not be known until run time.

There is no Implicit Conversion from Base to Derived

Because a base object might or might not be part of a derived object, there is no automatic conversion from the base class to its derived class(s):

Quote base;
Bulk_quote* bulkP = &base; // error: can't convert base to derived
Bulk_quote& bulkRef = base; // error: can't convert base to derived

We cannot convert from base to derived even when a base pointer or reference is bound to a derived object:

Bulk_quote bulk;
Quote *itemP = &bulk; // ok: dynamic type is Bulk_quote
Bulk_quote *bulkP = itemP; // error: can't convert base to derived

The compiler has no way to know (at compile time) that a specific conversion will be safe at run time. The compiler looks only at the static types of the pointer or reference to determine whether a conversion is legal.

Create an inheritance hierarchy that a bank might use to represent customers’ bank accounts. All customers at this bank can deposit (i.e., credit) money into their accounts and withdraw (i.e., debit) money from their accounts. More specific types of accounts also exist. Savings accounts, for instance, earn interest on the money they hold. Checking accounts, on the other hand, charge a fee per transaction (i.e., credit or debit). Create an inheritance hierarchy containing base class Account and derived classes SavingsAccount and CheckingAccount that inherit from class Account. Base class Account should include one data member of type double to represent the account balance. The class should provide a constructor that receives an initial balance and uses it to initialize the data member. The constructor should validate the initial balance to ensure that it is greater than or equal to 0.0. If not, the balance should be set to 0.0 and the constructor should display an error message, indicating that the initial balance was invalid. The class should provide three member functions. Member function credit should add an amount to the current balance. Member function debit should withdraw money from the Account and ensure that the debit amount does not exceed the Account’s balance. If it does, the balance should be left unchanged and the function should print the message "Debit amount exceeded account balance." Member function getBalance should return the current balance. Derived class SavingsAccount should inherit the functionality of an Account, but also include a data member of type double indicating the interest rate (percentage) assigned to the Account. SavingsAccount’s constructor should receive the initial balance, as well as an initial value for the SavingsAccount’s interest rate. SavingsAccount should provide a public member function calculateInterest that returns a double indicating the amount of interest earned by an account. Member function calculateInterest should determine this amount by multiplying the interest rate by the account balance. [Note: SavingsAccount should inherit member functions credit and debit as is without redefining them.] Derived class CheckingAccount should inherit from base class Account and include an additional data member of type double that represents the fee charged per transaction. CheckingAccount’s constructor should receive the initial balance, as well as a parameter indicating a fee amount. Class CheckingAccount should redefine member functions credit and debit so that they subtract the fee from the account balance whenever either transaction is performed successfully. CheckingAccount’s versions of these functions should invoke the base-class Account version to perform the updates to an account balance. CheckingAccount’s debit function should charge a fee only if money is actually withdrawn (i.e., the debit amount does not exceed the account balance). [Hint: Define Account’s debit function so that it returns a bool indicating whether money was withdrawn. Then use the return value to determine whether a fee should be charged.] After defining the classes in this hierarchy, write a program that creates objects of each class and tests their member functions. Add interest to the SavingsAccount object by first invoking its calculateInterest function, then passing the returned interest amount to the object’s credit function.
最新发布
05-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值