C++面向对象(四)Inheritance, Composition, Delegation

article/2025/8/23 7:24:08

C++面向对象(四)Composition, Delegation, Inheritance

    • 一、Composition(复合)
      • - 关系表示为:has-a
      • - 其构造和析构的关系
    • 二、Delegation(委托)
      • - 其关系表示为:composition by reference
    • 三、Inheritance(继承)
      • - 其表示关系为:is-a
      • - 其构造和析构的关系
      • - virtual functions(虚函数)
    • 四、多态
      • (一)Inheritance + Composition
      • (二)Delegation + Inheritance
        • - 一个设计模式:Observer
        • - 一个设计模式:Composite
        • - 一个设计模式:Prototype

一、Composition(复合)

- 关系表示为:has-a

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

由图可知,queue里面直接包含了deque,所以为复合关系。且其为同时存在的,一有container,便马上也有了component。

其中为一种设计模式:Adapter

在这里插入图片描述
从内存的角度看,也体现其包含复合的关系:
在这里插入图片描述

- 其构造和析构的关系

在这里插入图片描述
(1)构造由内而外 (从里面开始一层一层搭建)

container的构造函数首先调用Component的 default 的构造函数,然后才执行自己。

Container::Container(...):Component(){...};

(2)析构由外而内 (从外面开始一层一层剥离)

container的析构函数首先执行自己,然后才调用Component的析构函数。

Container::~Container(...){... ~Component(){...}};

注意:我们在写Container的构造或析构函数时,无需将Component的也加进去,因为红色部分是编译器自动帮我们加进去的。

在这里插入图片描述

二、Delegation(委托)

- 其关系表示为:composition by reference

	可以类似看成一种,由指针相连,指针包含的复合,而指针一般在学术界是用引用来描述的。

在这里插入图片描述其跟上面Composition有类似的地方,但不同的一点是,其左边包含拥有的右边并不是同时产生同时存在的,即左边的先创建,右边的只有当指针有使用、调用到时才产生右边的函数对象(不同步),因而成为委托的关系。左边只是对外的接口(保持不变的),真正的实现在右边操作(可变动的),也称为编译“防火墙”。

三、Inheritance(继承)

- 其表示关系为:is-a

继承有三种:public,private,protected
struct _List_node_base{_List_node_base* _M_next;-List_node_base* _M_prev;
};template<typename _Tp>
struct _List_node: public _List_node_base{_Tp _M_date;
};

关系如图:
在这里插入图片描述

- 其构造和析构的关系

在这里插入图片描述
(1)构造由内而外 (从里面开始一层一层搭建)

Derived的构造函数首先调用Base的 default 的构造函数,然后才执行自己。

Derived::Derived(...):Base(){...};

(2)析构由外而内 (从外面开始一层一层剥离)

Derived的析构函数首先执行自己,然后才调用Base的析构函数。

Derived::~Derived(...){... ~Base(){...}};

注意:我们在写Base的析构函数时,必须将其设置成虚函数,否则无法出现析构由外而内的操作行为。

- virtual functions(虚函数)

在继承的关系里面,所以的东西都可以被继承下来,数据可以被继承下来(即占用内存的一部分),函数也可以被继承下来,但函数的继承不应该从内存的角度去理解,而是从函数的调用权上去理解。子类继承父类,则子类可以拥有相应的父类函数的调用权。而这又引出了子类是否需要去改写或重新定义从父类继承的函数即虚函数的问题。

所以父类的成员函数可以分为三种:
(1)non-virtual 函数:不希望derived class 重新定义(override)它。
(2)virtual 函数:希望derived class 重新定义(override)它,且对它已有默认定义。
(3)pure virtual 函数:希望derived class 一定要重新定义(override)它,且对它没有默认定义。

class Shape{
public:virtual void draw() const = 0;  // pure virtualvirtual void error(const std::string& msg); // impure virtualint objectID() const;		// non-virtual...
}
class Rectangle: public Shape{...
};
class Ellipse: public Shape{...
};

