C++ STL vector 模拟实现

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:C++之STL
🔥<3>创作者:我的代码爱吃辣
☂️<4>开发环境:Visual Studio 2022
💬<5>前言:上次我们已经数字会用了vector,这次我们对其底层更深一步挖掘,其中重点是,Vector中一些深浅拷贝问题。

目录

一.Vector模拟实现的整体框架

二. Vector的构造与析构

三.size(),capacity()

 四.reserve(),resize()

1.reserve()

2.resize

五.push_back(),pop_back()

1.push_back()

2. pop_back()

六.Vector的迭代器

 七.operator [ ]

 八.insert(),erase()

1.迭代器失效

2.insert()

3.erase()

九.再看Vector构造函数

十.拷贝构造

1.深浅拷贝

2.正确的拷贝构造代码:

3.正确的 reserve()

4.赋值运算符重载

十一.总体代码


一.Vector模拟实现的整体框架

我们先认识一下Vector的整体模拟实现框架,Vector在功能上就是我们数据结构阶段实现的顺序表基本一致,但是Vector在成员框架上与顺序表有所不同,且Vector使用类和对象封装支持模板泛型。

template<class T>
class Vector
{
public://迭代器类型typedef T* iterator;typedef const T* const_iterator;//...private:iterator _start;//数据存储首地址iterator _finish;//有效数据尾部地址下一个地址。iterator _end_of_storage;//容量尾地址下一个地址
};

Vector的迭代器是对顺序表原生类型指针的封装。

二. Vector的构造与析构

Vector主要是对成员变量初始化:

	Vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}

析构主要释放我们申请的空间:

    ~Vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}

三.size(),capacity()

size()返回当前顺序表存储的数据个数,capacity()返回当前顺序表的容量。

    size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}

 四.reserve(),resize()

1.reserve()

设置Vector的容量,注意容量支持增加,但是不支持减小。

