C++面向对象高级编程(五)类与类之间的关系
技术在于交流、沟通,转载请注明出处并保持作品的完整性。
本节主要介绍一下类与类之间的关系,也就是面向对象编程先介绍两个术语
Object Oriented Programming OOP面向对象编程
Object Oriented Design OOD面向对象设计
对于类与类之间的关系有很多种,但是我认为理解3种足够
1.Inheritance (继承)
2.Composition (组合)
3.Delegation (委託) 该种关系也可以理解成聚合
一.组合
1.定义: has-a的关系,一个类中有包含另外一个类 (类中的成员变量之一是类),是包含一个对象,而不是包含一个指针,如果你组合了这个类,那么你就将拥有你包含的类的全部功能
下面我介绍一个组合的实际应用
#include<deque>
#include <queue>
template <class T>
class queue {
...
protected:
std::deque<T> c; // 底層容器 has-a的关系
public:
// 以下完全利用 c 的操作函數完成
bool empty() const { return c.empty(); }//利用deque的功能来实现queue新定义的功能
size_t size() const { return c.size(); }
reference front() { return c.front(); }
reference back() { return c.back(); } void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
queue是一种队列操作,单方向操作先进先出
deque是两端都可进出,所以说deque的功能较强大与quque,但是如果我queue组合deque(包含 has-a)那么我们就可以利用deque的功能来实现queue新定义的功能
这就是组合关系的一种实际应用,同时它也是adapter设计模式
2.类图
那么上面的queue与deque的类图为
queue包含deque
3.内存管理
template <class T>
class queue {
protected:
deque<T> c;
...
}; template <class T>
class deque {
protected:
Itr<T> start; Itr<T> start;//16 bit
Itr<T> finish; Itr<T> finish; //16 bit
T** map; T** map; //4bit
unsigned int map_size; //4bit
}; template <class T>
struct Itr { struct Itr {
T* cur; T* cur; //4bit
T* first; T* first;
T* last; T* last;
T** node;
...
};
图示
所以是queue的内存为40bit
4.构造与析构
未了方便我们的理解,我们可以将组合关系联想成下图
a.构造由内而外
Container 的构造函数首先调用 Component 的 default 构造函数,然後才执行自己 的构造函数,可以理解成这样
Container::Container(...): Component() { ... };
b.析构由外而内
Container 的析构函数首先执行自己的,然后调用 Component 的 析构函数,可以理解成这样
Container::~Container(...){ ... ~Component() };
5.生命周期
Container于Component具有相同的生命周期
二.聚合 也就是委托关系
1.定义has-a pointer,一个类中包含另一个类的指针,你也同样拥有被包含类的全部功能,他有一个重要的使用方法handle/body(pImpl)(我在格式工厂(六)shared_ptr中有介绍)
class StringRep; class String {//handle
public:
String();
String(const char* s);
String &operator=(const String& s); ~String();
....
private:
StringRep* rep; // pimpl
}; class StringRep { //body
friend class String;
StringRep(const char* s);
~StringRep();
int count;
char* rep;
};
功能其实与组合非常相似
2.类图
3.内存管理
包含一个指针 4bit
4.构造与析构
不发生影响
5.生命周期
生命周期可以不相同
三.继承
1.定义is-a的关系,分为父类(Base)和子类(Drived),可以理解成孩子继承父亲的财产,就是父类有的子类都可以有,也可以理解成子类有父类的成分
class _List_node_base
{
...
_List_node_base* _M_next;
_List_node_base* _M_prev;
...
}; template<typename _Tp>
class _List_node: public _List_node_base
{
_Tp _M_data;
};
2.类图
3.内存管理
无太大关联,抛去成员变量,子类比父类多一个虚函数表 4bit
4.构造与析构
子类含有父类的成分,可以理解成
构造由内而外
Derived 的构造函数首先调用Base 的 default 构造函数, 然后执行自己的
Derived::Derived(...): Base() { ... };
析构由外而内
Derived 的析构函数首先执行自己的,然后调用用 Base 的析构函数。
Derived::~Derived(...){ ... ~Base() };
5.继承真正的使用是与虚函数的搭配
虚函数:用virtual声明的函数,它有三种形式
non-virtual 即普通函数,你不希望子类重新定义它(重新定义override)
virtual 函数(虚函数):你希望 derived class 重新定义 它,且你对这个函数有默认定义
pure virtual 函数(纯虚函数):你希望 derived class 一定要重新定义它,你对它没有默认定义
void func_1();//non-virtual
virtual void func_2();//virtual
virtual void func_3() = ;//pure virtual
下面我们来验证一下上面的继承规则
class A
{
public:
A()
{
cout<< "A ctor" << endl;
}
virtual ~A()
{
cout<< "A dctor" << endl;
} void func()
{
cout<< "A::func()"<<endl;
} virtual void func_virtual()
{
cout<< "A::func_virtual()"<<endl;
}
}; class B : public A
{
public:
B()
{
cout<< "B ctor"<<endl;
}
~B()
{
cout<< "B dctor"<<endl;
} void func_virtual()
{
cout<< "B::func_virtual()"<<endl;
}
};
我们先创建一个B对象看看都能输出什么
int main(int argc, const char * argv[])
{
B b;
return ;
}
输出结果
说明继承由内而外的构造,和由外而内的析构
继续看
int main(int argc, const char * argv[]) {
A* a = new B(); //父类指针可以指向子类对象(一般情况下子类的内存占用会大于父类,所以父类指针指向子类是可以的,那么反过来 子类指针指向父类就不行了)
a->func();
a->func_virtual();
delete a;//谁申请谁释放
a = nullptr;
return ;
}
输出结果
你会返现为什么我用a调用func_virtual() 会调用到B的该函数,这个就是继承的好处之一了,他能动态识别是谁调用
用虚函数表来解释动态识别想必大家都会知道,现在我来介绍一下我的理解---this指针
在C++类中除了静态变量都有this指针,在上面第2行 A* a = new B(); 其实 a是一个b对象
在第3行 a->func(),编译器会编译成a->func(&a),(我在之前的文章中介绍过谁调用谁就是this,那么治理的&a 就相当于this),然后会在B中找func(),发现没有就去父类的A中去找
在第4行 a->func_virtual() => a->func_virtual(&a) 在B中找到了所以调用.
四 组合+继承
组合和继承共同使用它们的它们的创建顺序会是什么样子
第一种
Component构造 > Base构造 > 子类构造 析构相反
第二种
组合和继承的构造顺序都是由内而外,析构顺序都是由外而内,那上面的构造析构顺序呢
class A
{
public:
A(){cout<< "A ctor" << endl;}
virtual ~A(){cout<< "A dctor" << endl;}
void func(){cout<< "A::func()"<<endl;}
virtual void func_virtual(){cout<< "A::func_virtual()"<<endl;}
}; class C
{
public:
C(){cout<< "C ctor"<<endl;}
~C(){cout<< "C dctor"<<endl;}
}; class B : public A
{
public:
B(){cout<< "B ctor"<<endl;}
~B(){cout<< "B dctor"<<endl;}
void func_virtual(){cout<< "B::func_virtual()"<<endl;}
private:
C c;
};
输出结果
Base构造 > Component构造 > 子类构造 析构相反
Derived 的构造函数首先调用 Base 的 default 构造函数, 然后调用 Component 的 default 构造函数, 然后执行自己
Derived::Derived(...): Base(),Component() { ... };
Derived 的析构函数首先执行自己, 然后调用 Component 的 析构函数,然后調用 Base 的析构函数
Derived::~Derived(...){ ... ~Component(), ~Base() };
五 聚合 + 继承
这个我用一种设计模式来做实例
观察者模式(主要介绍聚合+继承的实现,详细的观察者模式我会在设计模式中介绍)
假设有一个txt文件,我用三个不同的阅读软件同时读取这一个txt文件,那么当txt内容发生改变时,这三个阅读器的内容都应做出相应的变化,其实现代码大致如下
用类图描述一下
大致实现如下
class Subject {
String m_value;
vector<Observer*> m_views;//包含指针
public:
void attach(Observer* obs) {
m_views.push_back(obs);//捕获Observe子类
}
void set_val(int value) {//当前内容发生改变
m_value = value;
notify();
}
void notify() {//通知所有子类发生改变,通过其继承关系调用相应的方法
for (int i = ; i < m_views.size(); ++i) m_views[i]->update(this, m_value);
}
}; class Observer {
public:
virtual void update(Subject* sub, int value) = ;
}; class Observer_Sub : public Observer //不同的阅读工具 同时观察Subject中的m_value
{
void update(){...;}
};
五 聚合 + 继承
下面这个例子有点难理解且非常抽象,
现在我以原型模式来实现一个自动创建创建子类的方法
1.类图
2.实现如下
#include <iostream>
using namespace std; enum imageType
{
LSAT, SPOT
}; class Image
{
public:
virtual void draw() = ;
static Image *findAndClone(imageType);
protected:
virtual imageType returnType() = ;
virtual Image *clone() = ;
// 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[];
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 = ; i < _nextSlot; i++)
{
if (_prototypes[i]->returnType() == type)
{
return _prototypes[i]->clone();
}
}
return nullptr;
}
子类SpotImage
class SpotImage: public Image
{
public:
imageType returnType() {
return SPOT;
}
void draw()
{
cout << "SpotImage::draw " << _id << endl;
}
Image *clone() {
return new SpotImage();
}
protected:
SpotImage(int dummy)
{
_id = _count++;
} private:
SpotImage()
{
addPrototype(this);
cout<< "static init SpotImage" << endl;
}
static SpotImage _spotImage;
int _id;
static int _count;
};
SpotImage SpotImage::_spotImage;
int SpotImage::_count = ;
子类LandSatImage
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 arg
Image *clone()
{
return new LandSatImage();
} 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);
cout<< "static init LandSatImage" << endl;
}
// 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 = ;
调用
// Simulated stream of creation requests
const int NUM_IMAGES = ;
imageType input[NUM_IMAGES] =
{
LSAT, LSAT, LSAT, SPOT, LSAT, SPOT, SPOT, LSAT
}; int main() { Image *images[NUM_IMAGES];
// Given an image type, find the right prototype, and return a clone for (int i = ; i < NUM_IMAGES; i++) images[i] = Image::findAndClone(input[i]); // Demonstrate that correct image objects have been cloned
for (int i = ; i < NUM_IMAGES; i++) images[i]->draw(); // Free the dynamic memory
for (int i = ; i < NUM_IMAGES; i++)
delete images[i]; return ;
}
其实主要难理解的地方有两个
a.静态变量率先初始化 a.SpotImage初始化其默认构造函数调用 Image::addPrototype()
b.LandSatImage 初始化其默认构造函数调用 Image::addPrototype()
这两步使Image::_nextSlot == 2 并使这两个子类注册在Image::_prototypes[]中
b.SpotImage和LandSatImage其clone()函数调用带参数的构造函数,默认构造函数留给静态变量初始化使用
如有不正确的地方请指正
参照<<侯捷 C++面向对象高级编程>>
C++面向对象高级编程(五)类与类之间的关系的更多相关文章
- Python面向对象高级编程-__slots__、定制类,枚举
当在类体内定义好各种属性后,外部是可以随便添加属性的,Python中类如何限制实例的属性? Python自带了很多定制类,诸如__slots__,__str__ __slots__ __slots__ ...
- C++面向对象高级编程(三)基础篇
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 概要 一.拷贝构造 二.拷贝赋值 三.重写操作符 四.生命周期 本节主要介绍 Big Three 即析构函数,拷贝构造函数,赋值拷贝函数,前面主 ...
- C++面向对象高级编程(八)模板
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 这节课主要讲模板的使用,之前我们谈到过函数模板与类模板 (C++面向对象高级编程(四)基础篇)这里不再说明 1.成员模板 成员模板:参数为tem ...
- C++面向对象高级编程(七)point-like classes和function-like classes
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 1.pointer-like class 类设计成指针那样,可以当做指针来用,指针有两个常用操作符(*和->),所以我们必须重载这两个操作 ...
- C++面向对象高级编程(四)基础篇
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 一.Static 二.模板类和模板函数 三.namespace 一.Static 静态成员是“类级别”的,也就是它和类的地位等同,而普通成员是“ ...
- C++面向对象高级编程(二)基础篇
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 概要 知识点1.重载成员函数 知识点2 . return by value, return by reference 知识点3 重载非成员函数 ...
- C++面向对象高级编程(一)基础篇
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 概要: 知识点1 构造函数与析构函数 知识点2 参数与返回值 知识点3 const 知识点4 函数重载(要与重写区分开) 知识点5 友元 先以C ...
- C++面向对象高级编程(九)Reference与重载operator new和operator delete
摘要: 技术在于交流.沟通,转载请注明出处并保持作品的完整性. 一 Reference 引用:之前提及过,他的主要作用就是取别名,与指针很相似,实现也是基于指针. 1.引用必须有初值,且不能引用nul ...
- C++面向对象高级编程(六)转换函数与non-explicit one argument ctor
技术在于交流.沟通,转载请注明出处并保持作品的完整性. 1.conversion function 转换函数 //1.转换函数 //conversion function //只要你认为合理 你可以任 ...
随机推荐
- PentestBox简明使用教程
介绍 PentestBox:渗透测试盒子 顾名思义,这是一个渗透工具包,但是不同于绝大多数国内xx工具包的是,这里集成的大都是Linux下的工具,Kali Linux上面的常用的很多工具这里面也都集成 ...
- ONVIF学习-ONVIF开发框架搭建(C++)
第一步.下载gsoap 从gsoap官网(http://www.genivia.com/products.html#notice)下载最新版gsoap(博主用的是gsoap_2.8.45).gsoap ...
- lvds split两channel输出到一个屏显示
转:https://blog.csdn.net/changqing1990/article/details/81128552 其实之前写过LCD/LVDS的一些时序的基本概念<与LCD移植相关的 ...
- Redis学习笔记之Redis中5种数据结构的使用场景介绍
原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构 ...
- 如何用纯 CSS 创作一个单元素抛盒子的 loader
效果预览 在线演示 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/qKwXbx 可交互视频 ...
- vsftpd基于mysql的认证方式
安装epel源: cd /etc/yum.repos.d wget http://mirrors.neusoft.edu.cn/epel/epel-release-latest-6.noarch.rp ...
- 20145314郑凯杰 《Java程序设计》第6周学习总结
20145314郑凯杰 <Java程序设计>第6周学习总结 教材学习内容总结 本次就不放上每个程序的运行结果了,在下面的问题探究里会写出的.代码托管如下: 第十章 InputStream与 ...
- 关于js中对事件绑定与普通事件的理解
普通事件指的是可以用来注册的事件: 事件绑定是指把事件注册到具体的元素之上. 通俗点说: 普通事件:给html元素添加一个特定的属性(如:onclick): 事件绑定:js代码中通过标记(id ta ...
- 在使用Vue.js中使用axios库时,遇到415错误(不支持的媒体类型(Unsupported media type))
知识点:vue2.0中使用axios进行(put,post请求时),遇到415错误 解决办法:在axios的第三个参数config中,设置请求头信息'Content-Type': 'applicati ...
- hbase(一)region
前言 文章不含源码,只是一些官方资料的整理和个人理解 架构总览 这张图在大街小巷里都能看到,感觉是hbase架构中最详细最清晰的一张,稍微再补充几点. 1) Hlog是低版本hbase术语,现在称为W ...