int objectID() const;
这个函数是产生个类似于编号号码的东西,不需要让下面的子类去重新定义它,在父类上的定义已经足够了,所以将其设置成非虚函数。

virtual void error(const std::string& msg);
这个函数表达错误信息,比如打开文件出错时,弹出个错误提示信息,但希望子类能给出更具体更精细的错误提示信息,所以将其设置成虚函数,希望在父类默认定义的基础上子类对其进行override,以表达更进一步的意思。

virtual void draw() const = 0;
这个函数作为父类根本不知道怎么去定义它,所以一定要子类去override它,所以将其设置成纯虚函数。

一个设计模式:Template Method
在这里插入图片描述
父类(Application framework)的框架可以是很多年前就写好的,但是里面的读取文件的函数Serialize()可以是多年前没办法完成的,因为之前不知道要怎样具体去实现,所以应将其设置成虚函数或者纯虚函数,让具体应用的子类(Application)去编写它。

所以设置框架时,将能固定下来的功能都写好,然后留下来的不确定的部分,就让它成为虚函数,然后让具体子类去override它。

执行过程:
1.在main里面创建个子类对象 myDoc;
2.通过子类对象调用父类函数OnFileOpen(),而此时相当于 CDocunment::OnFileOpen(&myDoc);
即传进去的是子类对象的指针 &myDoc;
3.因而在父类的函数中调用Serialize()时,this->Serialize(),这里的this指针便是子类对象&myDoc,所以又跳回到子类定义的virtual Serialize()函数中,然后运行。

