首页 > 编程语言 >C++ 面向对象程序设计(中)

C++ 面向对象程序设计(中)

时间:2023-03-06 13:12:12浏览次数:33  
标签:调用 函数 继承 子类 C++ 面向对象 程序设计 父类 定义

(上)讲述了基于对象,(中)则是在基于对象的基础上,建立类与类之间的联系,即面向对象编程以及面向对象设计。

  主要讲述以下三点:

  • Inheritance (继承)

  • Composition(复合)

  • Delegation (委托)

C++ 面向对象程序设计

Composition(复合)

 

  Composition(复合)就是 has a, 上面的事例就是队列(queue)类中有一个双端队列(deque)类,队列中的成员函数通过双端队列的操作函数完成,这是类与类之间的第一个关系。(黑色菱形代表内部拥有)。

  deque 中可能拥有很多方法,但 queue 中只通过 deque 提供了非常少的方法,这是一个设计模式 Adapter,将一个拥有许多功能的类改造一下,得到另一个类。

内存视角下的composition(复合)

 

  可以看到有两个复合关系,最后queue的内存是40。

composition(复合)关系下的构造与析构

 

  由于 Container 类是拥有 Component 类,所以在构造方面,先调用 Component 类的默认构造函数,然后再调用 Container 的构造函数,由内而外的构造,里面做好了,再做外面。析构则相反,先对 Container 进行析构,然后再对 Component 进行析构,过程是由外而内,将外面的去掉,才能看到里面去掉里面,符合常识。

Delegation (委托) —— Composition by reference

 

  如果一个类(string)中拥有一个指针(StringRep*),该指针指向的是另一个类(StringRep),这种关系是 Delegation(委托),更好的说法就是Composition by reference(学术界不说 by pointer),两种类的生命周期不一样,与复合两种类会同时初始化不同,委托当需要用的时候再进行初始化。上图中的实例是一种非常有名的设计,叫 handle/body,指针指向的类负责具体实现,可以看到有一个 //pimpl,意思是 pointer to implement,而拥有那个指针的类只提供外界接口,就是基于委托这种方式。

  Handle(string)是提供给外界的接口,body(StringRep)就是实现部分,为什么这么有名,这是因为 String 类设计好了就不需修改了,只需要修改实现的那一个类,具有很好的弹性,另外,还有可以进行共享机制(减小内存),下图的 a,b,c共享一个 StringRep,这种方式叫做 reference counting,当需要修改其中一个时,需要把内容 copy 出来一份进行修改,另外两个依然指向原 StringRep。(白色菱形代表指针指向)。

Inheritance (继承)

 

  继承的语法就是在类名后加上:public(还可以是protected,private)你想要继承的类,如果想继承多个类,用逗号隔开就可以了。什么时候用继承,确定一个关键点,子类 is a 父类(例如,狗 is a动物)。上述的 List_nodes 是继承了 List_node_base 所有的数据,另外还有自己的数据。

Inheritance (继承)关系下的构造与析构

 

  继承的类(derived object)的一部分是基类(base part),对于要被继承的基类,它的析构函数必须是 virtual,不然会出现问题,这个问题将在后面说。继承的构造函数会首先调用基类的构造函数,然后调用自己的构造函数(由内而外)。析构则相反,先析构自己,然后再调用基类的析构函数。

Inheritance (继承)with virtual functions(虚函数)

 

  子类继承了父类的两样东西,一种是父类的数据,一种是父类函数的调用权。

  对于一个类而言,它的子类都可以访问所以的 public 方法,而子类要不要重新定义父类的函数呢?这时候就需要虚函数了,当 public 里面的函数不是虚函数时,则希望子类不重新定义该函数。当函数是虚函数时(在返回类型前加入关键字 virtual),则希望子类重新定义它,并且父类已经有了默认定义。当函数是纯虚函数时(在结束符;前面加上 =0),则希望子类一定要重新定义它,父类没有默认定义(但可以有默认定义)。

  该事例是定义了一个基类 shape,然后矩形 Rectangle 和椭圆 Ellips 对 shape 进行继承,基类的 objectID 是无需继承的,可以提前定好,在父类调用即可,而 error 函数,父类有默认的错误信息,如果子类有更精细的错误信息,父类允许子类可以重新定义的,打印出子类调用时的错误,而 draw 函数则必须重新定义,父类没有定义(draw shape没有意义),子类不同,所画出的形状自然不同。

