OOP:概述¶
面向对象程序设计【Object-Oriented Programming】基于三个基本概念:数据抽象、继承和动态绑定
继承(inheritance) 将类联系起来构成层次关系,在层次关系的根部有 基类(base class),直接或间接地继承而来的类称为 派生类(derived class)。
基类将“类型相关的函数”与派生类”不做改变直接继承的函数“区分对待。对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明成 虚函数(virtual function)。
派生类必须通过使用 派生类列表(class derivation list) 明确指出它从哪个(哪些)基类继承而来:class 派生类:访问说明符 基类
。
动态绑定(dynamic binding) 使得我们用同一段代码分别处理不同类的对象。
由于函数的运行版本由实参决定,即在运行时选择函数的版本,所以动态绑定有时又被称为 运行时绑定(run-time binding)。
定义基类和派生类¶
定义基类¶
class Quote {
public:
Quote() = default;
Quote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) { }
std::string isbn() const { return bookNo; }
// 返回给定数量的书籍的销售总额
// 派生类负责改写并使用不同的折扣计算算法
virtual double net_price(std::size_t n) const { return n * price; }
virtual ~Quote() = default; // 对析构函数进行动态绑定
private:
std::string bookNo; // 书籍的 ISBN 编号
protected:
double price = 0.0; // 代表普通状态下不打折的价格
};
新增部分是net_price
函数、virtual
关键词以及protected
访问说明符。
基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。
成员函数与继承¶
派生类可以继承基类的成员,但如果成员涉及到与“类型”相关的操作时,派生类需要对该成员重新定义。即,派生类提供自己的新定义以 覆盖(override) 从基类继承而来的旧定义。
基类的两种成员函数:
- 基类希望其派生类进行“覆盖”的函数——> 虚函数(virtual)
- 基类希望其派生类“直接进程而不要改变”的函数
访问控制与继承¶
派生类能够访问公有成员,但不能访问私有成员。
受保护的(protected) 访问运算符规定了这样的成员。基类的派生类有权访问,但是其他用户禁止访问。
定义派生类¶
派生类必须通过使用 派生类列表(class derivation list) 明确指出它从哪个(哪些)基类继承而来。
class 派生类名:继承方式 基类名1, 继承方式 基类名2……继承方式 基类名n{
派生类成员声明
}
派生类的基函数¶
如果派生类没有覆盖其基类中的某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。
派生类构造函数¶
每个类控制它自己的成员初始化过程
Bulk_quote(const std::string& book, double p, std::size_t qty, double disc) :
Quote(book, p), min_qty(qty), discount(disc) { }
````
1. **基类部分初始化** :将前两个参数 `book` 和 `p` 传递给 `Quote` 的构造函数,由 `Quote` 的构造函数负责初始化 `Bulk_quote` 的基类部分,即 `bookNo` 成员和 `price` 成员。当`Quote`构造函数体结束后,构建的对象的基类部分也就完成初始化了。
2. **派生类成员初始化** :接下来初始化由派生类直接定义的 `min_qty` 成员和 `discount` 成员。
3. **构造函数体执行** :最后运行 `Bulk_quote` 构造函数的空函数体。
- 首先初始化基类部分,然后按照声明顺序依次初始化派生类的成员。
### 派生类使用基类的成员
- 派生类可以访问基类的公有成员和受保护成员
### 继承与静态成员
- 静态成员的唯一性
如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。不论从基类中派生出多少个派生类,对于每个静态成员来说都只存在唯一的实例。
- 访问控制规则——受基类中定义的访问控制规则约束
- 果基类中的静态成员是 `private` 的,则派生类无权访问它。
* 如果基类中的静态成员是可访问的(如 `public`),则我们既能通过基类使用它,也能通过派生类使用它。
* 若基类的静态成员可访问,派生类可以直接通过基类名、派生类名、对象或指针 / 引用等方式访问该静态成员。
# 虚函数
**对虚函数的调用可能在运行时才被解析**
当且仅当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个。
普通类型的表达式调用虚函数时,在编译时就会将调用的版本确定下来。。如 `base.net_price();`
**派生类中的虚函数**
一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型、返回类型必须与被它覆盖的基类函数完全一致。
### virtual & override
`virtual` 关键字
1. **声明虚函数**
- `virtual` 关键字用于在基类中声明一个虚函数,表示该函数可以在派生类中被重写。
- 语法:
```cpp
virtual 返回类型 函数名(参数列表);
```
- 示例:
```cpp
class Base {
public:
virtual void show() {
std::cout << "Base class show" << std::endl;
}
};
```
2. **虚函数表(vtable)**
- 每个包含虚函数的类都会生成一个虚函数表,其中存放着该类虚函数的地址。
- 当通过基类指针或引用来调用虚函数时,编译器会根据对象的实际类型查找对应的虚函数表,从而调用正确的函数。
`override` 关键字
1. **重写虚函数**
- `override` 关键字用于在派生类中重写基类的虚函数,确保派生类的函数确实重写了基类中的虚函数。
- 语法:
```cpp
返回类型 函数名(参数列表) override;
```
- 示例:
```cpp
class Derived : public Base {
public:
void show() override { // 明确表示重写基类的虚函数
std::cout << "Derived class show" << std::endl;
}
};
```
2. **增强代码安全性**
- 使用 `override` 可以防止因拼写错误或函数签名不匹配而导致的意外行为。
- 如果派生类中的函数没有正确重写基类的虚函数,编译器会报错。
**主要区别**
- **`virtual`**:用于在基类中声明虚函数,表示该函数可以在派生类中被重写。
- **`override`**:用于在派生类中重写基类的虚函数,确保派生类的函数确实重写了基类中的虚函数。
# 抽象基类
# 访问控制与继承
## 受保护的成员
`protected`关键字来声明那些它希望与派生类分享但是不想被其他公共访问使用的成员。
- 和私有成员类似,受保护的成员对于类的用户来说是不可访问的
- 和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的
- 派生类的成员或友元只能通过派生类对象来访问积累的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问权限。
## 公有、私有和受保护继承
某个类对于继承而来的成员的访问权限受两个因素影响:
- 基类中该成员的访问说明符
- 派生类的派生列表中的访问说明符
派生访问说明符对于派生类的成员(及友元)能否访问其直接基类的成员没有影响,该访问权限只与基类中的访问说明符有关——都能访问受保护的成员,都不能访问私有成员。
派生访问说明符是控制派生类用户(包括派生类的派生类在内)对于基类成员的访问权限:
```c++
Pub_Derv d1; // 继承自 Base 的成员是 public 的
Priv_Derv d2; // 继承自 Base 的成员是 private 的
d1.pub_Derv_mem(); // 正确:pub_mem 在派生类中是 public 的
d2.pub_Derv_mem(); // 错误:pub_mem 在派生类中是 private 的