void reserve(size_t capa){//仅支持容量扩大,不支持容量减小if (capacity() < capa){size_t sz = size();iterator tmp = new T[capa];//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,//如果之前没有容量仅需,将新开的空间指向我们的_start.if (_start){memcpy(tmp, _start, sizeof(T) * capacity()); /*error !!*/delete[] _start;}//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,//然后计算的size也并非是,准确的size。_start = tmp;_finish = tmp + sz;_end_of_storage = _start + capa;}}

注意:

此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,然后计算的size也并非是,准确的size。除此之外这份代码依旧是有问题的。我们后面解释。

错误代码如下:

    void reserve(size_t capa){if (capacity() < capa){iterator tmp = new T[capa];if (_start){memcpy(tmp, _start, sizeof(T) * capacity());delete[] _start;}//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,//然后计算的size也并非是,准确的size。_start = tmp;_finish = tmp + size();_end_of_storage = _start + capa;}}

2.resize

提供改变存储数据的个数的能力。如果 n < size 时就是删除数据,n > size且空间不够时需要扩容+初始化,空间足够,仅需要初始化剩下的空间。

    void resize(size_t n, T val = T()){	//1.n < size;-->删除数据if (n < size()){_finish = _start + n;}//2.n > sizeelse {//(1)如果空间不足,需要扩容+初始化if (n >= capacity()){reserve(n);}//(2)空间足够,仅需要初始化剩下的空间while (_finish != _start + n){*(_finish) = val;_finish++;}}}

五.push_back(),pop_back()

1.push_back()

从尾部插入一个数据。

	void push_back(const T& val){//检查是否需要扩容if (_finish == _end_of_storage){capacity() == 0 ? reserve(5) : reserve(capacity() * 2);}//插入数据*(_finish) = val;_finish++;}

2. pop_back()

	bool empty() const {return size() == 0;}void pop_back(){//判空assert(!empty());//我们仅需将维护尾部数据的指针向前挪一位。_finish--;}

六.Vector的迭代器

	typedef T* iterator;typedef const T* const_iterator;

Vector底层就是顺序存储的结构,所以可以使用原生指针作为迭代器。

	//普通迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const 迭代器const_iterator begin()const {return _start;}const_iterator end()const{return _finish;}

有了迭代器就可以支持迭代器访问,和范围for。

int main()
{Vector<int> v1;v1.push_back(100);v1.push_back(200);v1.push_back(300);v1.push_back(400);v1.push_back(500);v1.push_back(600);v1.push_back(700);Vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";it++;}cout << endl;for (auto e : v1){cout << e << " ";}return 0;
}

 七.operator [ ]

	//穿引用返回T& operator[](size_t pos){//判断位置的合法性assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}

 八.insert(),erase()

1.迭代器失效

在模拟实现之前我们先看一下什么是迭代器失效问题:

迭代器失效问题通常发生在容器类的成员函数中,例如erase和insert。在这些函数中,迭代器被重置或修改,导致原始迭代器不再指向容器的正确位置,从而导致迭代器失效。

int main()
{vector<int> v1;v1.push_back(100);v1.push_back(200);v1.push_back(300);v1.push_back(400);v1.push_back(500);v1.push_back(600);v1.push_back(700);  vector<int>::iterator pos = find(v1.begin(), v1.end(),200);//对pos位置插入v1.insert(pos, 150);//pos已经失效v1.insert(pos, 170);return 0;
}

原理图:

情况一:

 上述代码中的迭代器失效问题也是属于这种情况。

情况二:

2.insert()

    iterator insert(iterator pos,T val){assert(pos >= _start);assert(pos < _finish);//迭代器失效问题,记录pos的相对位置int len = pos - _start;if (_finish == _end_of_storage){capacity() == 0 ? reserve(5) : reserve(capacity() * 2);}//扩容后重新计算pos,没有发生扩容pos不变pos = _start + len;iterator end = _finish;//数据挪动while (end >= pos){(*end) = *(end - 1);end--;}_finish++;(*pos) = val;return pos;}

在使用pos时要注意扩容会使得pos失效,需要重新计算pos位置。

3.erase()

	iterator erase(iterator pos){//判断位置是否合法assert(pos >= _start);assert(pos < _finish);iterator end = pos ;/挪动数据删除while (end < _finish){*end = *(end + 1);end++;}_finish--;return pos;}

九.再看Vector构造函数

std中的vector还支持使用指定个数和初始化值初始化,和迭代器区间初始化。这两个功能在我们平时也是能用到的。

    //1.Vector<T> v(5,10);创建一个Vector并且初始化前5个值为10Vector(size_t n, const T& val = T()):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}//2.迭代器初始化,[frist,lest)template<class InputIterator>Vector(InputIterator frist, InputIterator lest):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(lest - frist);while (frist != lest){push_back(*frist);frist++;}}//3.防止构造函数调用错误Vector(int n, const T& val = T()):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}

第三个构造函数的作用是防止构造调用错误冲突,在我们进行如下的调用时:

Vector<int> v2(5, 10);

编译器会以为我们在调用迭代器区间初始化构造函数,因为经过模板的推导,只有迭代器区间初始化构造函数,更适合这个调用。然后将一个整形当作地址在迭代器区间初始化构造函数里面解引用了,报错是:非法的间接寻址

 正常调用结果:

十.拷贝构造

今天这里编译器默认生成的拷贝构造显然是不能用了。

1.深浅拷贝

万万不可以直接使用拷贝函数按二进制或者按字节直接拷贝了。

错误代码1:

	Vector(const Vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(v.capacity());//万万不可以直接按二进制拷贝memcpy(_start, v._start, sizeof(T) * v.capacity()); /*error!!!!*/_finish = _start + v.size();_end_of_storage = _start + v.capacity();}

原因:

调用处代码:


int main()
{string str("abcdefg");Vector<string> v2(5,str);Vector<string> v3(v2);return 0;
}

 会使得我们同一块空间被delete两次从而引发内存错误。

2.正确的拷贝构造代码:

	Vector(const Vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(v.capacity());//这里我们将数据一个一个push进去,这样我们借助push_back底层插入的时候,//会使用string的赋值构造,完成深拷贝。for (int i = 0; i < v.size(); i++){push_back(v[i]);}}//现代写法Vector(const Vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){Vector<T> tmp(v.begin(), v.end());swap(tmp);}

错误代码2:reserve()

	void reserve(size_t capa){//仅支持容量扩大,不支持容量减小if (capacity() < capa){size_t sz = size();iterator tmp = new T[capa];//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,//如果之前没有容量仅需,将新开的空间指向我们的_start.if (_start){//这里千万不能按二进制直接拷贝.memcpy(tmp, _start, sizeof(T) * capacity());   /*error !!*/delete[] _start;}//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,//然后计算的size也并非是,准确的size。_start = tmp;_finish = tmp + sz;_end_of_storage = _start + capa;}}

这里我们仍然是使用了memcpy。

调用处代码:

int main()
{string str("abcdefg");Vector<string> v2;for (int i = 0; i < 6; i++){v2.push_back(str);}return 0;
}

3.正确的 reserve()

void reserve(size_t capa){//仅支持容量扩大,不支持容量减小if (capacity() < capa){size_t sz = size();iterator tmp = new T[capa];//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,//如果之前没有容量仅需,将新开的空间指向我们的_start.if (_start){//这里千万不能按二进制直接拷贝.//memcpy(tmp, _start, sizeof(T) * capacity());   /*ror !!*/for (int i = 0; i < size(); i++){//=内置类型直接赋值,自定义类型使用赋值构造tmp[i]=_start[i];}delete[] _start;}//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,//然后计算的size也并非是,准确的size。_start = tmp;_finish = tmp + sz;_end_of_storage = _start + capa;}}

这里有一个细节就是在reserve和拷贝构造的拷贝数据的时候我们都是使用了赋值。问题我们并没有重载赋值运算符,编译器自动生成,简单来说就是这里又会是一个浅拷贝。

4.赋值运算符重载

    //传统写法Vector<T>& operator=(const Vector<T>& v){T* tmp = new T[v.capacity()];if (_start){for (int i = 0; i < v.size(); i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + v.size();_end_of_storage = _start + v.capacity();return *this;}//现代写法void swap(Vector<T>& v ){std::swap(v._start, _start);std::swap(v._finish, _finish);std::swap(v._end_of_storage, _end_of_storage);}Vector<T>& operator=(Vector<T> v){swap(v);return *this;}

现代写法利用,拷贝构造拷贝出来的对象,然后交换对象的成员。

十一.总体代码

#pragma once
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cassert>
using namespace std;template<class T>
class Vector
{
public:typedef T* iterator;typedef const T* const_iterator;Vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}//1.Vector<T> v(5,10);创建一个Vector并且初始化前5个值为10Vector(size_t n, const T& val = T()):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}//2.迭代器初始化,[frist,lest)template<class InputIterator>Vector(InputIterator frist, InputIterator lest):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(lest - frist);while (frist != lest){push_back(*frist);frist++;}}//3.防止构造函数调用冲突Vector(int n, const T& val = T()):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){reserve(n);for (int i = 0; i < n; i++){push_back(val);}}//传统写法 拷贝构造//Vector(const Vector<T>& v)//	:_start(nullptr),//	_finish(nullptr),//	_end_of_storage(nullptr)//{//	reserve(v.capacity());//	//这里我们将数据一个一个push进去,这样我们借助push_back底层插入的时候,//	//会使用string的赋值构造,完成深拷贝。//	for (int i = 0; i < v.size(); i++)//	{//		_start[i] = v[i];//	}//}//现代写法,拷贝构造Vector(const Vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){Vector<T> tmp(v.begin(), v.end());swap(tmp);}//传统写法,赋值拷贝//Vector<T>& operator=(const Vector<T>& v)//{//	T* tmp = new T[v.capacity()];//	if (_start)//	{//		for (int i = 0; i < v.size(); i++)//		{//			tmp[i] = _start[i];//		}//		delete[] _start;//	}//	_start = tmp;//	_finish = _start + v.size();//	_end_of_storage = _start + v.capacity();//	//	return *this;//}void swap(Vector<T>& v ){std::swap(v._start, _start);std::swap(v._finish, _finish);std::swap(v._end_of_storage, _end_of_storage);}//现代写法,赋值拷贝Vector<T>& operator=(Vector<T> v){swap(v);return *this;}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}void reserve(size_t capa){//仅支持容量扩大,不支持容量减小if (capacity() < capa){size_t sz = size();iterator tmp = new T[capa];//分清当前的是否已经有了容量,如果已经有了容量需要释放之前的容量,//如果之前没有容量仅需,将新开的空间指向我们的_start.if (_start){//这里千万不能按二进制直接拷贝.//memcpy(tmp, _start, sizeof(T) * capacity());   /*ror !!*/for (int i = 0; i < size(); i++){//=内置类型直接赋值,自定义类型使用赋值构造tmp[i]=_start[i];}delete[] _start;}//注意:此处不能直接tmp+size()来计算,因为在计算_start的时候已经已经改变了_start,//然后计算的size也并非是,准确的size。_start = tmp;_finish = tmp + sz;_end_of_storage = _start + capa;}}void resize(size_t n, T val = T()){	//1.n < size;-->删除数据if (n < size()){_finish = _start + n;}//2.n > sizeelse {//(1)如果空间不足,需要扩容+初始化if (n >= capacity()){reserve(n);}//(2)空间足够,仅需要初始化剩下的空间while (_finish != _start + n){*(_finish) = val;_finish++;}}}//穿引用返回T& operator[](size_t pos){//判断位置的合法性assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}void push_back(const T& val){if (_finish == _end_of_storage){capacity() == 0 ? reserve(5) : reserve(capacity() * 2);}//内置类型直接赋值,自定义类型使用赋值构造*(_finish) = val;_finish++;}bool empty() const {return size() == 0;}void pop_back(){//判空assert(!empty());//我们仅需将维护尾部数据的指针向前挪一位。_finish--;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator end = pos ;while (end < _finish){*end = *(end + 1);end++;}_finish--;return pos;}iterator insert(iterator pos,T val){assert(pos >= _start);assert(pos < _finish);//迭代器失效问题,记录pos的相对位置int len = pos - _start;if (_finish == _end_of_storage){capacity() == 0 ? reserve(5) : reserve(capacity() * 2);}//扩容后重新计算pos,没有发生扩容pos不变pos = _start + len;iterator end = _finish;//数据挪动while (end >= pos){(*end) = *(end - 1);end--;}_finish++;(*pos) = val;return pos;}//普通迭代器iterator begin(){return _start;}iterator end(){return _finish;}//const 迭代器const_iterator begin()const {return _start;}const_iterator end()const{return _finish;}~Vector(){delete[] _start;_start = _finish = _end_of_storage = nullptr;}
private:iterator _start;//数据存储首地址iterator _finish;//有效数据尾部地址下一个地址。iterator _end_of_storage;//容量尾地址下一个地址int tmp;
};

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/34672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

unity新输入系统的简单使用(New InputSystem)

1、在包管理器 unity注册表中下载安装InputSystem 2、给玩家添加组件PlayerInput&#xff0c;点击CreatAction,创建一个InputAct InputAct,这是玩家的输入文件&#xff0c;在里面可以设置玩家输入 3、使用 例如玩家控制角色移动 在InputAct中&#xff0c;默认已经设置好了移…

[python]RuntimeError: Can‘t decrement id ref count (unable to close file...

使用spectralspatial模型进行EEG分类时&#xff0c;出现以下错误 RuntimeError: Cant decrement id ref count (unable to close file, errno 5, error message Input/output error) Segmentation fault (core dumped) 猜测是因为存储的model太大了导致的&#xff0c;找到了…

hive 中最常用日期处理函数

hive 常用日期处理函数 在工作中&#xff0c;日期函数是提取数据计算数据必须要用到的环节。哪怕是提取某个时间段下的明细数据也得用到日期函数。今天和大家分享一下常用的日期函数。为什么说常用呢&#xff1f;其实这些函数在数据运营同学手上是几乎每天都在使用的。 技术交…

FreeRTOS(计数信号量)

资料来源于硬件家园&#xff1a;资料汇总 - FreeRTOS实时操作系统课程(多任务管理) 目录 一、计数信号量的定义与应用 1、计数信号量的定义 2、计数信号量的应用 二、计数信号量的运作机制 1、任务间计数信号量的实现 三、计数信号量常用的API函数 1、计数信号量典型流程…

双指针算法

文章目录 双指针算法leetcode题目 双指针算法 双指针算法可以实现对于时间复杂度降一维度&#xff0c;使得O(n2)的算法时间复杂度变为O(n) 指针类型 对撞指针快慢指针 对撞指针 一般是用于顺序结构中的&#xff0c;也可以称为左右指针&#xff0c;从两端向中间移动&#xff0c…

【工作中问题解决实践 十一】Kafka消费者消费堆积且频繁rebalance

最近有点不走运&#xff0c;老是遇到基础服务的问题&#xff0c;还是记着点儿解决方法&#xff0c;以后再遇到快速解决吧&#xff0c;今天遇到这个问题倒不算紧急&#xff0c;但也能通过这个问题熟悉一下Kafka的配置。 问题背景 正在开会的时候突然收到一连串的报警&#xff…

上篇文章viewerjs(npm包补丁)的错误更正。(npm补丁本地没问题,流水线打包要没效果,要么报错)

1、上篇文档的包补丁在本地使用没有问题&#xff0c;并且同事拉代码后也会自动同步npm_modules&#xff0c;也没有问题 2、问题出现在打包上&#xff0c;我这边的项目是用teambition上的飞流设置的流水线来进行打包的&#xff0c;里面用到了两个指令 yarn install npm run bu…

IDEA关闭项目,但是后台程序没有关闭进程(解决方案)

最近遇到一个很奇怪的问题&#xff0c;idea关闭项目后&#xff0c;系统进程没有杀死进程&#xff0c;再次执行的时候会提示端口占用&#xff0c;并提示Process exited with an error: 1 (Exit value: 1) 错误原因&#xff1a;应用程序关闭后&#xff0c;进程不能同步关闭 解决方…

使用ffmpeg将m4a及wav等文件转换为MP3格式

要使用ffmpeg将m4a及wav等文件转换为MP3格式&#xff0c;您可以按照以下步骤进行操作&#xff1a; 安装 ffmpeg 确保您已经安装了ffmpeg软件。如果没有安装&#xff0c;请访问ffmpeg的官方网站https://ffmpeg.org/ 并按照说明进行安装。 Win10 / Win11 可以通过 winget 命令…

角角の Qt学习笔记(一)

目录 一、解决在创建新项目时遇到的几个问题 二、信号和槽&#xff08;非自定义&#xff09; 三、调用 UI 中的元素&#xff08;比如按钮&#xff09; 一、解决在创建新项目时遇到的几个问题 在新建项目时&#xff0c;我选择的构建系统为CMake。然后勾选了Generate form&…

fabric.js里toDataURL后,画布内容展示不全?

复现场景&#xff1a; 用fabric生成画布后&#xff0c;转成图片&#xff0c;然后直接在浏览器里打开&#xff0c;画布展示内容缺失 画布原图&#xff1a; toDataURL后链接在浏览器打开&#xff1a; 原因解析&#xff1a; base64链接太长&#xff0c;输入浏览器链接被截断&…

MySQL 数据库文件的导入导出

目录 数据库的导出 导出整个数据库 导出数据库中的数据表 导出数据库结构 导出数据库中表的表结构 导出多个数据库 导出所有数据库 数据库的导入 数据库的导出 mysqldump -h IP地址 -P 端口 -u 用户名 -p 数据库名 > 导出的文件名 用管理员权限打开cmd进入MySQL的bi…

python+tkinter实现图书管理系统(首发)

文章目录 前文运行环境功能图数据操作图书数据管理用户数据管理借书记录管理 功能界面管理员界面首页图书管理用户管理借书记录更改密码 普通用户界面 其他功能数字时间显示加载画面显示输入框提示词界面居中显示借书时间和还书时间记录公告栏数据操作 结尾 前文 本文将用tkin…

springboot异步任务

在Service类声明一个注解Async作为异步方法的标识 package com.qf.sping09test.service;import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service;Service public class AsyncService {//告诉spring这是一个异步的方法Asyncp…

JavaWeb 中对 HTTP 协议的学习

HTTP1 Web概述1.1 Web和JavaWeb的概念1.2 JavaWeb技术栈1.2.1 B/S架构1.2.2 静态资源1.2.3 动态资源1.2.4 数据库1.2.5 HTTP协议1.2.6 Web服务器 1.3 Web核心 2 HTTP2.1 简介2.2 请求数据格式2.2.1 格式介绍2.2.2 实例演示 2.3 响应数据格式2.3.1 格式介绍2.3.2 响应状态码2.3.…

面试热题(合并两个有序列表)

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 合并链表这类型题也是比较经典的题了&#xff0c;因为链表是由指针相互指向而确定位置&#xff0c;所以我们只需要改变某些节点的指针便可以做到对链表进行排序 今天这个方法…

Spring Bean 生命周期的执行流程

问题描述 Spring 生命周期全过程大致分为五个阶段&#xff1a; 1、创建前准备阶段 2、创建实例阶段 3、依赖注入阶段 4、 容器缓存阶段 5、销毁实例阶段 下图是 Spring Bean 生命周期完整流程图&#xff0c;其中对每个阶段的具体操作做了详细介绍&#xff1a; 一、创建前准备阶…

阶梯费用计算(配置化_最小demo)

本文旨在提供一种配置化思路计算阶梯费用&#xff0c;更高级的做法则是通过数据库配置&#xff0c;注册中心等&#xff1b;在表达式上可以采用自定义或者spel表达式等其他方式进行处理&#xff1b;(代码仅展示最小demo,部分不完善地方自行补充) 思路&#xff1a;N个区间对应N个…

Spring Boot 项目应用消息服务器RabbitMQ(简单介绍)

一、背景 本章讲述的是在用户下单环节&#xff0c;消息服务器RabbitMQ 的应用 1.1 消息服务器的应用 在写一个电商项目的小demo&#xff0c;在电商项目中&#xff0c;消息服务器的应用&#xff1a; 1、订单状态通知&#xff1a;当用户下单、支付成功、订单发货、订单完成等…

【MFC】10.MFC六大机制:RTTI(运行时类型识别),动态创建机制,窗口切分,子类化-笔记

运行时类信息&#xff08;RTTI&#xff09; C: ##是拼接 #是替换成字符串 // RTTI.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <afxwin.h>#ifdef _DEBUG #define new DEBUG_NEW #endifCWinApp th…