Inheritance (继承)with virtual ——经典实例

 

  对于在 powerpoint 打开 ppt 文件而言,有以下几步,先点打开命令的菜单栏,然后出现文件界面,选择我们要打开的文件名,然后程序会检查文件名是否符合规范,符合规范则在磁盘上搜索文件,搜索到了打开文件即可。而遇到注意的是,所以打开文件的过程都是这样,只有最后打开文件可能会不同(可能会打开不同格式的文件),于是有团队就将除文件打开函数以外的函数进行打包,子类直接继承,只要子类重新定义父类打开文件的函数即可。如下图所示:

 

  团队开发了 CDocument 类,定义 Serialize 函数需要重新定义,在 OnFileOpen 函数中的省略号即为打包好的过程。用 CDocument 类的人只需重新定义 Serialize 函数即可,则在 main 函数中,先创建一个 CMyDoc 实例 myDoc,调用 myDoc.OnFileOpen 函数,子类没有定义这个函数,实则调用的是父类的函数,即CDocument::OnFileOpen(&myDoc), 进入父类函数中,运行打包好的过程,当运行到 Serialize 函数时,发现子类重新定义了它,则调用子类重新定义的 Serialize函数,最后再返回到 CDocument::OnFileOpen,继续下面的过程。再也不用写一般的步骤了,完美!

  这是一种非常有名的设计模式 Template method(不是说C++ template),提供了一种应用框架,它将重复一样的操作写好,不确定的步骤留给实际应用设计者重新实现。

  深层次的理解,谁调用函数,this 就是谁,当调用 Serialize 函数是,编译器是通过 this->Serialize()调用,于是就调用到了 this 重新定义的 Serialize函数。

 

   上图就是 CDocument 和 CMyDoc 的实例,用 cout 来模拟步骤,呼应上面两张图片。

Inheritance + Composition关系下的构造与析构

 

  当同时存在继承和复合,类是如何进行构造和析构呢?这一节要讨论的问题:

  1. 子类有父类的成分还有包含着另一个类;

  2. 子类继承父类,父类拥有另外一个类。

  情况2就很明显了,构造依然是自内而外,析构是由外而内。

  对于情况1的测试程序:

#include <iostream>

using namespace std;

namespace fy1{
    class Base;
    class Derived;
    class Component;

    class Base{
    public:
        Base() {cout << "Base Ctor" << endl;}
        virtual ~Base() {cout << "Base Dtor" << endl;}
    };

    class Component{
    public:
        Component() {cout << "Component Ctor" << endl;};
        ~Component() {cout << "Component Dtor" << endl;};
    };

    class Derived : Base{
    public:
        Derived() {cout << "Derived Ctor" << endl;}
        ~Derived() {cout << "Derived Dtor" << endl;}
    protected:
        Component c;
    };

    void fy1_test(){
        Derived d;
    }
}

int main() {
    cout << "Ctor and Dtor test:" << endl;
    fy1::fy1_test();
    return 0;
}

  运行结果为:

Ctor and Dtor test:
Base Ctor
Component Ctor
Derived Ctor
Derived Dtor
Component Dtor
Base Dtor 

  可以看到先初始化父类(Base),然后再初始化Component类,再初始化自己,析构与构造相反。

  下图也给出了结论。

 

 

  至此,面向对象的概念说完了,下面进入实例环节。

