C++学习9:OOP——类和类之间的关系(复合、委托、继承)

1、三种关系

1.1 Composition 复合 (has a)

1.1.1 定义

表示“has a ”,即“拥有”关系。一个class里面,有另一种class的东西。即我这种类包含有另外一种类的对象。

1.1.2 好处

所有的功能不用自己写,直接调用他拥有类的功能就好。如下例queue中使用deque的对象,即为复合关系,就可直接调用deque的成员函数。下例可见deque是很强大的
例:

template <class T, class Sequence = deque<T> >
class queue
{
protected:
	Sequence c;		//底层容器
public//以下完全利用c的操作函数完成
	bool empty() const { return c.empty(); }
	size_type size() const { return c.size(); }
	reference front() { return c.front(); }
	reference back() { return c.back(); }
	//deque是两端可进出,queue是末端进前端出
	void push(const value_type& x) { c.push_back(x); }
	void pop() { c.pop_front(); }
};

同样的,简单来看:

template <class T>
class queue
{
protected:
	deque<T> c;		//底层容器
public//以下完全利用c的操作函数完成
	bool empty() const { return c.empty(); }
	size_type size() const { return c.size(); }
	reference front() { return c.front(); }
	reference back() { return c.back(); }
	//deque是两端可进出,queue是末端进前端出
	void push(const value_type& x) { c.push_back(x); }
	void pop() { c.pop_front(); }
};

用图来表示:
queue类里拥有deque的东西,则在queue类用实心菱形表示,指向它含有的deque
在这里插入图片描述

1.1.3 引入一种设计模式:Adapter

一种的功能可以完全满足另一种需要的功能,如deque能完全满足queue。则只需改一些deque的接口即可,如上代码

1.1.4 queue内存结构

在这里插入图片描述

构造由内而外:要构造外面的东西,需要先把内部的东西构造完成。即调用内部的默认(default)构造函数,然后才执行自己。
析构由外而内:要进行析构,先把外面的东西剥离掉,即先执行自己,然后才调用内部的析构函数。

1.2 Delegation 委托 (Composition by reference)

(虽然是用指针在传,但术语都叫做by reference)

1.2.1 定义

不同于上面复合的“拥有”,是实实在在的包含,委托是一种用指针来指向另一个类的方式。在任何我想用你来做事情的时候,就可以用指针调用你,把任务委托给你。所以委托可以看做是一种比较虚的拥有,因此在图中用虚的菱形。

//String.hpp,这里以后科研不用再改变
class StringRep;
class String
{
public:
	String();
	String(const char* s);
	String(const String& s);			//拷贝构造
	String &operator=(const String& s);	//拷贝赋值
	~String();							//析构
private:
	StringRep* rep	//pimpl 
};
//String.cpp,要改变操作,改变这里的即可。下图的圈n就是这里的操作
include"String.hpp"
namespace
{
class StringRep
{
	friend class String;
	StringRep(const char* s)
	~StringRep();
	int count;	//后面的n
	char* rep;	//后面的rep
};
}

String::String(){......}

在这里插入图片描述
以上结构就是pimpl:pointer to implementation,也称Handle/Body。即左边String类都是借口,而下边StringRep都是实现,无论body是如何实现的,都不会影响handle。
在这里插入图片描述

1.2.2 与Composition的不同之处

Composition是有了外面就要有里面。(同步)
Delegation是有了外面不一定有里面,当外面调用里面时,才会创建里面的内容。(不同步)

1.2.3 不可以牵一发而动全身

当a要对hello进行改变时,b和c不能改变。因此应当在a改变之前进行复制,给a一个副本让a进行改动,b和c不变。这就是copy on write(在写的时候进行复制)。

1.3 Inheritance 继承(is a)

1.3.1 定义

写法:要定义一个类时,用冒号,后面加上继承方式,最后加上要继承的类
语法:从子类往父类画,一个空心的三角形。(子类的东西比父类多)
在这里插入图片描述

1.3.2 继承方式

public:使用public继承,传达出一种逻辑:是一种,即“is a”。这种继承方式是最重要的,后面两种其他语言不一定有,都默认public继承。
还要其余两种继承方式:private和protected,这里请先查阅相关书籍。

1.3.3 内存

子类的对象中要有父类的成分(part),所以这也是一种外部包含内部的状态,如Composition 。
因此构造由内而外:要构造外面的东西,需要先把内部的东西构造完成。即调用Based的默认(default)构造函数,然后才执行Derived的。析构由外而内:要进行析构,先把外面的东西剥离掉,即先执行Derived的,然后才调用Based的析构函数。
:父类Based的dtor必须是virtual的!