#include <iostream>
using namespace std;class CDocument{
public:void OnFileOpen(){// 这是个算法,每个cout代表一个实际动作cout << "dialog..." << endl;cout << "check file status..." << endl;cout << "update all views..." << endl;Serialize();cout << "close file..." << endl;cout << "update all views..." << endl;}virtual void Serialize(){}; // 注意,一定要将此成员函数设置为虚函数,否则子类无法override它
};class CMyDoc: public CDocument{
public:virtual void Serialize(){// 只有应用程序本身才知道如何读取自己的文件(格式)cout << "CMyDoc::Serialize()" << endl;}
};int main(){CMyDoc myDoc;myDoc.OnFileOpen();
}

四、多态

(一)Inheritance + Composition

在这里插入图片描述
(1)构造由内而外 (从里面开始一层一层搭建)

Derived的构造函数首先调用Base的 default 构造函数,
然后调用Component的default 构造函数,
然后才执行自己。

Derived::Derived(...):Base(),Component() {...};

(2)析构由外而内 (从外面开始一层一层剥离)

Derived的析构函数首先执行自己,
然后才调用Component的析构函数
然后才调用Base的析构函数。

Derived::~Derived(...){... ~Component(){...}, ~Base(){...}};

因为子类继承的是父类,所以在执行顺序上,先将继承的东西实现了,再去实现自己本体里面所包含的东西(即先调用父类Base的构造函数,再调用自己本体里面包含的Component的构造函数)。

(二)Delegation + Inheritance

- 一个设计模式: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 // 纯虚函数
}class Observer1: public Observer{int m_div;
public:Observer1(Subject* model, int div){model -> attach(this);m_div = div;}virtual void update(int v){...}
};class Observer2: public Observer{int m_mod;
public:Observer2(Subject* model, int mod){model -> attach(this);m_mod = mod;}virtual void update(int v){...}
}int main(){Subject subj;Observer1 o1(&subj, 4);Observer1 o2(&subj, 3);Observer2 o3(&subj, 3);subj.set_val(14);
}

构想是让Subject可以拥有很多很多的Observer,所以准备了个容器去存放它,且存放的是Observer的指针对象,即delegation。

vector<Observer*> m_views;

而Observer又有很多的观察对象,即可以被继承,所以设置为父类,则其成员函数要定义为虚函数,以便继承的子类去override相应的内容。

virtual void update(Subject* sub, int value) = 0

在这里插入图片描述

执行过程:
1.在main里面创建框架委托主体类对象 subj;
2.创建o1,o2,o3父类对象;
3.在定义的子类Observer1中,利用指针&subjk,调用框架委托主体对象的函数subj -> attach();
4.进入委托主体对象的执行部分,调用其成员函数attach(),将o1加进去容器m_views中,实现注册功能(o2,o3也执行类似步骤,加进去容器当中);
5.框架委托主体类对象 subj调用成员函数set_val(),设置m_value值为14,然后继续调用成员函数notify();
6.在调用notify()时,因为调用指针是m_views[0] 即o1,所以转到Observer父类的update()虚函数,然后由于继承和由子类指针所指向,进而跳到子类对象o1的成员函数 virtual void update(int v),执行相应操作。(o2,o3也执行类似步骤,执行各自对虚函数override的内容)。
在这里插入图片描述

- 一个设计模式:Composite

在这里插入图片描述
设计思路:

Primitive是基础个体,而Composite是组合物,即Composite要设计一个容器,既要容纳Primitive的类对象,又要可容纳自身的Composite类对象。所以创建出一个父类Component,通过让Primitive和Composite去继承它,然后Composite里的容器便可通过放置Component对象实现:既可容纳Primitive的类对象,又可容纳自身的Composite类对象。

另外由于容器里面放置的东西大小需一致,所以为了将不同东西放进容器因而选择放置指针进去,即Component*。

而由于要加东西,所以Composite类里面定义了成员函数add(e:Component*),同样需要使用父类指针作为参数,表示即可添加Primitive的类对象,又可添加自身的Composite类对象。
注意:因为Primitive作为一个基础类,是没有实现添加功能的函数,所以在继承父类Component时是无法override其成员函数add()的,而Composite作为组合类,是需要override成员函数add()以添加不同的对象。因此,父类Component中的成员函数add()应设置为默认定义为空的虚函数(即无动作),而不能设置为纯虚函数(必须子类override)。

在这里插入图片描述

- 一个设计模式:Prototype

设计一个父类框架需要包含未创建出来的子类class对象,即未来子类需要自己创建出一个对象并被框架看到识别(子类自己去创建自己),从而copy出一份进行操作处理。
在这里插入图片描述

LSAT: LandSatImage // 冒号右边表示类型,下划线表示创建出来的是静态对象LSAT
-LandSatImage(int) // 短破折号表示是private,#表示是protected,无符号表示public
在这里插入图片描述在这里插入图片描述
父类应该在多年前创建出一个空间,然后子类通过调用父类中的成员函数,将多年后创建出来的子类对象加入到父类多年前准备好的空间中。所以创建出来的子类对象的构造函数中有继承父类的成员函数addPrototype(),而该函数在父类中对应着添加进prototypes[10]中的操作。

// 父类
#include <iostream.h>
enum imagaeType{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 prototypestatic void addPrototype(Image* image){ _prototypes[_nextSlot++] = image;}
private:// addPrototype() saves each registered prototype herestatic Image* _prototypes[10];static int _nextSlot;
};
Image* Image::_prototypes[];
int Image::_nextSlot;
// Client calls this public static member function when it needs an instance of an Image subclass
Image* Image::findAndClone(imageType type){for(int i = 0; i < _nextSlot; i++)if(_prototypes[i] -> returnType() == type)return _prototypes[i] -> clone();
}

一个class里面静态的data,一定要在class本体的外头给定义,因为其是在外头才给内存的。

Image *Image::findAndClone(imageType type){
for(int i = 0; i < _nextSlot; i++)
if(_prototype[i] -> returnType() == type)
return _prototype[i] -> clone();
}
表示在容器_prototype[i]里面找到符合传入参数的类型的那个子类指针对象,然后clone()一份出来。

成员函数clone()因为不知道未来子类对象的具体类型,所以无法设置成静态函数,因为静态函数是需要知道具体类型的。所以在父类中,将其设置成纯虚函数,且一定要在子类中根据其具体类型去override它。

成员函数findAndClone()只是单纯地将未来的子类对象添加进容器中,所以可以设置为静态函数。

// 子类
class LandSatImage: public Image{
public:imageType returnType(){return LSAT;}void draw(){cout << "LandSatImage::draw " << _id << endl;}// When clone() is called, call the one-argument ctor with a dummy argImage* 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 prototypestatic LandSatImage _landSatImage;// This is only called when the private static data member is initedLandSatImage(){addPrototype(this);  // this指代的便是前面的_landSatImage的指针,里面记录了_id信息。}// Nominal "state" per instance mechanismint _id; static int _count;
};
// Register the subclass's prototype
LandSatImage LandSatImage::_landSatImage;
// Initialize the "state" per instance mechanis
int LandSatImage::_count = 1;

先创建个静态的变量_landSatImage,同时在创建_landSatImage调用构造函数LandSatImage(),则构造函数里面的成员函数addPrototype(this)被调用,this指针即是*_landSatImage,这时便跳到父类的静态成员函数addPrototype()中从而将新创建的相应的子类添加到_prototype[]容器中。

将子类指针对象放到父类的容器当中后,当发生需要调用findAndClone()函数时,则需要调用其中的纯虚函数clone(),此时在子类中override的clone()需要 new LandSatImage,所以意味着也要调用构造函数LandSatImage(),但如果像之前那样调用构造函数,则又会继续创建新的子类指针对象加入到_prototype[]容器当中。
所以子类在override纯虚函数clone()时,需另外写一个构造函数即LandSatImage(int dummy),通过加入一个传参数来区别两个不同的构造函数。而第二个构造函数放在private或protected区域都可,但不可放在public区域,因为不希望被外界调用,此处选择放置protected区域。
在这里插入图片描述

// Simulated stream of creation requests
const int NUM_IMAGES = 8;
imageType input[NUM_IMAGES] = {LSAT, LSAT, LSAT, SPOT, LSAT, SPOT, SPOT, LSAT}int main(){Image* images[NUM_IMAGES];// Given an image type, fnd the right prototype, and return a clonefor(int i = 0; i < NUM_IMAGES; i++)images[i] = Image::findAndClone(input[i]);// Demonstrate that correct image objects have been clonedfor(int i = 0; i < NUM_IMAGES; i++)images[i] -> draw();// Free the dynamic memorryfor(int i = 0; i < NUM_IMAGES; i++)delete images[i];// delete[] images;
}

执行过程:
1.父类Image一开始就生成静态的data:prototype[ ]指针数组 和 _nextSlot;
子类LandSatImage、SpotImage一开始也均生成静态的data: _landSatImage,_count 和 _spotImage,_count。
而子类在生成静态对象_landSatImage(_spotImage)时调用了构造函数,则其里面的成员函数addPrototype()也被调用(继承自父类的addPrototype() 函数),即参数传入子类指针去调用父类的addPrototype()函数(即将子类指针对象添加进父类的指针数组 _prototype[ ])。
而且因为class LandSatImage在前,所以_prototype[0] =LandSatImage*,而_prototype[1] = SpotImage*( 注意此时的_prototype[ ]数组长度只有2,因为只有两种子类)。
2.创建父类指针数组对象 images[i] ,(通过类名Image::)调用父类成员函数findAndClone(input[i]),且传入参数为子类类型。在findAndClone()函数中进行类型判断(在 _prototype[ ]数组中看类型是否有与传入参数类型匹配的),如果符合,则跳入相应的子类中去调用clone()函数,通过第二种构造函数LandSatImage(1)【或SpotImage(1)】,记录对应的_id,然后以指针类对象的数据形式返回到 image[i] 内。


http://chatgpt.dhexx.cn/article/2b6v5EZo.shtml

相关文章

Active Directory 03 - Delegation(委派),MS-SFU 规范以及 Protocol Transition

写在最前 如果你是信息安全爱好者&#xff0c;如果你想考一些证书来提升自己的能力&#xff0c;那么欢迎大家来我的 Discord 频道 Northern Bay。邀请链接在这里&#xff1a; https://discord.gg/9XvvuFq9Wb我会提供备考过程中尽可能多的帮助&#xff0c;并分享学习和实践过程…

ichunqiu云境 - Delegation Writeup

0x1 Info 靶场地址&#xff1a;https://yunjing.ichunqiu.com/ranking/summary?idBzMFNFpvUDU 0x2 Recon Target external IP 39.98.34.149 Nmap results 关注80端口的http服务&#xff0c;目录爆破&#xff08;省略&#xff09;找到 /admin 使用弱口令登录进入后台&am…

Spark与hdfs delegation token过期的排查思路总结

背景 hadoop delegation token的问题相对比较混乱和复杂&#xff0c;简单说下这东西的出现背景&#xff0c;最早的hadoop的因没有的完善的安全机制&#xff08;安全机制主要包括&#xff1a;认证 鉴权&#xff0c;hadoop这里主要是身份认证机制没有&#xff09;&#xff0c;所…

Hadoop Delegation Tokens详解

转载自&#xff1a;《Hadoop Delegation Tokens详解》 https://www.jianshu.com/p/617fa722e057 本文是cloudera公司的一篇技术博客&#xff0c;原文地址&#xff1a;Hadoop Delegation Tokens Explained 译文 Hadoop Security在2009年被设计并实现&#xff0c;此后趋于稳定…

delegation java_SQL Server配置delegation实现double-hop

参考文献 前言 在上一篇博客SQL Kerberos的原理及实验中讲到了windows验证的两种模式分别是NTLM和Kerberos&#xff0c;那么他们有何区别&#xff0c;在功能上又有何不同。 NTLM是一种比较简单的方法&#xff0c;能够在大部分情况下完成任务。但是它只能完成单跃点认证(只有一个…

一文讲透hdfs的delegation token

【背景】 前一段时间总结了hadoop中的token认证、yarn任务运行中的token&#xff0c;其中也都提到了delegation token。而最近也遇到了一个问题&#xff0c;问题现象是&#xff1a;flink任务运行超过七天后&#xff0c;由于宿主机异常导致任务失败&#xff0c;继而触发任务的重…

hdfs delegation token 过期问题分析

什么是delegation token delegation token其实就是hadoop里一种轻量级认证方法&#xff0c;作为kerberos认证的一种补充。理论上只使用kerberos来认证是足够了&#xff0c;为什么hadoop还要自己开发一套使用delegation token的认证方式呢&#xff1f;这是因为如果在一个很大的…

Delegation Token

Delegation Token 为什么要用delegation tokenDelegation Token 生命周期NameNode中Delegation Token的实现 Hadoop最初的实现中并没有认证机制&#xff0c;这意味着存储在Hadoop中的数据很容易泄露。在2010年&#xff0c;安全特性被加入Hadoop&#xff08;HADOOP-4487&#xf…

用委托机制(delegation)来定制行为

用委托机制&#xff08;delegation&#xff09;来定制行为 应用程序的委托是Cocoa最重要的设计模式——委托机制的一个例子。 委托机制的想法在于&#xff1a;一个对象能有单一的委托对象&#xff0c;可以在某些事件发生的时候调用它。从委托的角度来看&#xff0c;这就是某种…

设计模式--委托模式( Delegation)

1、模式定义 委托是对一个类的功能进行扩展和复用的方法。它的做法是&#xff1a;写一个附加的类提供附加的功能&#xff0c;并使用原来的类的实例提供原有的功能。 假设我们有一个 TeamLead 类&#xff0c;将其既定任务委托给一个关联辅助对象 JuniorDeveloper 来完成&#x…

委托(delegation)的使用方法

1&#xff0c;组合和委托 委托是一个对象请求另一个对象的功能&#xff0c;是复用的一种常见形式。 2&#xff0c;委托和继承 3&#xff0c;使用委托的好处 从程序的角度来讲&#xff1a;你就可以把委托看成是用来执行方法&#xff08;函数&#xff09;的一个“指针” 通俗的…

线性代数:05 实对称矩阵与二次型

本讲义是自己上课所用幻灯片&#xff0c;里面没有详细的推导过程&#xff08;笔者板书推导&#xff09;只以大纲的方式来展示课上的内容&#xff0c;以方便大家下来复习。 本章是特征值与特征向量知识的延续&#xff0c;根据谱定理可知实对称矩阵可以正交对角化&#xff0c;对…

矩阵空间、秩1矩阵

今天要介绍一种新的向量空间&#xff0c;即矩阵空间&#xff0c;之前碰到的所有向量空间&#xff0c;都是n维的实数空间&#xff0c;现在我们将矩阵当成向量&#xff0c;比如说将3*3的矩阵看作向量&#xff0c;这相当于从原来的n维为扩展到n*n维&#xff0c;那么明明是矩阵为什…

【线性代数】详解正定矩阵、实对称矩阵、矩阵特征值分解、矩阵 SVD 分解

前言 本文主要针对线性代数中的正定矩阵、实对称矩阵、矩阵特征值分解以及矩阵 SVD 分解进行总结。 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 正定矩阵 1. 概念 首先正…

【线性代数】矩阵及其特性

【线性代数】矩阵及其特性 写在前面只拉伸不旋转的方向特征值和特征向量相似和对角化正交&#xff0c;对称矩阵 拉伸最大的方向二次型理论从曲线而来合同矩阵正定二次型 参考资料 写在前面 本文是笔者用于复习本科期间所学线性代数&#xff0c;试图用一种更易接受的方式加强记…

对称函数、半正定矩阵(核函数涉及)

一、对称函数 在对称函数中&#xff0c;函数的输出值不随输入变数的排列而改变。从函数的形式中可以看出若输入变数排列后&#xff0c;方程式不会改变。例如对于一个球体&#xff0e;若 φ 为其方位角&#xff0c;θ为其天顶角&#xff0c;r为半径&#xff0c;则大圆距离可以表…

矩阵的秩,特征值和特征向量 矩阵基础概念

矩阵是非常重要而基础的数学知识了。大学课上学线性代数基本就是在学矩阵的各种操作和运算。在深度学习里&#xff0c;几乎所有的参数也都是存放在矩阵中&#xff0c;并通过矩阵来做各种运算。大概把矩阵的基本知识点复习和总结一下。 行列式和矩阵的区别&#xff1a; 行列式和…

c++求矩阵的秩_常见的矩阵分解

矩阵的谱分解(可对角化矩阵——满秩可逆) 谱分解定理&#xff1a;设 为一个n阶可对角化矩阵&#xff0c;A的谱为 其中 的重数为 ,则存在唯一一组s个n阶方阵 ,满足(1) (2) (3) (4) (5) 这些矩阵 称为矩阵A的成分矩阵或主幂等矩阵。一般成分矩阵不一定是Hermite矩阵&a…

线性代数笔记15——矩阵空间和秩1矩阵

矩阵空间 矩阵空间是对向量空间的扩展&#xff0c;因为矩阵的本质是向量&#xff0c;所以与向量空间类似&#xff0c;也存在矩阵空间。 在向量空间中&#xff0c;任意两个向量的加法和数乘仍然在该空间内。类似的&#xff0c;所有固定大小的矩阵也组成了矩阵空间&#xff0c;在…

满秩矩阵与正定矩阵

满秩矩阵 设A是n阶矩阵, 若r&#xff08;A&#xff09; n, 则称A为满秩矩阵。但满秩不局限于n阶矩阵。 若矩阵秩等于行数&#xff0c;称为行满秩&#xff1b;若矩阵秩等于列数&#xff0c;称为列满秩。既是行满秩又是列满秩则为n阶矩阵即n阶方阵。 矩阵的秩&#xff1a; 用初…