Windows点击X关闭窗口的过程
WM_CLOSE- 这是点击“X”按钮后产生的第一个消息,是一个关闭请求
- 应用程序可以在此阶段决定是否允许关闭
- 例如,如果文档有未保存的修改,程序可以弹出一个对话框询问用户是否保存
WM_DESTROY- 当窗口确定要关闭时(通常是调用了
DestroyWindow函数),系统会发送WM_DESTROY消息 - 这个消息标志着窗口正在被销毁,是程序进行最终清理工作的信号,例如释放内存、关闭文件句柄等
- 当窗口确定要关闭时(通常是调用了
WM_QUIT- 在
WM_DESTROY消息的处理中,程序通常会调用PostQuitMessage函数,该函数会向应用程序的消息队列中放入一个WM_QUIT消息 - 这个消息的使命很特殊:它并不会传递给窗口过程,而是被消息循环中的
GetMessage或PeekMessage函数捕获 - 一旦
GetMessage取到WM_QUIT,它就会返回0,从而导致消息循环结束,最终使得程序的主函数(如WinMain)退出,进程终止
- 在
基类指针转派生类
dynamic_cast
- 特征
- 用于多态类型(有虚函数),进行安全的向下转型
- 安全性
- 失败返回
nullptr或抛异常
- 失败返回
- 时机
- 运行时
- 场景
- 当不确定基类指针是否指向目标派生类对象时
- 注意
- 基类必须至少有一个虚函数(即必须是多态类型)
static_cast
- 特征
- 用于相关类型间的转换,不进行运行时类型检查
- 安全性
- 依赖程序员保证
- 时机
- 编译时
- 场景
- 当确定基类指针指向的就是目标派生类对象时
- 注意
- 仅当百分之百确定基类指针指向的就是目标派生类对象时使用
- 一旦判断失误,使用
static_cast进行错误的向下转型会导致严重问题
C风格强制转换
-
特征
(Derived*)basePtr,强大但危险
-
安全性
- 无类型检查
-
时机
- 编译时
-
场景
- 不推荐在
C++中使用,可能存在风险
- 不推荐在
reinterpret_cast
-
特征
- 低级别的比特位重新解释
-
安全性
- 非常危险
-
时机
- 编译时
-
场景
- 几乎不用于类层次结构的向下转型
-
注意
- 这个操作是“重新解释”底层的比特模式,完全不考虑类的继承关系和内存布局
Windows透明窗口
分层窗口 (WS_EX_LAYERED)
- 概述
- 最经典和直接的透明窗口实现方式,主要通过设置窗口的扩展样式
WS_EX_LAYERED来实现
- 最经典和直接的透明窗口实现方式,主要通过设置窗口的扩展样式
SetLayeredWindowAttributes:此函数允许你通过两种方式实现透明:- 整体透明度 (
Alpha):设置LWA_ALPHA标志和一个0(全透)到255(不透明)之间的Alpha值,使整个窗口(包括其上的控件)呈现统一的半透明效果 - 颜色键透明 (
Color Keying):设置LWA_COLORKEY标志并指定一种颜色(如RGB(0, 255, 0)),则窗口中所有为该颜色的像素将变为完全透明,常用于创建异形窗口
- 整体透明度 (
UpdateLayeredWindow:这个方法功能更强大- 它允许你提供一个包含
Alpha通道的位图(如32位的ARGB格式),从而实现每像素级别的透明度控制(类似PNG图片的透明效果) - 这对于创建非矩形复杂透明窗口非常有用
- 需要注意的是,使用此方法后,窗口将不再接收典型的
WM_PAINT消息,你需要自行管理窗口内容的绘制和更新
- 它允许你提供一个包含
DWM 相关 API
- 概述
Windows Vista及之后系统引入了桌面窗口管理器 (DWM),它利用GPU进行桌面合成,也提供了实现透明效果的新方式
DwmEnableBlurBehindWindow- 这个
API最初旨在为窗口启用类似Windows Aero的毛玻璃模糊背景效果 - 但从
Windows 8开始,它常被用来实现简单的背景透明或半透明效果
- 这个
无重定向表面窗口
- 概述
- 这是
Windows 8及以上系统支持的高性能方案,被WPF、WinUI等现代UI框架广泛采用
- 这是
- 核心是在创建窗口时使用
WS_EX_NOREDIRECTIONBITMAP扩展样式- 这个样式告诉系统不要为该窗口创建默认的离屏重定向表面
- 没有了这个表面,
DWM就不知道如何合成窗口内容,因此需要开发者自己利用DirectX(如Direct3D 11)和DirectComposition API来创建交换链(Swap Chain),并将渲染好的图像内容(通常是带有Alpha通道的纹理)通过一个"Visual"树提交给DWM进行最终合成
如何自己实现多态
概述
- 关键在于,我们将手动管理一个函数指针表来替代编译器生成的虚函数表
实现
|
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
#include <iostream> #include <unordered_map> // 1. 定义函数指针类型,所有"虚函数"必须符合此签名 typedef void (*DrawFunc)(void* this_ptr); // 2. 基类 Shape struct Shape { // 手动模拟虚函数表指针 std::unordered_map<std::string, DrawFunc>* vtable; Shape() { // 基类构造函数中初始化vtable,注册基类的"虚函数" vtable = new std::unordered_map<std::string, DrawFunc>(); (*vtable)["draw"] = nullptr; // 纯虚函数,也可以设置一个默认实现 (*vtable)["area"] = nullptr; } virtual ~Shape() { delete vtable; } // 提供统一的接口,模拟动态绑定 void draw() { auto func = (*vtable)["draw"]; if (func) { func(this); // 将当前对象的指针(this)传递给函数 } else { std::cout << "Error: draw() not implemented!" << std::endl; } } double area() { auto func = reinterpret_cast<double(*)(void*)>((*vtable)["area"]); if (func) { return func(this); } return 0.0; } }; // 3. 派生类 Circle struct Circle : public Shape { double radius; Circle(double r) : radius(r) { // 重写vtable,注册派生类的具体实现 delete vtable; vtable = new std::unordered_map<std::string, DrawFunc>(); (*vtable)["draw"] = reinterpret_cast<DrawFunc>(&Circle::draw_impl); (*vtable)["area"] = reinterpret_cast<DrawFunc>(&Circle::area_impl); } // 具体的实现函数 static void draw_impl(void* this_ptr) { Circle* self = static_cast<Circle*>(this_ptr); std::cout << "Drawing a Circle with radius " << self->radius << std::endl; } static double area_impl(void* this_ptr) { Circle* self = static_cast<Circle*>(this_ptr); return 3.14159 * self->radius * self->radius; } }; // 4. 派生类 Rectangle struct Rectangle : public Shape { double width, height; Rectangle(double w, double h) : width(w), height(h) { delete vtable; vtable = new std::unordered_map<std::string, DrawFunc>(); (*vtable)["draw"] = reinterpret_cast<DrawFunc>(&Rectangle::draw_impl); (*vtable)["area"] = reinterpret_cast<DrawFunc>(&Rectangle::area_impl); } static void draw_impl(void* this_ptr) { Rectangle* self = static_cast<Rectangle*>(this_ptr); std::cout << "Drawing a Rectangle " << self->width << "x" << self->height << std::endl; } static double area_impl(void* this_ptr) { Rectangle* self = static_cast<Rectangle*>(this_ptr); return self->width * self->height; } }; // 5. 使用示例 int main() { Shape* shapes[2]; shapes[0] = new Circle(5.0); shapes[1] = new Rectangle(3.0, 4.0); for (int i = 0; i < 2; ++i) { shapes[i]->draw(); // 多态调用 std::cout << "Area: " << shapes[i]->area() << std::endl; std::cout << std::endl; } delete shapes[0]; delete shapes[1]; return 0; } |
关键点
- 函数指针管理:
- 我们使用
std::unordered_map来模拟虚函数表,将函数名(如 "draw")映射到具体的静态成员函数地址 - 这种字符串查找的方式虽然比
C++原生的直接偏移访问要慢,但更直观
- 我们使用
- 对象身份传递:
- 每个静态成员函数(如
Circle::draw_impl)都接受一个void* this_ptr参数,它实际上指向调用该函数的对象(Circle或Rectangle实例) - 在函数内部,需要将
void*转换回正确的类型[CITATION]
- 每个静态成员函数(如
- “重写”机制:
- 派生类的构造函数会“重写”从基类继承来的
vtable,将其中的函数指针替换为自己实现的函数地址,从而实现多态行为[CITATION]
- 派生类的构造函数会“重写”从基类继承来的
自己实现vector
概述
- 核心在于模拟一个动态数组,并自动处理其生命周期的各个环节
实现
|
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
template <typename T> class MyVector { public: // 类型别名 using iterator = T*; using const_iterator = const T*; // 构造函数 MyVector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {} // 拷贝构造函数(深拷贝) MyVector(const MyVector& other) : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { reserve(other.capacity()); for (const auto& e : other) { push_back(e); } } // 赋值运算符(拷贝交换技法) MyVector& operator=(MyVector other) { swap(other); return *this; } // 析构函数 ~MyVector() { delete[] _start; _start = _finish = _end_of_storage = nullptr; } // 交换 void swap(MyVector& other) noexcept { std::swap(_start, other._start); std::swap(_finish, other._finish); std::swap(_end_of_storage, other._end_of_storage); } // 容量操作 size_t size() const { return _finish - _start; } size_t capacity() const { return _end_of_storage - _start; } bool empty() const { return _start == _finish; } void reserve(size_t new_cap) { if (new_cap <= capacity()) return; T* new_start = new T[new_cap]; size_t old_size = size(); // 拷贝元素(深拷贝) for (size_t i = 0; i < old_size; ++i) { new_start[i] = _start[i]; } delete[] _start; _start = new_start; _finish = _start + old_size; _end_of_storage = _start + new_cap; } // 元素访问 T& operator[](size_t index) { return _start[index]; } const T& operator[](size_t index) const { return _start[index]; } // 迭代器 iterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin() const { return _start; } const_iterator end() const { return _finish; } // 修改操作 void push_back(const T& value) { if (_finish == _end_of_storage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish = value; ++_finish; } void pop_back() { if (!empty()) { --_finish; } } private: T* _start; T* _finish; T* _end_of_storage; }; |
关键点
- 内存管理与布局
- 使用三个指针(
_start,_finish,_end_of_storage)分别指向内存起始点、最后一个元素的下一个位置、已分配内存的末尾 - 通过指针差值计算
size()和capacity()
- 使用三个指针(
- 构造与析构
- 实现多种构造函数(默认、数量-值、迭代器范围、拷贝构造等),注意初始化列表
- 析构函数必须正确释放内存
- 核心操作:
push_back/insert/erase- 动态扩容是重点,通常采用成倍增长策略(如原容量为
0时扩至4,否则扩至2倍) - 在中间位置
insert或erase元素需要移动后续所有元素
- 动态扩容是重点,通常采用成倍增长策略(如原容量为
- 深浅拷贝问题
- 拷贝构造函数和赋值运算符重载必须实现深拷贝
- 避免使用
memcpy等浅拷贝方式,应为每个元素调用其拷贝构造函数或赋值运算符
- 迭代器失效
- 任何可能引起内存重新分配(如
insert、push_back导致扩容)或元素移动(如erase)的操作都会使指向该内存区的迭代器、指针和引用失效
- 任何可能引起内存重新分配(如
- 元素访问
- 重载
operator[]以实现数组式访问。可考虑添加边界检查 - 提供
begin()和end()函数以支持范围循环
- 重载
其他
vector- 进程间通信
- vector删掉元素,背后的内存变化
- std::move
- vector迭代器失效问题
- 带权图的最短路径问题
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 2020_05_11_0105/14
- ♥ 2022_03_0103/01
- ♥ 2025_03_1103/11
- ♥ 2023_02_2002/20
- ♥ 2022_02_2602/26
- ♥ 2020_11_0902/16
热评文章
- 2022_03_09 0
- 2025_03_18 0
- 2020_04_28 0
- 2022_03_14 0
- 2020_11_23 0
- 2025_03_25 0