【C/C++】回调函数详解&注册窗口类&LRESULT & CALLBACK详解以及游戏中的应用

简介: 【C/C++】回调函数详解&注册窗口类&LRESULT & CALLBACK详解以及游戏中的应用


#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);
int WINAPI WinMain(
  HINSTANCE hInstance,      // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,          // command line
  int nCmdShow              // show state
)
{
  WNDCLASS wndcls;
  wndcls.cbClsExtra=0;
  wndcls.cbWndExtra=0;
  wndcls.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
  wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
  wndcls.hIcon=LoadIcon(NULL,IDI_ERROR);
  wndcls.hInstance=hInstance;
  wndcls.lpfnWndProc=WinSunProc;
  wndcls.lpszClassName="lqkwudizhiwang";
  wndcls.lpszMenuName=NULL;
  wndcls.style=CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&wndcls);
  HWND hwnd;
  hwnd=CreateWindow("lqkwudizhiwang","lqk wudi",WS_OVERLAPPEDWINDOW,
    100,100,600,400,NULL,NULL,hInstance,NULL);
  ShowWindow(hwnd,SW_SHOWNORMAL);
  UpdateWindow(hwnd);
  MSG msg;
  while(GetMessage(&msg,NULL,0,0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
    switch (uMsg)
  {
  case WM_CHAR:
    char szChar[20];
    sprintf(szChar, "bieluananle %d", wParam);
    if (wParam == 49) {
      MessageBox(hwnd, "我们无敌?", "李奇坤的标题", MB_OK);
    }
    else{
      MessageBox(hwnd, szChar, "这是框框", 0);
    }
    break;
  case WM_LBUTTONDOWN:
    MessageBox(hwnd,"mouse clicked","message",0);
    HDC hdc;
    hdc=GetDC(hwnd);
    TextOut(hdc,0,50,"wode程序员之家",strlen("wode程序员之家"));
    //ReleaseDC(hwnd,hdc);
    break;
  case WM_PAINT:
    HDC hDC;
    PAINTSTRUCT ps;
    hDC=BeginPaint(hwnd,&ps);
    TextOut(hDC,0,0,"lqkkkkkkk新的",strlen("lqkkkkkk新的"));
    EndPaint(hwnd,&ps);
    break;
  case WM_CLOSE:
    if(IDYES==MessageBox(hwnd,"nitama是否真的结束?","message",MB_YESNO))
    {
      DestroyWindow(hwnd);
    }
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
  }
  return 0;
}

函数指针(Function Pointer)

  1. 指针变量存储的内容是一个地址信息,而指针的类型确定了指向内容的类型
  2. 指针指向函数:
//定义函数 cm_to_inches
double cm_to_inches(double cm) {
    return cm / 2.54;
}
//将函数变量 cm_to_inches 赋值给 func1 变量
double (*func1)(double) = cm_to_inches;
//输出结果
printf("%fn", func1(15.0));
  1. 上面的代码中 func1 就是一个函数指针cm_to_inches 这个函数的声明和函数变量 *func1一致的,所以二者是可以赋值的,就像两个整形赋值一样,可以类比下面的代码:
//定义整形 int
int cm_to_inches = 15;
//将整形变量 cm_to_inches 赋值给 func1 变量
int func1 = cm_to_inches;
//输出结果
printf("%dn", func1);
  1. double (*func1)(double) = cm_to_inches; 等价于
typedef double (*FUNC1)(double);
//定义函数 cm_to_inches
double cm_to_inches(double cm) {
    return cm / 2.54;
}
//将函数变量 cm_to_inches 赋值给 func1 变量
FUNC1 func1 = &cm_to_inches;
//输出结果
printf("%fn", func1(15.0));
  1. 如果你对 typedef 不是很熟悉,请立刻回去翻一翻语法书,并感受下面的三条语句:
typedef int myinteger;
typedef char *mystring;
typedef void (*myfunc)();
等价于:
myinteger i;   // is equivalent to    int i;
mystring s;    // is the same as      char *s;
myfunc f;      // compile equally as  void (*f)();
  1. 回调函数(Callback Function)
    如果说 函数指针 是语言相关的话**,回调函数 就是一个语言无关的概念了。回调函数这个名字起的很好,可以明显感受到它有点 “返过来调用的意思”,它还有一个被大众熟悉的称号:“好莱坞法则”。** don’t call us, we’ll call you.

  2. 其实回调函数以及不是单纯的手段了,它已经上升到了一种架构的层次,这个回调手法其实被多种设计模式所使用,特别在异步编程中,函数本身是一阶公民的语言更是如此。JavaScript 就是重灾区,甚至产生了 “回调地狱” 这种神奇的 “意大利恶魔” !

回调函数===窗口过程函数

  1. 回调函数首先是一个你需要自己实现内部逻辑的一个 函数,函数内部可以处理不同状态下的多种逻辑策略,最后将函数的调用权交给第三方(操作系统、程序插件等等),当第三方检测到某些状态发生的时候,会通过执行该函数通知你,这个通知的过程叫做 回调
  2. 第一种在程序中用 轮询 来实现,第二种程序中用 回调 来实现。
  3. 回调是替代轮询的一种策略方法。之所以叫做回调函数,是因为回调策略一般和函数本身是绑定关系,而C语言中,函数指针就是实现回调策略的一种技巧,这种技巧常被称为 回调函数。
  4. 在 Windows 编程中,操作系统通过 回调函数 告诉你发生了什么事件,例如鼠标移动、键盘响应、窗口最大化、程序退出、计算机休眠等等,你只需要定义一个回调函数,并将这个回调函数的指针交给操作系统即可,
  5. 按照这个回调函数的功能,该函数也被称为 窗口过程函数,表示窗口在运行过程中 Windows 不断调用的函数。

注册窗口类

  1. 每次注册窗口类都需要先填充一个叫做 WNDCLASSEX 的结构体
  2. 下面是填充窗口类的代码:
// Register the window class.
const wchar_t CLASS_NAME[]  = L"Sample Window Class";
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = CLASS_NAME;
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

WNDCLASSEX 成员比较多,这里简单的做一下介绍,具体信息可以查看相关 MSDN 文档。

cbSize 用来指定结构体的大小,直接使用 sizeof(WNDCLASSEX) 赋值。

style 用来指定窗口类的样式,具体可以查看这篇文章 Class Styles 了解,这里使用 CS_HREDRAW | CS_VREDRAW 组合样式,代表当窗口改变大小时进行重绘操作。

lpfnWndProc 用来指定 窗口过程函数 指针。该函数定义了窗口大多数的行为,具体可以查看 WindowProc。

cbClsExtra 用来定义窗口类结果体的扩展数据大小,一般填充0。

cbWndExtra 用来定义窗口实例的扩展数据大小,一般填充0。

hInstance 代表应用程序的实例句柄。该值就是 WinMain 函数 的 hInstance 参数。

hIcon 代表窗口类的图标句柄,这里使用默认的应用程序图标。

hCursor 代表窗口类的光标句柄,这里使用默认的箭头图标。

hbrBackground 代表窗口类背景颜色的画刷句柄,这里使用纯色的白色画刷。

lpszMenuName 代表窗口类的菜单句柄,这里没有菜单,填 NULL。

lpszClassName 是一个字符串,用来标识一个窗口类。

hIconSm 代表窗口类的小图标句柄,这里和 hIcon 指定相同的图标。

窗口类的名称(lpszClassName)在进程内必须唯一不可以重名。需要注意 Windows 标准控件一样具有类名,如果你是用了这些控件,请避免与其重名,否则会导致窗口类注册失败的情况。

上述结构体中,主要的成员其实只有四个:cbSize、lpfnWndProc、hInstance 和 lpszClassName,其它的值都可以临时设置为 0。

填充 WNDCLASSEX 结构体后,需要将其注册通知操作系统,具体使用下面的函数:

ATOM WINAPI RegisterClassEx(
  _In_ const WNDCLASSEX *lpwcx
);

函数接收一个窗口类的指针,如果成功会返回一个窗口类的句柄,如果失败则会返回0。

LRESULT & CALLBACK详解

  1. LRESULT 是一个整形变量,应用程序在执行完窗口过程函数后通过该值将结果返回给 Windows。这个值包含了应用程序对具体消息的处理结果,不同的消息该值可能不同。
  2. CALLBACK函数调用约定。窗口过程函数本质上是一个回调函数调用者是操作系统。一个典型的窗口过程函数内部是一个巨大的选择/分支语句根据不同的消息类型执行不同代码逻辑
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  1. 对于 WM_SIZE 消息处理可以这样:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_SIZE:
        {
            int width = LOWORD(lParam);  // Macro to get the low-order word.
            int height = HIWORD(lParam); // Macro to get the high-order word.
            // Respond to the message:
            OnSize(hwnd, (UINT)wParam, width, height);
        }
        break;
    }
}
void OnSize(HWND hwnd, UINT flag, int width, int height)
{
    // Handle resizing
}

