热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

OOP1(定义基类和派生类)

面向对象程序设计基于三个基本概念:数据抽象,继承和动态绑定数据抽象是一种依赖于接口和实现分离的编程技术。继承和动态绑定对程序的编号有两方面的影响:一是我们可以更容易地定义与其它类相似但不完全相同的类

面向对象程序设计基于三个基本概念:数据抽象,继承和动态绑定

数据抽象是一种依赖于接口和实现分离的编程技术。继承和动态绑定对程序的编号有两方面的影响:一是我们可以更容易地定义与其它类相似但不完全相同的类;二是在使用这些彼此相似的类编写程序时,我们可以在一定程度上忽略掉它们的区别。

 

在 c++ 语言中,当我们使用基类的引用或指针调用一个虚函数时将发生动态绑定

 

定义基类:

 1 class Quote {
2 public:
3 Quote() = default;
4 Quote(const std::string &book, double sales_price) :
5 bookNo(book), price(sales_price) {}
6
7 std::string isbn() const {
8 return bookNo;
9 }
10
11 virtual double net_price(std::size_t n) const {//定义成虚函数,运行2时进行动态绑定
12 return n * price;
13 }
14
15 virtual ~Quote() = default;//基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作
16
17 private:
18 std::string bookNo;//书籍的isbn编号
19
20 protected://可被派生类访问
21 double price = 0.0;//代表普通状态下不打折的价格
22 };
View Code

注意:基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作

基类通过在其成员函数的声明之前加上关键字 virtual 使得该函数执行动态绑定。任何构造函数之外的非静态函数都可以是虚函数。如果基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数

派生类可以继承定义在基类中的成员,但是派生类不一定有权访问从基类继承而来的成员。派生类只能访问公有成员和受保护的成员,不能访问私有成员

 

定义派生类: 

 1 class Bulk_quote : public Quote {
2 public:
3 Bulk_quote() = default;
4 Bulk_quote(const std::string&, double, std::size_t, double);
5
6 double net_price(std::size_t) const override;//override显式注明该成员函数覆盖它继承的虚函数
7
8 // ~Bulk_quote();
9
10 private:
11 std::size_t min_qty = 0;//适用折扣政策的最低购买量
12 double discount = 0.0;//以小数表示的折扣额
13
14 };
View Code

注意:override 显式注明该成员函数覆盖它继承的虚函数(只能对继承自虚函数的成员使用 override 关键字)

 

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

因为在派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当作基类对象使用,而且我们也能将基类的指针或引用绑定到派生类对象中的基类部分上:

1     Quote item;//基类对象
2 Bulk_quote bulk;//派生类对象
3
4 Quote *p = &item;//p指向Quote对象
5 p = &bulk;//p指向bulk中的Quote部分
6
7 Quote &r = bulk;//r绑定到bulk中的Quote部分
View Code

注意:这种转换通常称为派生类到基类的类型转换。和其它类型转换一样,编译器会隐式地执行派生类到基类的转换。这种隐式特性意味着我们可以把派生类对象或者派生类对象的引用用在需要基类引用的地方;同样的,我们可以把派生类对象的指针用在需要基类指针的地方

 

派生类构造函数:

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

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

注意:首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员

派生类中基类数据成员如果没有显式构造,则会执行默认初始化

 

派生类使用基类的成员:

1 double Bulk_quote::net_price(size_t cnt) const {
2 if(cnt >= min_qty) return cnt * (1 - discount) * price;
3 return cnt * price;
4 }
View Code

注意:派生类可以访问基类的公有成员和受保护成员

派生类的作用域嵌套在基类的作用域之内。因此,对于派生类的一个成员来说,它使用派生类成员的方式与使用基类成员的方式是一样的

 

继承与静态成员:

 1 #include 
2 using namespace std;
3
4 class Base{
5 public:
6 // Base();
7 // ~Base();
8 static void statmem();
9
10 };
11
12 void Base::statmem() {
13 //
14 }
15
16 class Derived : public Base{
17 public:
18 // Derived();
19 // ~Derived();
20 void f(const Derived&);
21 };
22
23 void Derived::f(const Derived &derived_obj) {
24 Base::statmem();//正确,Base定义了stamem
25 Derived::statmem();//正确,Derived继承了stamem
26 derived_obj.statmem();//通过Derived对象访问
27 statmem();//通过this对象访问
28 }
29
30 int main(void){
31
32 }
View Code

注意:如果基类定义了一个静态成员,则在整个继承体些中只存在该成员的唯一定义。无论从基类中派生出多少个派生类,对于每个静态成员来说都只存在唯一的实例

静态成员遵循通用的访问控制规制,如果基类中的成员是 private 的,则派生类无权访问它

 

派生类的声明:

派生类的声明中包含类名但不包含派生列表:

1 class Bulk_quote : public Quote;//错误,派生列表不能出现在这里
2 class Bulk_quote;//正确
View Code

 

被用作基类的类:

如果我们想将某个类用作基类,则该类必须已经定义而非仅仅声明:

1 class Quote; //声明但未定义
2 class Bulk_quote : public Quote {//错误,Quote必须被定义
3 //
4 }
View Code

 

一个类是基类,同时也可以是一个派生类。最终的派生类将包含它的直接基类的子对象以及每个简介基类的子对象

 

防止继承的发生:

在类名后面跟一个关键字 final 能防止继承发生:

1 class NoDerived final {};//NoDerived不能作为基类
2 class Base {};
3
4 class Last final : Base {};//Last是final的,我们不能继承Last
5
6 // class Bad : NoDerived {};//错误,NoDerived是final的
7 // class Bad2 : Last {};//错误,Last是final的
View Code

 

类型转换与继承:

可以将基类的指针或引用绑定到派生类对象上,当使用基类的引用或指针时,实际上我们并不清楚该引用或指针所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象

和内置指针一样,只能指针类也支持派生类想基类的类型转换,这意味着我们可以将一个派生类对象的指针存储在一个基类的智能指针内

 

静态类型与动态类型:

当使用存在继承关系的类型时,必须将一个变量或其它表达式的静态类型与该表达式表示对象的动态类型区分开来。表达式的静态类型在编译时总是已知的,它是变量声明时类型或表达式生成的类型;动态类型则是变量或表达式的内存中对象的类型。动态类型直到运行时才可知。

如果达式既不是引用也不是指针,则它的动态类型永远与静态类型一致。而基类的指针或引用的静态类型可能与其动态类型不一致。如:

1     Bulk_quote bulk;//派生类对象
2 Quote q* = &bulk;//q的静态类型为Quote,动态类型为Bulk_quote
3 Quote &p = bulk;//p的静态类型为Quote,动态类型为Bulk_quote
View Code

 

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

之所以存在派生类向基类的类型转换是因为每个派生类对象都包含一个基类部分,而基类的引用或指针可以绑定到该基类部分上。但基类不一定包含派生类(派生类可能定义了新的成员),所以如果我们将一个基类的对象向派生类的类型转换,则我们有可能会使用基类中没有的成员,所以不存在从基类向派生类的自动类型转换

1     Quote base;
2 // Bulk_quote *bulkp = &base;//错误,不能将基类转换成派生类
3 // Bulk_quote &bulkref = base;//错误,不能将基类转换成派生类
4
5 // 即使一个基类指针或引用绑定在一个派生类对象上,也不能执行从基类向派生类的转换
6 Bulk_quote bulk;
7 Quote *itemp = &bulk;//正确,从派生类转换到基类
8 // Bulk_quote *bulkp = itemp;//错误,不能将基类转换成派生类
View Code

注意:即使一个基类指针或引用绑定在一个派生类对象上,也不能执行从基类向派生类的转换

我们可以通过 dynamic_cast 或 static_cast 显式地将一个基类对象转换为派生类类型。详见百度百科:https://baike.baidu.com/item/dynamic_cast/4473047?fr=aladdin

 

在对象之间不存在类型转换:

派生类向基类的自动类型转换只对指针或引用类型有效,在派生类类型和基类类型之间不存在这样的转换。但我们可以通过拷贝构造函数、移动构造函数、拷贝赋值运算符或移动赋值运算符将一个派生类类型转换成基类类型,因为这些拷贝控制成员中通常包含一个本类类型的 const 引用或右值引用:

 1 #include 
2 using namespace std;
3
4 class Quote {
5 public:
6 Quote() = default;
7 Quote(const std::string &book, double sales_price) :
8 bookNo(book), price(sales_price) {}
9
10 std::string isbn() const {
11 return bookNo;
12 }
13
14 virtual double net_price(std::size_t n) const {//定义成虚函数,运行2时进行动态绑定
15 return n * price;
16 }
17
18 virtual ~Quote() = default;//基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作
19
20 private:
21 std::string bookNo;//书籍的isbn编号
22
23 protected://可被派生类访问
24 double price = 0.0;//代表普通状态下不打折的价格
25 };
26
27 class Bulk_quote : public Quote {
28 public:
29 Bulk_quote() = default;
30 Bulk_quote(const std::string&, double, std::size_t, double);
31
32 double net_price(std::size_t) const override;//override显式注明该成员函数覆盖它继承的虚函数
33
34 // ~Bulk_quote();
35
36 private:
37 std::size_t min_qty = 0;//适用折扣政策的最低购买量
38 double discount = 0.0;//以小数表示的折扣额
39
40 };
41
42 Bulk_quote::Bulk_quote(const std::string &book, double p, std::size_t qty, double disc) :
43 Quote(book, p), min_qty(qty), discount(disc) {}
44
45 double Bulk_quote::net_price(size_t cnt) const {
46 if(cnt >= min_qty) return cnt * (1 - discount) * price;
47 return cnt * price;
48 }
49
50 int main(void){
51
52 Bulk_quote bulk;//派生类对象
53 Quote item(bulk);//使用合成拷贝构造函数Quote::Quote(const Quote&)
54 item = bulk;//使用合成拷贝赋值运算符Quote& Quote::operator=(const Quote&)
55
56 //显然我们不能将基类类型通过拷贝控制成员转换成派生类对象
57 // Quote cnt;
58 // Bulk_quote gg(cnt);
59
60 return 0;
61 }
View Code

注意:在上述过程中会忽略 Bulk_quote 中新定义的成员,即 bulk 中只有从基类中继承来的部分被赋值给了 item

显然,我们不能将基类类型通过拷贝控制成员转换成派生类对象

 


推荐阅读
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • C++中的三角函数计算及其应用
    本文介绍了C++中的三角函数的计算方法和应用,包括计算余弦、正弦、正切值以及反三角函数求对应的弧度制角度的示例代码。代码中使用了C++的数学库和命名空间,通过赋值和输出语句实现了三角函数的计算和结果显示。通过学习本文,读者可以了解到C++中三角函数的基本用法和应用场景。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了C++中的引用运算符及其应用。引用运算符是一种将变量定义为另一个变量的引用变量的方式,在改变其中一个变量时,两者均会同步变化。引用变量来源于数学,在计算机语言中用于储存计算结果或表示值抽象概念。变量可以通过变量名访问,在指令式语言中引用变量通常是可变的,但在纯函数式语言中可能是不可变的。本文还介绍了引用变量的示例及验证,以及引用变量在函数形参中的应用。当定义的函数使用引用型形参时,函数调用时形参的改变会同时带来实参的改变。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
author-avatar
月亮哥哥是团宠他男人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有