C++面向对象程序设计

类的基本思想是数据抽象封装。数据抽象是一种依赖于接口实现分离的编程技术。
面向对象程序设计的核心思想是数据抽象继承动态绑定
定义基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Quote.h
#ifndef BULK_QUOTE_H
#define BULK_QUOTE_H
#include <string>
using std::string;

class Quote {
public:
Quote() = default; // 默认构造函数,目的是既需要其他形式的构造函数,也需要默认的构造函数
Quote(const std::string &book, double sales_price):
bookNo(book), price(sales_price) { }
string isbn() const;
double getPrice() const;
virtual double net_price(size_t n) const;
virtual ~Quote() = default; // 对析构函数进行动态绑定
private:
string bookNo;
double price = 0.0; // 普通状态不打折的价格
};
#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Quote.cpp
#include "Quote.h"
string Quote::isbn() const
{
return bookNo;
}

double Quote::getPrice() const
{
return price;
}

double Quote::net_price(size_t n) const
{
return n * getPrice();
}

在C++语言中基类将它的两种成员函数区分开:一种是基类希望其派生类进行覆盖的函数,另一种是基类希望派生类直接继承而不要改变的函数。对于前者基类通常将其定义为虚函数(virtual)。当我们使用基类的引用或指针调用一个虚函数时,将发生动态绑定,根据引用或绑定的对象类型不同,该调用可能执行基类的版本,也可能执行某个派生类的版本。
定义派生类
派生类必须通过使用类派生列表(class derivation list)明确指出他是从哪个(哪些)基类继承而来的。

1
2
3
4
5
6
7
8
9
10
11
// Bulk_quote.h
#include "Quote.h"
class Bulk_quote : public Quote {
public:
Bulk_quote() = default;
Bulk_quote(const string &n, double p, size_t m, double d): Quote(n, p), min_qty(m), discount(d) { }
double net_price(size_t) const override; // 覆盖基类的函数以实现折扣政策
private:
size_t min_qty = 0;
double discount = 0.0;
};

C++11允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数:在该函数的形参列表之后加一个override关键字。

1
2
3
4
5
6
7
8
// Bulk_quote.cpp
#include "Bulk_quote.h"
double Bulk_quote::net_price(size_t n) const
{
if (n > min_qty)
return (n - min_qty) * getPrice() * discount + min_qty * getPrice();
return n * getPrice();
}

成员函数通过一个名为this的隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。
net_price()函数参数列表之后的const关键字的作用是修改隐式this指针的类型。默认情况下,this的类型是指向类类型非常量版本的常量指针。例如在Bulk_quote成员函数中,this的类型是Bulk_quote *const。默认情况下我们不能把this绑定到一个常量对象上。这也使得我们不能在一个常量对象上调用普通的成员函数。
紧跟在参数列表后面的const表示this是一个指向常量的指针。这样使用const的成员函数称作常量成员函数。常量成员函数不能改变调用它的对象的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// main.cpp
#include "Bulk_quote.h"
#include "Quote.h"
#include <iostream>
using std::ostream;
using std::cout;
using std::endl;

double print_total(ostream &os, const Quote &item, size_t n)
{

double ret = item.net_price(n);
os << "ISBN: " << item.isbn()
<< " # sold: " << n << " total due: " << ret << endl;
return ret;
}
int main()
{

Quote basic("241024", 12.5);
Bulk_quote bulk("241025", 12.5, 10, 0.8);

print_total(cout, basic, 20);
print_total(cout, bulk, 20);
return 0;
}

编译

1
2
3
4
5
6
7
8
9
10
11
12
13
CC = g++
SRC = main.cpp Quote.cpp Bulk_quote.cpp
OBJ = main.o Quote.o Bulk_quote.o
EXEC = main

$(EXEC) : $(OBJ)
$(CC) -std=c++11 $(OBJ) -o $@
rm -f $(OBJ)
$(OBJ) : Quote.h Bulk_quote.h
$(CC) -std=c++11 -c $(SRC)

clean:
rm -f main

防止继承的发生
C++11新标准提供了一种防止继承发生的方法,即在类名后跟一个关键字final:

1
class NoDerived final { ... }; // NoDerived不能作为基类

类型转换与继承
通常情况下,把引用或指针绑定到一个对象上,则引用或指针的类型应与对象的类型一致。存在继承关系的类是一个重要的例外:可以将基类的引用或指针绑定到派生类对象上。使用基类的引用或指针时,实际上我们并不清楚该引用或指针所绑定类型的真实类型。
派生类中的虚函数
当我们在派生类中覆盖了某个虚函数时,可以再一次使用virtual关键字指出该函数的性质。然而这么做并非必须,因为一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数。
纯虚函数
想要定义一个Disc_quote类来支持不同的折扣策略,Disc_quote负责保存购买量的值和折扣值。其他表示某种特定策略的类将分别继承自Disc_quote,Disc_quote类中的net_price函数显然没有实际含义,因此可以将net_price定义成纯虚(pure virtual)函数。和普通的虚函数不一样,一个纯虚函数无须定义。通过在函数体的位置书写=0就可以将一个虚函数说明为纯虚函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Disc_quote.h
#include "Quote.h"

class Disc_quote : public Quote {
public:
Disc_quote() = default;
Disc_quote(const string &book, double price, size_t qty, double disc):
Quote(book, price), quantity(qty), discount(disc) { }
double net_price(size_t) const = 0;
protected:
size_t quantity = 0; // 折扣适用的购买量
double discount = 0.0; // 折扣
};

我们也可以为纯虚函数提供定义,不过函数体必须定义在类的外部。
含有纯虚函数的类是抽象基类(abstract base class)。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。我们不能直接创建一个抽象基类的对象。
重新实现Bulk_quote
让Bulk_quote继承Disc_quote而非直接继承Quote:

1
2
3
4
5
6
7
8
9
10
// Bulk_quote.h
#include "Disc_quote.h"

class Bulk_quote : public Disc_quote {
public:
Bulk_quote() = default;
Bulk_quote(const string &book, double price, size_t qty, double disc):
Disc_quote(book, price, qty, disc) { }
double net_price(size_t) const override;
};

Bulk_quote.cpp不需要改变,也不需要实现Disc_quote.cpp文件。
reference
《C++ Primer 第五版》