消息循环

  1. 常见的游戏循环逻辑如下:
MSG msg = {0};
while (msg.message != WM_QUIT)
{
    if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
    {
        processInput();
        update();
        render();
    }
}

PostMessage 和 SendMessage

  1. 消息会保存到消息队列中,但是有的时候,操作系统会跳过队列,直接将消息传递到窗口过程函数中。
  2. Posting a message投递一个消息的含义是将消息放到队列中,然后应用程序会在消息循环中调用 GetMessage 和 DispathMessage 函数获取分发消息。
  3. Sending a message。发送一消息的含义是跳过消息队列,操作系统直接将其传递到窗口过程函数。
  4. 前者对应 API 中的 PostMessage 函数,该函数调用后会立即返回。通过调用它可以确保你将消息投放到消息队列,但是无法保证该消息响应(执行)的时间,可以将其看做是异步的。
  5. 后者对应 API 中的 SendMessage,该函数调用后并不会插入队列而是直接传递到窗口过程函数进行处理执行,直到消息处理结束返回,可以将其看做同步
  6. 一般在使用中 SendMessage 会导致线程堵塞,所以在处理耗时的任务时不推荐使用,会导致界面假死,常用的场景是一些同步通知且处理迅速的场景。如果不是很在意消息响应的时间和处理顺序,推荐始终用 PostMessage 替代 SendMessage。