Delegation (委托) + Inheritance (继承) (一)

 

  上述代码解决的是下图所示的问题,对同一份文件使用四个不同窗口去查看,或者右下角所示的,同一个数据,三种不同的 viewer 查看。数据只有一份,表现多种形式,数据变化,表现形式也会发生变化,要表现这样的特性,这就对表现文件的 class 和存储数据的 class 之间关系要有要求,上图就是下图的一种解法,23种设计模式之一。

  Subject 类是存储数据的类,不过类中有 delegation,使用了一个 vector 类用来存放 Observer 类的指针,这样 Observer 类以及它的所有子类都可以导入这个 vector 中,Observer 类相当于表现形式类的父类,可以有多种表现形式,这些都是子类。update 则是子类需要重新定义的方法,不同表现形式可以有不同的更新方法。对于 Subject 类来说,当我们想创建新的窗口(新的 observer 类)去查看它的时候,需要对将新的 Observer 类进行注册,函数 attach 就是实现这样的功能,可以将新的 observer 子类的指针加到 vector 中,注销的函数没有写出来,另外,当数据发生变化时,使用 set_val 函数,需要使用一个函数去更新所有的 observer 子类,这就是 notify 函数干的事,遍历 vector 每一个observer 指针,调用指针指向的 update 方法,对表现形式进行更新。

 

  下图是一个更详细的Observer解法构建:

Delegation (委托) + Inheritance (继承) (二)

 

  第二个实例,构建一个文件系统。可以把 Primitive 类当作文件类,而 Composite 类当作目录类,与日常使用的文件系统一样,目录里面可以包含目录,也可以包含文件,所以目录里面存放的不止是目录本身还可以是文件,但是需要放入到同一个容器中,想法是使用指针,但文件和目录是不太一样的,所以解决方案是将文件和目录共同继承 Component 类,然后 Composite 类中的容器存放的是 Component 的指针,这样就可以既存放文件又可以存放目录了,这是一种经典的解决方案,也是23种设计模式之一,Composite。另外,Component 类中还有一个虚函数 add,这是给目录进行继承的,因为目录可以新建目录和文件,这里不能设置为纯虚函数,因为文件不能继承这个函数,文件是不能在进行添加的。

Prototype

 

对象模型

 

标签:调用,函数,继承,子类,C++,面向对象,程序设计,父类,定义
From: https://www.cnblogs.com/kyzh-lhl/p/17181986.html

相关文章

  • C++学生成绩管理系统[2023-03-06]
    C++学生成绩管理系统[2023-03-06]C++课程设计说明参与专业信息和数学专业所有学生时间安排完成需求分析、类设计以及代码的实现答辩注意:答辩未过的需要参加下届C++......
  • C/C++飞机订票系统[2023-03-06]
    C/C++飞机订票系统[2023-03-06]三、飞机订票系统1.某公司每天有10航班(航班号、价格),每个航班的飞机,共有80个坐位,分20排,每排4个位子。编号为A,B,C,D·如座位号:10D......
  • C/C++课程设计题目[2023-03-06]
    C/C++课程设计题目[2023-03-06]课题1:公司考勤管理系统(一)、课程设计题目:某公司的考勤管理系统(二)、目的与要求:1、目的:(1)要求学生达到熟练掌握C++语言的基本知识和技能;(2......
  • 序章 高质量C++/C编程指南
    一、文件结构避免头文件被重复引用,用#pragmaonce进行预处理用<>引用标注库头文件,用""引用自定义库头文件C语言头文件只进行函数声明,不进行函数定义;C++类的成员......
  • Python、C++、Swift或任何其他语言会取代Java吗?为什么?
    很难预测Python,C++,Swift或任何其他编程语言是否会取代Java作为最受欢迎的语言,但在不久的将来不太可能.以下是一些原因:受欢迎程度并建立的用法:Java已经存在了25年以上,并......
  • C++类和对象
                       ......
  • C++--引用和函数调用
             ......
  • C++编译小技巧
    1.单文件//math.cppintmultiply(inta,intb){returna*b;}//main.cpp#include<iostream>intmain(){ std::cout<<"Hello,world!"<<std::endl; s......
  • 虚幻c++的习惯
    枚举E打头 起名字用E加后面的驼峰  用UENUM宏标记是为了通过反射系统给蓝图使用识别,nint8是为了限制枚举的范围  可以为了使蓝图端更方便使用 加到具体的注......
  • 面向对象(可增加)
    引用传递和值传递引用传递:类的对象在方法中当做形参传递时,对其中类对象的操作会影响实参值传递:八大基本类型的数据当做形参传递时,不会对实参产生变化类和对象的关系类......