STL
STL容器
- 顺序容器
- string
- vector
- list
- forward_list
- queue
- deque
- stack
- 关联容器
- map
- set
- unordered_map
- unordered_set
STL容器底层数据结构
- vector
- 顺序表
- 维护的是一个连续线性空间,是物理上的连续
- list
- 双向链表,是逻辑上的连续
- forward_list
- 单链表封装,是逻辑上的连续
- deque
- 多个连续的存储块,并且在一个映射结构中保存对这些块及其顺序的跟踪
- stack
- 默认双端队列实现(deque)
- 运算受限的线性表
- queue
- 运算受限的线性表(deque)
- priority_queue
- 默认vector实现
- 顺序表
- array
- std风格的数组
- map/set
- 红黑树
- unordered_map/unordered_set
- 哈希表
Windows消息处理机制
- 哈希表
概述
Windows 是事件驱动的,事件驱动围绕着消息的产生与处理展开,事件驱动是靠消息循环机制来实现的。也可以理解为消息是一种报告有关事件发生的通知。
消息(Message)指的就是Windows 操作系统发给应用程序的一个通告,它告诉应用程序某个特定的事件发生了。
比如,用户单击鼠标或按键都会引发Windows 系统发送相应的消息。最终处理消息的是应用程序的窗口函数,如果程序不负责处理的话系统将会作出默认处理。
从数据结构的角度来说,消息是一个结构体,它包含了消息的类型标识符以及其他的一些附加信息。
系统定义的结构体MSG用于表示消息,MSG 具有如下定义形式:
1 2 3 4 5 6 7 8 9 |
typedef struct tagMSG { HWND hwnd;//hwnd 是窗口的句柄,这个参数将决定由哪个窗口过程函数对消息进行处理 UINT message;//message是一个消息常量,用来表示消息的类型 WPARAM wParam;//wParam 和lParam 都是32 位的附加信息,具体表示什么内容,要视消息的类型而定 LPARAM lParam; DWORD time;//time 是消息发送的时间 POINT pt;//pt 是消息发送时鼠标所在的位置。 }MSG; |
Windows 是一消息(Message)驱动式系统,Windows 消息提供了应用程序与应用程序
之间、应用程序与Windows 系统之间进行通讯的手段。应用程序要实现的功能由消息来触发,并靠对消息的响应和处理来完成。
所谓消息就是描述事件发生的信息,Windows 程序是事件驱动的,用这一方法编写程序避免了死板的操作模式,因为Windows 程序的执行顺序将取决于事件的发生顺序,具有不可预知性。
消息队列
Windows 系统中有两种消息队列
- 一种是系统消息队列
- 另一种是应用程序消息队列
计算机的所有输入设备由 Windows 监控,当一个事件发生时,Windows 先将输入的消息放入系统消息队列中,然后再将输入的消息拷贝到相应的应用程序队列中,应用程序中的消息循环从它的消息队列中检索每一个消息并发送给相应的窗口函数中。
Windows 操作系统为每个线程维持一个消息队列,当事件产生时,操作系统感知这一事件的发生,并包装成消息发送到消息队列,应用程序通过
GetMessage()
函数取得消息并存于一个消息结构体中,然后通过一个TranslateMessage()
和DispatchMessage()
解释和分发消息
1 2 3 4 5 6 |
//Windows消息循环 while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg); DispatchMessage(&msg); } |
TranslateMessage(&msg)
对于大多数消息而言不起作用,但是有些消息,比如键盘按键按下和弹起(分别对于KeyDown
和KeyUp
消息),却需要通过它解释,产生一个WM_CHAR
消息。DispatchMessage(&msg)
负责把消息分发到消息结构体中对应的窗口,交由窗口过程函数处理。GetMessage()
在取得WM_QUIT
之前的返回值都为TRUE,也就是说只有获取到WM_QUIT
消息才返回FALSE,才能跳出消息循环。
消息循环
消息循环是Windows 应用程序存在的根本,应用程序通过消息循环获取各种消息,并通过相应的窗口过程函数,对消息加以处理。
正是这个消息循环使得一个应用程序能够响应外部的各种事件,所以消息循环往往是一个Windows 应用程序的核心部分。
消息处理
取得的消息将交由窗口处理函数进行处理,对于每个窗口类Windows为我们预备了一个默认的窗口过程处理函数
DefWindowProc()
,这样做的好处是,我们可以着眼于我们感兴趣的消息,把其他不感兴趣的消息传递给默认窗口过程函数进行处理。每一个窗口类都有一个窗口过程函数,此函数是一个回调函数,它是由Windows操作系统负责调用的,而应用程序本身不能调用它。以switch语句开始,对于每条感兴趣的消息都以一个case引出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { //... switch(uMsgId) { case WM_TIMER://对定时器消息处理 //... return 0; case WM_LBUTTONDOWN://对鼠标左键单击消息的处理 //... return 0; default: return DefWindowProc(hwnd,uMsgId,wParam,lParam); } } |
对于每条已经处理过的消息都必须返回0,否则消息将不停的重试下去;对于不感兴趣的消息,交给DefWindowProc()函数进行处理,并需要返回其处理值。
MFC消息映射
在MFC 的框架结构下,“消息映射”是通过巧妙的宏定义,形成一张消息映射表格来进行的。
这样一旦消息发生,Framework 就可以根据消息映射表格来进行消息映射和命令传递。
- 首先在需要进行消息处理的类的头文件(.H)里,都会含有
DECLARE_MESSAGE_MAP()
宏, 声明该类拥有消息映射表格。- 然后在类应用程序文件(.CPP)实现这一表格
1 2 3 4 5 6 |
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass) ON_COMMAND(ID_EDIT_COPY,OnEditCopy) //... END_MESSAGE_MAP() |
BEGIN_MESSAGE_MAP
- 参数1:拥有消息表格的类
- 参数2:拥有消息表格的类的父类
ON_COMMAND
- 指定命令,消息的处理函数
END_MESSAGE_MAP()
- 结尾符号
DECLARE_MESSAGE_MAP
AFX_MSGMAP_ENTRY
- 包含了一个消息的所有相关信息
AFX_MSGMAP
- 用来得到基类的消息映射入口地址
- 得到本身的消息映射入口地址
实际上,MFC 把所有的消息一条条填入到
AFX_MSGMAP_ENTRY
结构中去,形成一个数组,该数组存放了所有的消息和与它们相关的参数。同时通过
AFX_MSGMAP
能得到该数组的首地址,同时得到基类的消息映射入口地址。当本身对该消息不响应的时候,就可以上溯到基类的消息映射表寻找对应的消息响应。
MFC 通过钩子函数_AfxCbtFilterHook()截获消息,并在此函数中把窗口过程函数设置为AfxWindProc,而原来的窗口过程函数被保存在成员变量m_pfnSuper中。
MFC消息映射的步骤
- 函数
AfxWndProc
接收Windows操作系统发送的消息。 - 函数
AfxWndProc
调用函数AfxCallWndProc
进行消息处理,这里一个进步是把对句柄的操作转换成对CWnd 对象的操作。 - 函数
AfxCallWndProc
调用CWnd 类的方法WindowProc
进行消息处理 WindowProc
调用OnWndMsg
进行正式的消息处理,即把消息派送到相关的方法中去处理- 如果
OnWndMsg
方法没有对消息进行处理的话,就调用DefWindowProc
对消息进行处理
MFC消息分类
- 命令消息(
WM_COMMAND
)- 比如菜单项的选择,工具栏按钮点击等发出该消息。所有派生自
CCmdTarget
的类都有
能力接收WM_COMMAND
消息。
- 比如菜单项的选择,工具栏按钮点击等发出该消息。所有派生自
- 标准消息(
WM_XXX
)- 比如窗口创建,窗口销毁等。所有派生自CWnd的类才有资格接收标准消息。
- 通告消息(
WM_NOTIFY
)- 这是有控件向父窗口发送的消息,标示控件本身状态的变化。比如下拉列表框选项的改变
CBN_SELCHANGE
和树形控件的TVN_SELCHANGED
消息都是通告消息。 - Window 9x 版及以后的新控件通告消息不再通过
WM_COMMAND
传送,而是通过WM_NOTIFY
传送,但是老控件的通告消息,比如CBN_SELCHANGE
还是通过WM_COMMAND
消息发送。
- 这是有控件向父窗口发送的消息,标示控件本身状态的变化。比如下拉列表框选项的改变
- 自定义消息
- 利用MFC编程,可以使用自定义消息。使用自定义消息需要遵循一定的步骤,并需要
自己编写消息响应函数
- 利用MFC编程,可以使用自定义消息。使用自定义消息需要遵循一定的步骤,并需要
MFC
自定义消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//定义消息的值 #define WM_MYMESSAGE (WM_USER + 100) //声明消息处理函数 afx_msg LRESULT OnMyMessage(WPARAM wParam,LPARAM lParam); //添加消息映射处理 BEGIN_MESSAGE_MAP(CTestDlg,CDialog) ON_MESSAGE(WM_MYMESSAGE,OnMyMessage) END_MESSAGE_MAP() //实现自定义消息函数 LRESULT CTestDlg::OnMyMessage(WPARAM wParam,LPARAM lParam) { //do something return 0; } |
自定义系统唯一消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//定义消息的值 static UINT WM_MYMESSAGE = RegisterWindowMessage("myMessage"); //声明消息处理函数 afx_msg LRESULT OnMyMessage(WPARAM wParam,LPARAM lParam); //添加消息映射处理 BEGIN_MESSAGE_MAP(CTestDlg,CDialog) ON_MESSAGE(WM_MYMESSAGE,OnMyMessage) END_MESSAGE_MAP() //实现自定义消息函数 LRESULT CTestDlg::OnMyMessage(WPARAM wParam,LPARAM lParam) { //do something return 0; } //如果需要让很多应用程序都接受这个消息 ::SendMessage(HWND_BROADCAST,WM_MYMESSAGE,NULL,NULL); |
发送自定义消息
1 2 3 |
SendMessage(WM_MYMESSAGE,NULL,NULL); PostMessage(WM_MYMESSAGE,NULL,NULL); |
引用
左值引用
- 左值持久
- 左值是变量
- 左值引用通过&获取
右值引用
- 右值短暂
- 右值不能被赋值
- 右值引用是为了支持移动操作,通过&&来获得右值引用
构造函数
默认构造函数
当没有声明构造函数或者对象在声明的时候没有任何初始化参数的时候,编译器会调用默认构造函数。
但是只要类声明了任何带参数的构造函数,编译器就不会隐式的调用默认构造函数了。
拷贝构造函数
当用一个已经初始化了的自定义对象去初始化另一个新构造的对象时,拷贝构造函数就会被调用。
- 一个对象以值传递的方式传入函数体
- 一个对象以值传递的方式从函数返回
- 一个对象需要通过另一个对象进行初始化
如果在类中没有显示的声明一个拷贝构造函数,那么编译器会形成一个默认的拷贝构造函数,这个拷贝构造函数属于浅拷贝
浅拷贝
如果没有声明一个拷贝构造函数,编译器会形成一个默认的拷贝构造函数。如果类里面有指针数据成员的情况下,在发生拷贝构造函数被调用时,它是位逐次拷贝,也就是说,用来初始化新对象的对象的指针数据成员的值,会赋值给新对象的指针数据成员。后果就是,这两个对象指向同一块内存空间,如果,用来初始化新对象的那个对象释放了这个指针指向的空间,但新对象还是指向那块空间,那新对象的这个指针数据成员就成了一个野指针了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Student { public: Student(); ~Student(); private: int num; char * name; } Student::Student() { name = new char(20); } Student::~Student() { delete name; name = nullptr; } int main() { Student stu1; Student stu2(stu1); return 0; } //没有拷贝构造函数,所以编译器会生成一个默认的拷贝构造函数,是浅拷贝 |
深拷贝
自定义拷贝构造函数,让类的对象在复制过程中,对指针指向的资源进行单独分配
移动构造函数
移动构造实现的是对象的值的真实的转移:源对象将丢失内容,目的对象将占有内容
对于那些有大量数据需要拷贝的情况,使用移动构造可以提高效率和速度
- 当使用一个临时变量对对象进行构造初始化的时候,调用移动构造函数
- 使用
std::move()
,会讲参数转变为右值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class Example { public: Example(const string & str):ptr(new string(str)) { } ~Example() {delete ptr;} Example(Example && obj):ptr(obj.ptr) { obj.ptr = nullptr; } Example & operator= (Example && obj) { delete ptr; ptr = obj.ptr; obj.ptr = nullptr; return *this; } const string & content() const {return * ptr;} Example operator+ (const Example & rhs) { return Example(content() + rhs.content()); } private: string * ptr; } int main() { Example foo("exam");//构造函数 Example bar = Example("tss");//拷贝构造函数 Example tar(move(foo));//移动构造函数 bar = bar + tar;//右值是一个临时的值//移动复制操作符 cout << bar.content() << endl; return 0; } |
进程间通信
- 管道
- 无名管道
pipe()
- 有名管道
FIFO
mkfifo()
- 无名管道
- 消息队列
- 信号量
- 共享内存
- Socket
- 支持不同主机上的两个进程间的通信
- Streams
- 支持不同主机上的两个进程间的通信
线程间通信
- 全局变量
- 使用volatile关键字,每次从内存中读取,而不是因编译器优化而从缓存区读取
- 互斥量
- 信号量
- 事件
- 临界区
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 2020_11_19_0202/17
- ♥ 2023_02_2703/06
- ♥ 2022_03_1403/17
- ♥ 2023_02_0502/05
- ♥ 2022_02_1602/16
- ♥ 2022_09_1409/26