C++_Primer 面向对象程序设计

OOP(object-oriented programming)概述

OOP的核心思想是数据抽象、继承和动态绑定。

通过继承联系在一起的类构成一种层次关系。在层次关系底部的类称为基类,继承这个类的类称为派生类。

对于某些函数,基类希望它的派生类各自定义适合自己的版本,此时基类将这些函数声明为虚函数。

class Quote{
public:
	std::string isbn const;
	virtual double net_price(std::size_t) const;
}

派生类必须通过派生类列表来指出它是从哪个基类继承来的。其形式是:冒号+基类列表(每个基类哑油访问说明符)。

class Bulk_quote : public Quote{
public:
	double net_price(std::size_t) const override;
}

派生类必须在其内部对所有重新定义的虚函数进行声明。派生类可以在这样的函数之前加上virtual关键字。C11允许派生类显式地注明它是使用哪个成员函数改写基类的虚函数,具体措施是在该函数的形参列表只有增加一个override关键字。

通过使用动态绑定,我们能用同一段代码分别处理Quote和Bulk_quote的对象。例如,当要购买的书籍和购买的数量都已知,下面的函数负责打印总的费用:

double print_total(ostream &os, const Quote &item, size_t n){
	double ret = item.net_price(n);
	os << item.isbn() << ret << endl;
	return ret;
}

print_total(cout, basic, 20); // 传入Quote类型
print_total(cout, bulk, 20); // 传入Bulk_quote类型

根据传入的数据类型不同,调用不同的函数,这一特性称为动态绑定或运行时绑定。

定义基类和派生类

定义基类

Quote类的定义:

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;
protected:
	double price = 0.0;
}

之后会详细介绍虚析构函数的知识,现在只需记住作为继承关系中根节点的类通常会定义一个虚析构函数。

成员函数与继承

派生类可以继承其基类的成员,然而遇到如net_price这样与类型相关的操作时,派生类必须对其重新定义。也就是说,派生类需要对这些操作提供自己的新定义以覆盖(override)从基类继承而来的旧定义。

基类包含两种成员函数:

  1. 基类希望派生类进行覆盖的函数;
  2. 基类希望派生类直接继承而不要改变的函数。

对于前者,基类通常将其定义为虚函数。

当我们使用指针或引用调用虚函数时,该调用将被动态绑定。

基类通过在成员函数前加上关键字virtual使得该函数执行动态绑定。任何构造函数之外的非静态函数都可以是虚函数。

关键字virtual只能出现在类内部的声明语句之前而不能用于类外部的函数定义。

如果基类把一个函数声明为虚函数,那么该函数在派生类中隐式地也是虚函数。

如果基类没有把一个函数声明为虚函数,那么调用时不存在动态绑定问题。

访问控制与继承

派生类无法访问基类中的私有成员(private),但是基类又希望有那么一些成员,不能被外界访问,但是可以给派生类访问,于是定义为protected。

定义派生类

派生类必须通过类派生列表明确指出它是从哪个基类继承来的。

派生类必须将其继承来的成员函数中需要覆盖的那些(虚函数\构造函数)重新声明。

class Bulk_quote : public Quote{
public:
	Bulk_quote() = default;
	BUlk_quote(const std::string, double, std::size_t, double);
	double net_price(std::size_t) const override;
private:
	std::size_t min_qty = 0; 	// 适用折扣的最低购买量
	double discount = 0.0; 	// 以小数表示折扣额
}

该派生类还继承了isbn、bookNo、price等数据成员。

访问说明符的作用是控制从基类继承过来的成员是否对派生类的用户可见。

类派生列表只含一个类的话,称为单继承。

派生类中的虚函数

如果派生类中的虚函数没有覆盖,那么它自动回复继承其在基类中的版本。

覆盖的话,需要在前注明virtual或者在后注明override。

派生类对象及派生类向基类的类型转换

派生类自定义的成员和继承的成员在内存中不一定连续分布。

因为派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当成基类对象来使用。

Quote item;				// 基类对象
Bulk_quote bulk;		// 派生类对象
Quote *p = &item;		// p指向Quote对象
p = &bulk;					// p指向bulk的Quote对象
Quote &r = bulk;		// r绑定到bulk的Quote部分

这种转换通常称为派生类到基类的类型转换。

派生类构造函数

尽管派生类对象中含有从基类继承而来的成员,但是派生类并不能直接初始化这些成员。和其他创建了基类对象的代码一样,派生类也必须使用基类的构造函数来初始化它的基类部分。

每个类控制它自己的成员初始化过程

其构造函数如下所示:

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

派生类使用基类成员

派生类可以访问基类的共有成员和受保护成员。

继承与静态成员

如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。不论从基类中派生出多少个派生类,对每个静态成员只存在唯一实例。

class Base{
public:
	static void statmen();
}

被用作基类的类

基类必须被定义,不能仅仅声明,因为派生类会用到。

一个类是基类,他也可以是一个派生类:

class Base;
class D1: public Base;
class D2: public D1;

Base是D1的直接基类,是D2的间接基类。D1即使基类又是派生类。

D2既有D1的成员又有Base的成员。

防止继承的发生

在类名后跟一个final即可。

类型转换与继承

我们可以将基类的指针或者引用绑定到派生类对象上。

这其中有一层重要的含义:当使用基类的引用、指针时,实际上我们并不清楚该引用、指针所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象。

静态类型与动态类型

当我们使用存在继承关系的类型时,必须将一个变量或其他表达式的静态类型与该表达式表示对象的动态类型区分。

表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型;

动态类型则是变量或表达式表示的内存中的对象的类型,直到运行时才知道。

double ret = item.net_price(n);

item的静态类型时Quote&,它的动态类型则依赖于item绑定的实参。

如果我们传递一个Bulk_quote对象给item,那么其静态类型和动态类型是不一样的。

如果表达式既不是引用也不是指针,那么其动态类型与静态类型一致。

不存在基类向派生类的隐式类型转换

因为每个派生类都包含一个基类,而基类的引用或指针可以绑定到该基类部分上。

虚函数

所有的虚函数都必须有定义。

派生类中虚函数的返回类型必须与基类函数匹配,形参类型也是。

如果派生类定义了一个与基类中的虚函数名字相同,形参列表不同的函数,那么认为这是一个独立的函数,无法覆盖。

为了避免这样的错误的发生,可以在函数后面加上override。

只有虚函数能被覆盖。

当我们希望回避虚函数机制,强制执行特定的虚函数时,有以下写法:

double undiscounted = baseP -> Quote::net_price(n);

抽象基类

比如说Quote包含商品的基本信息,DiscQuote包含折扣信息,BulkQuote包含特定商品的信息,派生关系呈现出Quote -> DiscQuote -> BulkQuote。但是DiscQuote因为不需要显示商品的总价,所以不需要net_price(在Quote中是一个虚函数)。

但是吧,不要的话,DiscQuote会默认采用Quote中的函数。我们不需要定义net_price,但是又不想继承,于是可以写成:

double net_price(std::size_t) const =0;

如此一来,这个函数在DiscQuote中就变成了纯虚函数。含有纯虚函数的类称为抽象基类,其不能够实例化。

继承抽象基类的派生类务必要覆盖这一纯虚函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

右边是我女神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值