1.3.4 好处——virtual

子类除了拥有自己的数据和方法以外,还可以拥有父类的。但最有价值的是和虚函数搭配的情况。
继承的过程:数据可以被继承下来,函数也可以被继承下来(继承的是调用权),根据子类是否重新定义决定是否virtual。

class Shape
{
public:
	int objectID() const;						//non virtual
	virtual void error(const std::string& msg)	//virtual
	virtual void draw() const = 0;				//pure virtual
};

class Rectangle : public Shape{...};
class Ellipse : public Shape{...};

非虚函数non virtual:不希望子类重新定义(也叫重写override,只有继承的时候可以用这个词);
虚函数virtual:已经有了默认定义,希望子类重新定义;
纯虚函数pure virtual:希望子类一定重新定义;

1.3.5 经典用例——Template Method:框架设计模式

方法可以跑向子类:
可以跑去的原因:this指向的是myDoc,通过子类this调用,见下图左上部分。
在这里插入图片描述
1.main函数创建子类对象myDoc
2.通过子类对象调用父类函数OnFileOpen
3.父类函数Serialize看到子类有写这个函数。执行的过程,通过子类的virtual定义的方法执行子类的方法,再回到父类继续的方法。
大体代码如下:
在这里插入图片描述

2、三种关系的组合

2.1 继承+复合

构造和析构的顺序:把握住一点
构造:由内而外
析构:由外而内
在这里插入图片描述
子类是比父类要大一圈的,在外面;复合关系是类“拥有”的,在里面。

2.2 委托+继承

2.2.1 Observer设计模式

这是使用最多的组合,典型应对如例:
在这里插入图片描述
如,把一个幻灯片复制成几份放在一个窗口,一个变化剩下的都跟着变化;再如用不同的方式(如数字、表格、折线图)表现一份数据。

class Subject
{
    int m_value;
    vector<Observer*> m_views;
public:
    void attach(Observer* obs)
    {
        m_views.push_back(obs);
    }
    void set_val(int value)
    {
        m_value = value;
        notify();
    }
    void notify()
    {
        for(int i = 0; i < m_views.size(); ++i)
            m_views[i]->update(this, m_value);
    }
};
class Observer
{
public:
    virtual void update(Subject* sub, int value) = 0;
};

模式类型见上图的右上角。
Observer是指向右边的指针(委托),用于观察,可以被继承成不同的观察方式。而m_view是观察方式对象。
set_val函数作用就是放数据。
attach函数作用是注册,把观察方式对象放进容器。
notify函数作用是遍历、通知,准备更新(子类update)数据。

2.2.2 Composite设计模式

在这里插入图片描述
类似目录,可以有同级目录,可以有下一级目录:

class Primitive:public Component
{
public:
    Primitive(int val):Component(val) {}
};
class Component
{
    int value;
public:
    Component(int val) { value = val; }
    virtual void add(Component*) {}
};
class Composite:public Component
{
    vector<Component*>c;
public:
    Composite(int val):Component(val) {}

    void add(Component* elem)
    {
        c.push_back(elem);
    }
...
};

Primitive:个体
Composite:组合物。应该可以容纳很多个Primitive,甚至可以容纳自己这种东西。所以应该继承Component
Component:add操作可以加左边的东西,也可以加右边的东西。故传入一个指针指向Component类型。add不能是纯虚函数,故应该是空函数。

2.2.3 Prototype设计模式

一个继承体系,想去创建未来才会出现的子类。即现在要去创建未来的class对象!
在这里插入图片描述
上图中,红色线以上的部分是父类(多年前写的部分),而下面的子类是未来(买代码的 买回去后才自己定义的)才会被派生出去的。
解决方案:让子类都创建一个自己,当成“原型”,让父类有办法去看到子类创造出的原型放在什么位置上。这样父类就可以复制他,就等于父类在创建了。
有下划线的部分是指静态的,LAST即为静态对象,其类型名即LandSatImage。
为了让父类看到,定义了父类。构造函数LandSatImage()是private的,调用父类的addPrototype函数,把得到的子类指针放到父类prototypes容器中去;LandSatImage(int)是protected的
clone函数就是new自己,做一个副本出来,如果没有这个原型,就无法通过对象调用函数(若要用静态函数不用原型,就需要class name,而父类并不知道)。