GetMessage 和 PeekMessage

  1. 前面讲到 GetMessage堵塞执行直到消息队列中有新的消息插入普通的应用程序使用没有任何问题,但是如果是游戏应用就会存在游戏逻辑不能及时更新的情况。
  2. 一般游戏中都会存在游戏循环,其会一直调用游戏的处理逻辑每一帧都会调用,而大多数的游戏循环都是和消息循环合并到一起
  3. 如果在游戏循环中调用 GetMessage 的时候正好消息队列为空就会导致下面的游戏逻辑不能及时执行。而游戏程序恰好对实时性要求极高,这就会造成游戏运行时期画面卡顿的现象。
  4. 既然如此,是否可以不执行 GetMessage 分发消息,直接抛弃消息循环?显然不可行,这会导致键盘鼠标不能及时响应,消息队列中消息积累很多确无法及时处理,整个应用处于卡顿假死状态。
  5. 为了解决这个问题游戏程序中一般使用 PeekMessage 函数替代 GetMessage 函数,二者的功能几乎一致,唯一的差别是 PeekMessage 不管消息队列中有没有消息都会立刻返回,也就解决了刚刚提到的更新不及时和不更新卡顿假死问题。
  6. PeekMessage 函数和 GetMessage 函数的唯一差别是多了一个控制消息处理方式的参数 wRemoveMsg:
BOOL WINAPI PeekMessage(
  _Out_    LPMSG lpMsg,
  _In_opt_ HWND  hWnd,
  _In_     UINT  wMsgFilterMin,
  _In_     UINT  wMsgFilterMax,
  _In_     UINT  wRemoveMsg
);
  1. wRemoveMsg 有三种类型:
    PM_NOREMOVE,该值会导致 PeekMessage 获取消息后不会将该消息从消息队列中移除。
    PM_REMOVE,该值会导致调用 PeekMessage 后将消息从消息队列中移除。
    PM_NOYIELD,该值使系统不释放等待调用程序空闲的线程。可以和前两个值组合使用。

窗口绘制消息

  1. 窗口第一次显示的时候,客户区必须被绘制。因此当应用程序被显示的时候,你至少会收到一次 WM_PAINT 消息。
  2. 完成客户区的绘制工作,清除更新区域,这会告诉操作系统在发生某些变化之前不需要再次发送 WM_PAINT 消息了。
  3. 现在假设用户移动窗口遮挡了你程序的一部分。当遮挡部分再次可见的时候,这部分区域会加入到更新区域,并通过 WM_PAINT 消息通知你的程序。
  4. 用户在伸缩窗口的时候也会触发窗口重绘。如下图所示,用户向右拉伸窗口,这个右侧新的扩展区域也会加入到更新区域中
  5. 案例中的代码逻辑非常简单,它只是使用纯色填充整个更新客户区域,但是用来说明问题足够了。
switch(uMsg)
{
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc=BeginPaint(hwnd,&ps);
        // All painting occurs here, between BeginPaint and EndPaint.
        FillRect(hdc,&ps.rcPaint,(HBRUSH)(COLOR_WINDOW+1));
        EndPaint(hwnd,&ps);
    }
    return 0;
}
  1. 调用 BeginPaint 函数开始绘制操作。这个函数会将重绘信息填充到 PAINTSTRUCT 结构体中,结构体中的 rcPaint 成员就是当前需要重绘的区域。这个更新区域是相对于客户区来定义的:

更新区域

在应用程序的重绘代码中,有两个常见的策略:

  1. 一种策略是绘制整个客户区,不管操作系统传过来的更新区域的大小。任何在更新区域之外的内容都会被裁剪掉,也就是说操作系统会忽略它们。
  2. 另一种是只绘更新区域的内容。
  3. 如果选择第一种策略,代码会很简单,反之选择第二种会让程序的效率更高,对于复杂的绘制逻辑优化效果会非常明显。
  4. 下面的代码会使用单一的颜色填充整个更新区域,使用的颜色是系统默认的窗口背景颜色(COLOR_WINDOW)。实际的颜色依赖于当前用户的配色方案。
FillRect(hdc,&ps.rcPaint,(HBRUSH)(COLOR_WINDOW+1));
  1. FillRect 函数的内部细节对于例子来说不是很重要,但是该函数的第二个参数就是要填充的矩形坐标。在代码中,我们传入了整个更新区域。在窗口第一次收到 WM_PAINT 消息的时候,整个客户区都需要被重绘,所以 rcPaint 将包含整个客户区,而随后的 WM_PAINT 消息,rcPaint 参数内部包含的区域可能会小一些。
  2. FillRect 函数是图形设备接口(GDI)的一部分,这套接口已经非常古老,在 Windows 7 以后的系统,微软推出了一个新的 2D 图形引擎,名字叫做 Direct2D。该引擎支持硬件加速等高性能的图形操作
  3. 在绘制结束后,需要调用 EndPaint 函数。该函数会清除更新区域,并向 Windows 发送信号,通知它程序已经完成了窗口的绘制,在下次发送变换之前无需再次发送 WM_PAINT 消息。

