Windows编程[下]
- 一、线程
- 1. 内核对象
- 2.多线程群聊服务器
- 3.多线程群聊客户端
- 4.线程同步之事件对象
- 常用函数和参数解释
- 二、进程
- 三、Qt
- 1.第一个Qt项目
- 2.Qt助手的使用
- 3.QPushButton简介
- 4.Qt对象树
- 对象树的基本概念
- 使用对象树模式的好处
- 对象树的问题
- 5.信号与槽
- 5.1 自定义信号和槽(不带参数版本)
- 5.2 自定义信号和槽(带参数版本和重载)
- 5.3 信号和槽的扩展
- 5.3.1 信号和槽的参数匹配
- 5.3.2 多对多连接
- 5.3.3 信号连接信号
- 5.3.4 使用lambda表达式
- 6.QTextEdit控件
- 信号
- 7.QMainWindow
- 7.1 添加菜单栏
- 7.2 添加工具栏
- 7.3 添加状态栏
- 7.4 中心部件、铆接部件
- 8.Qt乱码解决
- 9.Ui设计器
- 9.1 添加资源文件
- 9.2 对话框(模态、非模态)
- 9.3 QMessageBox对话框
- 9.4 基本对话框
一、线程
1. 内核对象
在 Windows 操作系统中,内核对象(Kernel Objects)是系统资源的抽象,用于管理和协调系统的底层功能。内核对象可以被应用程序、系统进程和操作系统自身使用。这些对象的创建、使用和管理通常通过 Windows API 完成。下面详细介绍内核对象及其特点:
内核对象的特点:
- 资源管理:内核对象用于管理各种系统资源,如内存、进程、线程、文件、设备等。
- 安全性:内核对象具有安全属性,允许系统定义哪些用户或进程可以访问或操作这些对象。
- 引用计数:内核对象通常具有引用计数机制,确保在不再需要时自动释放资源。
- 句柄访问:用户模式进程通过句柄(Handle)来访问内核对象,而内核模式下则直接操作内核对象。
以下是一些常见的内核对象及其用途:
- 进程(Process)和线程(Thread)对象:用于管理和调度系统中的进程和线程。
- 文件(File)对象:用于管理文件系统中的文件和目录。
- 同步对象:
- 互斥体(Mutex):用于在线程间同步访问共享资源。
- 信号量(Semaphore):用于控制同时访问资源的线程数量。
- 事件(Event):用于线程间的信号通知。
- 等待链(Waitable Timer):用于定时操作。
- I/O 完成端口(I/O Completion Port)对象:用于高效处理异步 I/O 操作。
- 设备(Device)对象:用于与硬件设备交互。
- 节(Section)对象:用于共享内存的进程间通信。
- 注册表(Registry)对象:用于管理系统配置和应用程序的设置信息。
内核对象的使用计数与生命周期:
- 所有者:内核对象的所有者是操作系统内核,而非进程。这意味着即使进程退出,内核对象也不一定会销毁。
- 使用计数:操作系统内核通过内核对象的使用计数(Reference Count)来跟踪当前有多少个进程正在使用某个内核对象。初次创建内核对象时,使用计数为 1。当一个进程获取该内核对象的访问权之后,使用计数加 1。如果内核对象的使用计数减少为 0,操作系统内核就会销毁该内核对象。这就是说,内核对象在当前进程中创建,但在当前进程退出时,内核对象有可能被另外一个进程访问。
2.多线程群聊服务器
服务器:
-
对于每一个上线的客户端,都开辟一个线程去维护。
-
将收到的消息转发给全部客户端
3.多线程群聊客户端
客户端:
- 请求连接(上线)
- 发送消息:等待用户从控制台输入,然后发送给服务端
- 等待服务端发送消息,收到数据后输出到控制台
- 等待用户关闭(下线) -> q
4.线程同步之事件对象
什么是事件对象?
事件对象就像一个小信号灯,可以用来控制多个程序(线程)之间的协调工作。想象一下,有一个红绿灯可以让不同的线程知道什么时候可以继续,什么时候需要等待。
事件对象是一种同步原语,它可以用来协调线程之间的操作。
事件对象通常有两种状态:已触发(set)和未触发(reset)。通过设置和重置事件对象的状态,我们可以控制线程的执行。
- 已触发(set):当事件对象被设置为已触发状态时,所有等待这个事件的线程都会被唤醒,继续执行。
- 未触发(reset):当事件对象被设置为未触发状态时,所有等待这个事件的线程都会被阻塞,直到事件被触发。
常用函数和参数解释
在 Windows 编程中,我们可以使用一些特定的函数来创建和操作事件对象。这些函数包括 CreateEvent
、SetEvent
、ResetEvent
和 WaitForSingleObject
。让我们逐个来看。
CreateEvent 函数
这个函数用来创建一个事件对象。
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
参数解释:
- lpEventAttributes:这个参数一般设为
NULL
,表示使用默认的安全属性。 - bManualReset:这个参数决定事件是手动重置还是自动重置。设为
TRUE
表示手动重置(需要我们自己调用ResetEvent
),设为FALSE
表示自动重置(系统会自动重置)。 - bInitialState:这个参数决定事件初始状态是触发(
TRUE
)还是未触发(FALSE
)。大多数情况下设为FALSE
。 - lpName:事件的名字,可以设为
NULL
表示匿名事件。如果需要多个程序共享同一个事件,可以给它起个名字。
SetEvent 函数
这个函数用来触发事件,相当于把信号灯变成绿色,让等待的线程继续执行。
BOOL SetEvent(
HANDLE hEvent
);
ResetEvent 函数
这个函数用来重置事件,相当于把信号灯变成红色,阻止线程继续执行。
BOOL ResetEvent(
HANDLE hEvent
);
WaitForSingleObject 函数
这个函数用来让线程等待事件的触发,相当于在线程面前设置一个信号灯,等待信号灯变绿(事件被触发)。
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
参数解释:
-
dwMilliseconds:这是等待的时间,单位是毫秒。如果设为
INFINITE
,表示一直等到事件被触发。
二、进程
三、Qt
1.第一个Qt项目
创建一个按钮并且弹出对话框。
2.Qt助手的使用
3.QPushButton简介
4.Qt对象树
在 Qt 中,对象树就像是一个家庭树(Family Tree),它表示了对象之间的父子关系。每个对象都可以有一个“父亲”(Parent)和很多“孩子”(Children)。
为什么需要对象树?
对象树帮助我们组织和管理程序中的对象。通过建立父子关系,可以方便地控制对象的生命周期,也就是对象什么时候创建和销毁。
对象树的基本概念
- 父对象(Parent):一个对象可以有一个父对象。当父对象被销毁时,它的所有子对象也会被自动销毁。
- 子对象(Children):一个对象可以有多个子对象。子对象通常依赖于父对象。
想象你有一个小玩具车(父对象),上面有几个人(子对象)。如果你把玩具车扔掉(销毁父对象),车上的人(子对象)也会跟着被扔掉(销毁)。
使用对象树模式的好处
自动内存管理:
- 当一个父对象(比如窗口)被销毁时,它的所有子对象(比如按钮、标签、文本框)也会自动被销毁。
- 这意味着你不需要手动删除每一个子对象,从而减少了编写和维护代码的工作量。
例子解释:
假设你有一个应用程序窗口(Window),窗口里面有一个标签(Label),一个文本输入框(TextEdit),和一个按钮(Button)。这些元素都是窗口的子对象。
- 传统方法:你需要手动删除标签、文本输入框和按钮,这样非常麻烦。
- 使用对象树模式:当你关闭窗口时,Qt 会自动删除窗口和所有它的子对象(标签、文本输入框和按钮)。
对象树的问题
如果button创建在栈空间,那么当代码块执行结束后,系统会自动回收,同时点击对话框[X]退出,又会通过对象树析构,造成程序崩溃。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton button;
Widget w;
button.setParent(&w);
button.setText("崩溃之源");
w.show();
return a.exec();
}
如果这样写,就不会:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
QPushButton button;
button.setParent(&w);
button.setText("崩溃之源");
w.show();
return a.exec();
}
在 C++ 中,对象的销毁顺序与它们的定义顺序相反。也就是说,先定义的对象最后被销毁,后定义的对象先被销毁。
在当前代码中,QPushButton button
的生命周期依赖于 Widget w
。当 Widget w
被定义在 button
前面时,它在 button
之后销毁,这样在 button
被销毁时,它的父对象 w
仍然存在,不会产生问题。
在 Qt 中,确保父对象在子对象之前定义是很重要的,这样可以避免在父对象销毁时子对象被二次释放的问题。
5.信号与槽
信号与槽机制是 Qt 框架中的一种独特的通信方式,用于对象之间的消息传递。想象一下,当你按下遥控器的按钮时,电视机会响应这个动作。这里,按下按钮就像是发出一个信号,而电视机的响应就像是槽函数。
**信号(Signal)😗*信号是对象发出的消息。它表示某个事件发生了。信号不做任何处理,只是发出通知。
槽(Slot):槽是一个函数,它会对信号做出反应。当信号发出时,连接到该信号的槽函数就会被调用。
举个例子:
想象你有一个按钮,当你点击这个按钮时,显示一个消息框。这就涉及到一个信号和一个槽:
- 信号:按钮被点击。
- 槽:显示消息框的函数。
使用 connect(button, &QPushButton::clicked, this, &MyWidget::showMessage)
函数,将信号与槽函数连接起来。
connect函数的参数依次是:
- 发射信号的对象(
button
) - 信号(
&QPushButton::clicked
) - 接收信号的对象(
this
) - 槽函数(
&MyWidget::showMessage
)
在父类中找到按钮支持的信号类型。
5.1 自定义信号和槽(不带参数版本)
假设我们写一个表白的信号与槽,boy类有一个表白信号,girl有一个回应的槽。
5.2 自定义信号和槽(带参数版本和重载)
这里我们love函数传递一个QString参数给女孩对象,女孩对象也使用这个传递过来的参数。
5.3 信号和槽的扩展
5.3.1 信号和槽的参数匹配
在Qt的信号和槽机制中,信号和槽的参数匹配需要遵循一定的规则:
- 参数数量:信号的参数数量可以多于槽函数的参数数量,但不能少于槽函数的参数数量。也就是说,信号的参数可以被槽函数部分接收。例如,如果一个信号有三个参数,而槽函数只有两个参数,那么信号的前两个参数会传递给槽函数,第三个参数会被忽略。
- 参数类型:信号和槽函数对应参数的类型必须兼容。这意味着信号的参数类型必须能够隐式转换为槽函数的参数类型。
5.3.2 多对多连接
Qt允许一个信号连接多个槽函数,也允许一个槽函数连接多个信号。
5.3.3 信号连接信号
在Qt中,一个信号也可以连接到另一个信号。这种机制允许我们将信号链式传递,从而简化复杂的信号处理逻辑。
5.3.4 使用lambda表达式
Qt 5引入了对C++11 lambda表达式的支持,使得我们可以使用lambda表达式作为槽函数。这特别有用,可以将不具备参数的信号连接到具备参数的槽函数,或者进行更复杂的信号处理逻辑。
6.QTextEdit控件
信号
7.QMainWindow
QMainWindow
是 Qt 框架中用于创建主窗口的类,提供了一个标准化的应用程序主窗口框架。QMainWindow
继承自 QWidget
,但添加了一些特定于主窗口的功能,如菜单栏、工具栏、状态栏和停靠窗口等。
一个典型的 QMainWindow
由以下几个主要部分组成:
- 菜单栏(Menu Bar):通常位于窗口顶部,用于组织和显示菜单项。
- 工具栏(Tool Bar):通常位于菜单栏下方,用于放置常用操作按钮。
- 状态栏(Status Bar):通常位于窗口底部,用于显示应用程序的状态信息。
- 中心窗口部件(Central Widget):主窗口的核心部分,通常用于显示主要内容。
- 停靠窗口(Dock Widgets):可以停靠在主窗口的四周,用于显示辅助工具或信息。
7.1 添加菜单栏
7.2 添加工具栏
7.3 添加状态栏
7.4 中心部件、铆接部件
8.Qt乱码解决
9.Ui设计器
9.1 添加资源文件
9.2 对话框(模态、非模态)
标准对话框可以类比为我们在日常生活中购买的智能手机中的内置应用,比如相机、短信、电话等。用户只需要调用这些内置功能即可,无需再去开发新的相机应用。Qt中的标准对话框就是这样的“内置应用”,它们已经提供了基本的功能,开发者只需调用即可。
常见的标准对话框:
- QColorDialog:选择颜色的对话框。
- QFileDialog:选择文件或目录的对话框。
- QFontDialog:选择字体的对话框。
- QInputDialog:允许用户输入一个值,并将其返回的对话框。
- QMessageBox:模态对话框,用于显示信息、询问问题等。
这些对话框几乎可以满足所有应用程序中常见的交互需求,因此没有必要在每个程序中都重新实现这些对话框。