#include<iostream.h>
enum imageType
{
    LSAT, SPOT
};
class Image
{
public:
    virtual void draw() = 0;
    static Image *findAndClone(imageType);
protected:
    virtual imageType returnType() = 0;
    virtual Image* clone() = 0;
    //As each subclass of Image is declared, it registers its prototype
    static void addPrototype(Image *image)
    {
        _prototypes[_nextSlot++] = image;
    }
private:
    //addPrototype() saves each registered prototype here
    static Image* _prototypes[10];
    static int _nextSlot;
};
Image *Image::prototypes[];//定义
int Image::_nextSlot;//定义
//Client calls this public static member function when it needs an instance 
Image *Image::findAndClone(imageType type)
{
    for(int i = 0; i < _nextSlot; i++)
    {
        if(_prototypes[i]->returnType())
        return _prototypes[i]->clone();
    }
}
class LandSatImage:public Image
{
public:
    imageType returnType()
    {
        return LSAT;
    }
    void draw()
    {
        cout << "LandSatImage::draw" << _id << endl;
    }
    //When clone() is called, call the one-argument with a dummy arg
    Image *clone()
    {
        return new LandSatImage(1); 
    }
protected:
    //This is only called from clone()
    LandSatImage(int dummy)
    {
        _id = _count++;
    }
private:
    //Mechanism for initializing an Image subclass - this causes
    the default ctor to be called, which registers the subclass's prototype
    static LandSatImage _landSatImage;
    //This is only called when the private static data member is inited
    LandSatImage()
    {
        addPrototype(this);
    }
    //Nominal "state" per instance mechanism
    int _id;
    static int _count;
};
//Register the subclass's prototype
LandSatImage LandSatImage::_landSatImage;
//Initialize the "state" per instance mechanism
int LandSatImage::_count = 1;
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是使用C++实现的oop租车系统(多重继承)的代码示例: ```cpp #include <iostream> #include <string> #include <vector> using namespace std; // 定义车辆类 class Vehicle { public: Vehicle(const string& make, const string& model, int year, int rent_per_day) : make_(make), model_(model), year_(year), rent_per_day_(rent_per_day) {} // 打印车辆信息 void print_info() const { cout << make_ << " " << model_ << " (" << year_ << ") - $" << rent_per_day_ << "/day" << endl; } private: string make_; // 品牌 string model_; // 型号 int year_; // 年份 int rent_per_day_; // 租金/天 }; // 定义客户类 class Customer { public: Customer(const string& name, int age) : name_(name), age_(age) {} // 打印客户信息 void print_info() const { cout << name_ << " (" << age_ << " years old)" << endl; if (rented_vehicle_) { cout << "Rented vehicle: "; rented_vehicle_->print_info(); } } // 租用车辆 void rent_vehicle(Vehicle* vehicle) { rented_vehicle_ = vehicle; cout << name_ << " rented "; rented_vehicle_->print_info(); } private: string name_; // 姓名 int age_; // 年龄 Vehicle* rented_vehicle_ = nullptr; // 租用的车辆 }; // 定义租车公司类 class CarRentalCompany { public: // 初始化车辆和客户 CarRentalCompany() { // 添加车辆 vehicles_.push_back(new Vehicle("Make1", "Model1", 2020, 50)); vehicles_.push_back(new Vehicle("Make2", "Model2", 2020, 60)); vehicles_.push_back(new Vehicle("Make3", "Model3", 2020, 70)); // 添加客户 customers_.push_back(new Customer("Customer1", 25)); customers_.push_back(new Customer("Customer2", 30)); } // 打印所有车辆信息 void print_all_vehicles() const { cout << "All vehicles:" << endl; for (const auto& vehicle : vehicles_) { vehicle->print_info(); } } // 打印所有客户信息 void print_all_customers() const { cout << "All customers:" << endl; for (const auto& customer : customers_) { customer->print_info(); } } private: vector<Vehicle*> vehicles_; // 所有车辆的数组 vector<Customer*> customers_; // 所有客户的数组 }; int main() { // 创建租车公司 CarRentalCompany company; // 打印所有车辆和客户信息 company.print_all_vehicles(); company.print_all_customers(); // 客户1租用第2辆车 company.print_all_customers(); company.print_all_vehicles(); company.print_all_customers(); company.print_all_vehicles(); return 0; } ``` 该代码定义了三个类:Vehicle、Customer 和 CarRentalCompany。其中,Vehicle 和 Customer 分别表示车辆和客户,而 CarRentalCompany 则表示租车公司。使用多重继承,CarRentalCompany 类同时继承了 Vehicle 和 Customer 类的属性和方法。 使用该代码,我们可以创建一个租车公司,并初始化其中的车辆和客户。然后,我们可以打印所有车辆和客户的信息,以及让某个客户租用某辆车。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值