窗口关闭消息

  1. 用户可以随时点击右上角的关闭按钮或者使用键盘上的 ALT+F4 组合键关闭一个应用程序,这两种方式都会触发 WM_CLOSE 消息
  2. WM_CLOSE 消息可以在用户关闭窗口之前给出一个友好的提示信息。如果你确认想要关闭窗口,则可以直接调用DestoryWindows 函数,否则,只需要简单的返回0即可,操作系统会忽略这条消息而不会关闭销毁窗口。
  3. WM_CLOSE 消息:
case WM_CLOSE:
    if (MessageBox(hwnd, L"Really quit?", L"My application", MB_OKCANCEL) == IDOK)
    {
        DestroyWindow(hwnd);
    }
    // Else: User canceled. Do nothing.
    return 0;
  1. 代码中,MessageBox 函数会展示一个包含确定和取消按钮的模态对话框,如果用户点击确定,程序就会调用 DestoryWindows 销毁窗口。如果用户点击取消,则会跳过 DestoryWindows,不会做任何改变。任何情况下,返回0 代表着你已经处理了该消息。
  2. 如果你想直接关闭窗口而不显示任何提示信息,你只要简单的调用 DestoryWindows 即可
  3. 当一个窗口销毁之后会收到 WM_DESTORY 消息。这个条消息是在窗口从屏幕中移除之后,真正销毁窗口之前发送的。
  4. 在你的主应用程序中,典型的响应 WM_DESTORY 消息的代码是调用 PostQuitMessage 函数
case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  1. PostQuitMessage 函数内部会将 WM_QUIT 消息放到消息队列,在消息循环中读取到 WM_QUIT 消息,会直接退出消息循环,关闭程序。

应用程序的状态管理

  1. 窗口过程是一个函数,该函数包含了每条消息对应的业务逻辑。它本身是无状态的。然而有些时候你需要跟踪应用程序中每次函数调用的状态信息
  2. 最简单的方法就是将所有东西都放到一个全局变量中,对于简单的程序来说这种方法足够了,许多 SDK 的案例都是用这种方法
  3. 对于复杂程序来说,这种方法会导致全局变量的骤增
  4. 你的应用程序可能不止一个窗口每个窗口都有自己的窗口过程区分哪个窗口使用哪个全局变量有时候并不是一个简单的事情,过多相似属性的全局变量会让程序可读性非常差,书写过程中非常容易造成错误。
  5. CreateWindowEx 函数提供一种方式可以将任何一种数据结构以指针的方式传递给窗口。当这个函数被调用的时候,它会向窗口过程发送下面两条消息WM_NCCREATE WM_CREATE
    消息的顺序和列表中的一致,CreateWindowEx 函数不仅仅发送这两条消息,但是其它的消息暂时被忽略。
  6. WM_NCCREATE 和 WM_CREATE 消息会在窗口显示之前发送初始化 UI 的逻辑可以放到二者的消息处理函数中。例如,可以在处理函数中处理窗口布局初始化的代码。
  7. CreateWindowEx 函数最后一个参数是一个 void* 类型的指针变量。你可以通过该参数传递任何你想传递的值。当窗口过程在处理 WM_NCCREATE 和 WM_CREATE 消息的时候,它能从消息的附加数据中拿到该值
  8. 展示一下这个功能,首先你需要定义一个结构体保存状态信息。
// Define a structure to hold some state information.
struct StateInfo {
    // ... (struct members not shown)
};
当你调用 CreateWindowEx 函数的时候,将这个结构体的指针传入。
StateInfo *pState = new (std::nothrow) StateInfo;
if (pState == NULL)
{
    return 0;
}
// Initialize the structure members (not shown).
HWND hwnd = CreateWindowEx(
    0,                              // Optional window styles.
    CLASS_NAME,                     // Window class
    L"Learn to Program Windows",    // Window text
    WS_OVERLAPPEDWINDOW,            // Window style
    // Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL,       // Parent window
    NULL,       // Menu
    hInstance,  // Instance handle
    pState      // Additional application data
);

