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 };

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

基类通过在其成员函数的声明之前加上关键字 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 };

注意: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部分

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

派生类构造函数:

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

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) {}

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

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

派生类使用基类的成员:

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 }

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

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

继承与静态成员:

 1 #include <iostream>
 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 }

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

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

派生类的声明:

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

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

被用作基类的类:

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

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

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

防止继承的发生:

在类名后面跟一个关键字 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的

类型转换与继承:

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

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

静态类型与动态类型:

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

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

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

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

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

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;//错误,不能将基类转换成派生类

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

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

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

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

 1 #include <iostream>
 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 }

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

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

原文地址:https://www.cnblogs.com/geloutingyu/p/8450189.html

时间: 2024-10-19 02:54:22

OOP1(定义基类和派生类)的相关文章

C++ Primer 学习笔记_66_面向对象编程 --定义基类和派生类[续]

算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进.最后,再考虑时间和空间复杂度的问题. 了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maxim

C++ Primer 学习笔记_65_面向对象编程 --概述、定义基类和派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++ Primer 学习笔记_65_面向对象编程 -概述、定义基类跟派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++ Primer 学习笔记_66_面向对象编程 -定义基类跟派生类[续]

面向对象编程 --定义基类和派生类[续] 四.virtual与其他成员函数 C++中的函数调用默认不使用动态绑定.要触发动态绑定,必须满足两个条件: 1)只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定. 2)必须通过基类类型的引用或指针进行函数调用. 1.从派生类到基类的转换 因为每个派生类对象都包含基类部分,所以可以将基类类型的引用绑定到派生类对象的基类部分可以用指向基类的指针指向派生类对象: void print_total(const Item_

定义基类和派生类

定义基类 对于基类,我们需要记住的是作为继承关系中根节点的类通常都会定义一个虚析构函数. 基类通常都会定义一个虚析构函数,即使该函数不执行任何实际操作也是如此. 成员函数和继承 派生类可以继承其基类的成员,也可以对基类中的虚函数进行重新定义.换句话说,派生类需要对这些操作提供自己的新定义以覆盖(override)从基类继承而来的旧定义. 在C++语言中,基类必须将它的两种成员函数区分开来:一种是基类希望其派生类进行覆盖的函数:另一种是基类希望派生类直接继承而不要改变的函数.对于前者,基类通过将其

面向对象程序设计——概述,定义基类和派生类,虚函数

一.OOP:概述 面向对象程序设计的核心思想是数据抽象.继承和动态绑定.通过使用数据抽象,我们可以将类的接口和实现分离:使用继承,可以定义相似的类型并对其相似关系建模:使用动态绑定,可以在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象. 1)继承 通过继承联系在一起的类构成一种层次关系.通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承而来,这些继承得到的类称为派生类.基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自的成员. 在C++语言中,基类将类型相

C++:基类与派生类对象之间的赋值兼容关系

4.5 基类与派生类对象之间的赋值兼容关系 在一定条件下,不同类型的数据之间可以进行类型转换,例如可以将整型数据赋给双精度型变量. 在赋值之前,先把整型数据转换为双精度型数据,然后再把它双精度型变量.这种不同类型之间的自动转换,称为赋值兼容.在基类和派生类对象之间也存在有赋值兼容关系,基类和派生类对象之间的赋值兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来代替.因为,通过公有继承,除了构造函数和析构函数外,派生类保留了基类其他的所有的成员.那么,派生类就具有基类的全部功能,凡

C++:基类和派生类

4.1 派生类的声明 继承实例如下: class Person{ //声明基类Person public: void print() { cout<<"name:"<<name<<endl; cout<<"age:"<<age<<endl; cout<<"sex:"<<sex<<endl; } protected: string name;

继承,基类,派生类

在 C++中,继承机制通过类的派生实现,被继承的类称为基类或父类:在继承类的基础上创建的新类称为派生类或子类.派生类的定义格式为:class 派生类名:继承方式基类名 1,继承方式基类名 2,…,继承方式基类名 n{派生类增加的成员声明;};其中,定义中的基类名必须是已有类的名称,派生类名则是新建的类名.一个派生类可以只有一个基类,称为单继承:也可以同时有多个基类,称为多继承.派生类也可作为基类继续派生子类.