虚函数

BaseWindow 类中有一个纯虚函数,用来实现自定义的窗口过程函数。例如:

LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(m_hwnd, &ps);
            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
            EndPaint(m_hwnd, &ps);
        }
        return 0;
    default:
        return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
    }
    return TRUE;
}
  1. 上面虚函数内部的第一个参数不是窗口句柄(HWND),原因是窗口句柄已经成为了 MainWindow 的成员变量(m_hwnd),并不需要传递就可以直接在函数中获取到
  2. 很多 Windows 程序框架都是使用类似的方法,如 MFC、ATL等等,当然它们是比较完善通用的框架,所以代码要比上边展示的要复杂很多。

代码

#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);
int WINAPI WinMain(
  HINSTANCE hInstance,      // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,          // command line
  int nCmdShow              // show state
)
{
  WNDCLASS wndcls;
  wndcls.cbClsExtra=0;
  wndcls.cbWndExtra=0;
  wndcls.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);
  wndcls.hCursor=LoadCursor(NULL,IDC_CROSS);
  wndcls.hIcon=LoadIcon(NULL,IDI_ERROR);
  wndcls.hInstance=hInstance;
  wndcls.lpfnWndProc=WinSunProc;
  wndcls.lpszClassName="sunxin2006";
  wndcls.lpszMenuName=NULL;
  wndcls.style=CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&wndcls);
  HWND hwnd;
  hwnd=CreateWindow("sunxin2006","http://www.sunxin.org",WS_OVERLAPPEDWINDOW,
    0,0,600,400,NULL,NULL,hInstance,NULL);
  ShowWindow(hwnd,SW_SHOWNORMAL);
  UpdateWindow(hwnd);
  MSG msg;
  while(GetMessage(&msg,NULL,0,0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
LRESULT CALLBACK WinSunProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
  switch(uMsg)
  {
  case WM_CHAR:
    char szChar[20];
    sprintf(szChar,"char code is %d",wParam);
    MessageBox(hwnd,szChar,"char",0);
    break;
  case WM_LBUTTONDOWN:
    MessageBox(hwnd,"mouse clicked","message",0);
    HDC hdc;
    hdc=GetDC(hwnd);
    TextOut(hdc,0,50,"程序员之家",strlen("程序员之家"));
    //ReleaseDC(hwnd,hdc);
    break;
  case WM_PAINT:
    HDC hDC;
    PAINTSTRUCT ps;
    hDC=BeginPaint(hwnd,&ps);
    TextOut(hDC,0,0,"http://www.sunxin.org",strlen("http://www.sunxin.org"));
    EndPaint(hwnd,&ps);
    break;
  case WM_CLOSE:
    if(IDYES==MessageBox(hwnd,"是否真的结束?","message",MB_YESNO))
    {
      DestroyWindow(hwnd);
    }
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hwnd,uMsg,wParam,lParam);
  }
  return 0;
}

🍃博主昵称:一拳必胜客

🌸博主寄语:欢迎点赞收藏关注哦,一起成为朋友一起成长;

特别鸣谢:木芯工作室 、Ivan from Russia


相关文章
|
3月前
|
Ubuntu API C++
C++标准库、Windows API及Ubuntu API的综合应用
总之,C++标准库、Windows API和Ubuntu API的综合应用是一项挑战性较大的任务,需要开发者具备跨平台编程的深入知识和丰富经验。通过合理的架构设计和有效的工具选择,可以在不同的操作系统平台上高效地开发和部署应用程序。
187 11
|
10月前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
300 15
|
9月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
386 12
|
7月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
207 0
|
7月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
330 0
|
10月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
200 16
|
10月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
10月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
10月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
571 6
|
10月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!