Windows API 学习~

用api 创建一个Windows 窗口

windows窗口创建过程

Windows API 索引 - Win32 apps | Microsoft Learn

创建项目

image

配置属性,系统里面子系统选择窗口

image

  1. 注册窗口类: 首先需要调用RegisterClassEx 函数来注册一个窗口类,该函数会向操作系统注册一个新的窗口类, 并返回一个唯一的类标识符, 以供后续创建窗口时使用。在注册窗口类时需要指定窗口的基本属性, 如窗口过程、背景颜色、光标等信息。

  2. 创建窗口: 调用CreateWindowEx函数来创建一 个窗口实例。在创建窗口时需要指定窗口类名称、窗口名称、窗口样式等参数,同时可以设置窗口位置、大小、字体、菜单等属性。

  3. 显示窗口: 调用ShowWindow函数将窗口显示到屏蒂上。该函数可以设置窗口的可见性、最小化、最大化状态等。

  4. 进入消息循环: 通过调用GetMessage或者PeekMessage函数来读取并处理窗口消息队列中的消息。在消息循环中,窗口过程会接收和处理各种与窗口相关的事件,如鼠标点击、键盘输入、窗口移动、大小调整等。

创建一个WinMain.cpp,winMain函数按F1可以查看微软的官方文档,以下代码中的函数也可以F1查看官方文档

语法

int __clrcall WinMain(
  [in]           HINSTANCE hInstance,
  [in, optional] HINSTANCE hPrevInstance,
  [in]           LPSTR     lpCmdLine,
  [in]           int       nShowCmd
);

参数

[in] hInstance

类型:HINSTANCE

应用程序的当前实例的句柄。

[in, optional] hPrevInstance

类型:HINSTANCE

应用程序的上一个实例的句柄。 此参数始终 NULL。 如果需要检测另一个实例是否已存在,请使用 CreateMutex 函数创建唯一命名的互斥体。 即使互斥体已存在,CreateMutex 也会成功,但该函数将返回 ERROR_ALREADY_EXISTS。 这表示应用程序的另一个实例存在,因为它首先创建了互斥体。 但是,恶意用户可以在执行此操作之前创建此互斥体,并阻止应用程序启动。 为防止这种情况,请创建一个随机命名的互斥体并存储该名称,以便它只能由授权用户获取。 或者,可以将文件用于此目的。 若要将应用程序限制为每个用户的一个实例,请在用户的配置文件目录中创建锁定的文件。

[in] lpCmdLine

类型:LPSTR

应用程序的命令行,不包括程序名称。 若要检索整个命令行,请使用 GetCommandLine 函数。

[in] nShowCmd

类型:int

控制窗口的显示方式。 此参数可以是 ShowWindow 函数的 nCmdShow 参数中指定的任何值。

关于树视图控件

树视图控件是一种显示分层项目列表的窗口,例如文档中的标题、索引中的条目或磁盘上的文件和目录。 每个项包含一个标签和一个可选位图图像,且每个项可以有一个与之关联的子项列表。 通过单击某个项目,用户可以展开或折叠相关的子项目列表。

下图显示了一个简单的树形视图控件,其中包含一个根节点、一个展开节点和一个折叠节点。 该控件对选定项目使用一个位图,而对其他项目使用另一个位图。

screen shot showing five nodes in a hierarchy; the text of one node is selected, but nodes are not linked to each other by lines

创建树视图控件后,可以通过向控件发送消息来添加、删除、排列或以其他方式来操作项目。 每条消息都有一个或多个相应的宏,可以使用这些宏来代替显式发送消息。

显示卷路径

以下 C++ 示例演示如何显示每个卷和设备的所有路径。 对于系统中的每个卷,本示例查找卷,获取设备名称,获取该卷的所有路径,并显示路径。

#include <windows.h>
#include <stdio.h>

void DisplayVolumePaths(
        __in PWCHAR VolumeName
        )
{
    DWORD  CharCount = MAX_PATH + 1;
    PWCHAR Names     = NULL;
    PWCHAR NameIdx   = NULL;
    BOOL   Success   = FALSE;

    for (;;) 
    {
        //
        //  Allocate a buffer to hold the paths.
        Names = (PWCHAR) new BYTE [CharCount * sizeof(WCHAR)];

        if ( !Names ) 
        {
            //
            //  If memory can't be allocated, return.
            return;
        }

        //
        //  Obtain all of the paths
        //  for this volume.
        Success = GetVolumePathNamesForVolumeNameW(
            VolumeName, Names, CharCount, &CharCount
            );

        if ( Success ) 
        {
            break;
        }

        if ( GetLastError() != ERROR_MORE_DATA ) 
        {
            break;
        }

        //
        //  Try again with the
        //  new suggested size.
        delete [] Names;
        Names = NULL;
    }

    if ( Success )
    {
        //
        //  Display the various paths.
        for ( NameIdx = Names; 
              NameIdx[0] != L'\0'; 
              NameIdx += wcslen(NameIdx) + 1 ) 
        {
            wprintf(L"  %s", NameIdx);
        }
        wprintf(L"\n");
    }

    if ( Names != NULL ) 
    {
        delete [] Names;
        Names = NULL;
    }

    return;
}

void __cdecl wmain(void)
{
    DWORD  CharCount            = 0;
    WCHAR  DeviceName[MAX_PATH] = L"";
    DWORD  Error                = ERROR_SUCCESS;
    HANDLE FindHandle           = INVALID_HANDLE_VALUE;
    BOOL   Found                = FALSE;
    size_t Index                = 0;
    BOOL   Success              = FALSE;
    WCHAR  VolumeName[MAX_PATH] = L"";

    //
    //  Enumerate all volumes in the system.
    FindHandle = FindFirstVolumeW(VolumeName, ARRAYSIZE(VolumeName));

    if (FindHandle == INVALID_HANDLE_VALUE)
    {
        Error = GetLastError();
        wprintf(L"FindFirstVolumeW failed with error code %d\n", Error);
        return;
    }

    for (;;)
    {
        //
        //  Skip the \\?\ prefix and remove the trailing backslash.
        Index = wcslen(VolumeName) - 1;

        if (VolumeName[0]     != L'\\' ||
            VolumeName[1]     != L'\\' ||
            VolumeName[2]     != L'?'  ||
            VolumeName[3]     != L'\\' ||
            VolumeName[Index] != L'\\') 
        {
            Error = ERROR_BAD_PATHNAME;
            wprintf(L"FindFirstVolumeW/FindNextVolumeW returned a bad path: %s\n", VolumeName);
            break;
        }

        //
        //  QueryDosDeviceW does not allow a trailing backslash,
        //  so temporarily remove it.
        VolumeName[Index] = L'\0';

        CharCount = QueryDosDeviceW(&VolumeName[4], DeviceName, ARRAYSIZE(DeviceName)); 

        VolumeName[Index] = L'\\';

        if ( CharCount == 0 ) 
        {
            Error = GetLastError();
            wprintf(L"QueryDosDeviceW failed with error code %d\n", Error);
            break;
        }

        wprintf(L"\nFound a device:\n %s", DeviceName);
        wprintf(L"\nVolume name: %s", VolumeName);
        wprintf(L"\nPaths:");
        DisplayVolumePaths(VolumeName);

        //
        //  Move on to the next volume.
        Success = FindNextVolumeW(FindHandle, VolumeName, ARRAYSIZE(VolumeName));

        if ( !Success ) 
        {
            Error = GetLastError();

            if (Error != ERROR_NO_MORE_FILES) 
            {
                wprintf(L"FindNextVolumeW failed with error code %d\n", Error);
                break;
            }

            //
            //  Finished iterating
            //  through all the volumes.
            Error = ERROR_SUCCESS;
            break;
        }
    }

    FindVolumeClose(FindHandle);
    FindHandle = INVALID_HANDLE_VALUE;

    return;
}

下面是运行应用程序的示例输出。 对于每个卷,输出包括卷设备路径、卷 GUID 路径和驱动器号。

Found a device:
 \Device\HarddiskVolume2
Volume name: \\?\Volume{4c1b02c1-d990-11dc-99ae-806e6f6e6963}\
Paths:  C:\

Found a device:
 \Device\CdRom0
Volume name: \\?\Volume{4c1b02c4-d990-11dc-99ae-806e6f6e6963}\
Paths:  D:\

列出目录中的文件

以下示例调用 FindFirstFileFindNextFileFindClose 列出指定目录中的文件。

#include <windows.h>
#include <tchar.h> 
#include <stdio.h>
#include <strsafe.h>
#pragma comment(lib, "User32.lib")

void DisplayErrorBox(LPTSTR lpszFunction);

int _tmain(int argc, TCHAR *argv[])
{
   WIN32_FIND_DATA ffd;
   LARGE_INTEGER filesize;
   TCHAR szDir[MAX_PATH];
   size_t length_of_arg;
   HANDLE hFind = INVALID_HANDLE_VALUE;
   DWORD dwError=0;
   
   // If the directory is not specified as a command-line argument,
   // print usage.

   if(argc != 2)
   {
      _tprintf(TEXT("\nUsage: %s <directory name>\n"), argv[0]);
      return (-1);
   }

   // Check that the input path plus 3 is not longer than MAX_PATH.
   // Three characters are for the "\*" plus NULL appended below.

   StringCchLength(argv[1], MAX_PATH, &length_of_arg);

   if (length_of_arg > (MAX_PATH - 3))
   {
      _tprintf(TEXT("\nDirectory path is too long.\n"));
      return (-1);
   }

   _tprintf(TEXT("\nTarget directory is %s\n\n"), argv[1]);

   // Prepare string for use with FindFile functions.  First, copy the
   // string to a buffer, then append '\*' to the directory name.

   StringCchCopy(szDir, MAX_PATH, argv[1]);
   StringCchCat(szDir, MAX_PATH, TEXT("\\*"));

   // Find the first file in the directory.

   hFind = FindFirstFile(szDir, &ffd);

   if (INVALID_HANDLE_VALUE == hFind) 
   {
      DisplayErrorBox(TEXT("FindFirstFile"));
      return dwError;
   } 
   
   // List all the files in the directory with some info about them.

   do
   {
      if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
      {
         _tprintf(TEXT("  %s   <DIR>\n"), ffd.cFileName);
      }
      else
      {
         filesize.LowPart = ffd.nFileSizeLow;
         filesize.HighPart = ffd.nFileSizeHigh;
         _tprintf(TEXT("  %s   %ld bytes\n"), ffd.cFileName, filesize.QuadPart);
      }
   }
   while (FindNextFile(hFind, &ffd) != 0);
 
   dwError = GetLastError();
   if (dwError != ERROR_NO_MORE_FILES) 
   {
      DisplayErrorBox(TEXT("FindFirstFile"));
   }

   FindClose(hFind);
   return dwError;
}


void DisplayErrorBox(LPTSTR lpszFunction) 
{ 
    // Retrieve the system error message for the last-error code

    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError(); 

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | 
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        dw,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR) &lpMsgBuf,
        0, NULL );

    // Display the error message and clean up

    lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
        (lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)lpszFunction)+40)*sizeof(TCHAR)); 
    StringCchPrintf((LPTSTR)lpDisplayBuf, 
        LocalSize(lpDisplayBuf) / sizeof(TCHAR),
        TEXT("%s failed with error %d: %s"), 
        lpszFunction, dw, lpMsgBuf); 
    MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 

    LocalFree(lpMsgBuf);
    LocalFree(lpDisplayBuf);
}

用树视图列出文件夹代码示例:

#include<Windows.h>
#include<windowsx.h>
#include<commctrl.h>
#include <vector>
#include <string>

#pragma comment(lib, "Comctl32.lib");
#define BTN_BTN_1 1000
#define BTN_CHECHEBOX_1 2000
#define BTN_RADIO_1 3000
#define ID_EDITCHILD 4000

HWND hwndTV{};
HTREEITEM AddItemToTree(HWND hwndTV, LPTSTR lpszItem, int nLevel);

/// <summary>
/// path 路径
/// </summary>
std::vector<std::wstring> GetFileList(std::wstring path);

void InsrtTree(HTREEITEM hParent, HTREEITEM hInsertAfter,std::wstring name)
{
    TVINSERTSTRUCT trStruct;
    trStruct.hParent = hParent;
    trStruct.hInsertAfter = hInsertAfter;
    TVITEM tItem;
    WCHAR namebuf[MAX_PATH]{};
    tItem.mask = TVIF_TEXT;
    //tItem.hItem = hParent;
    tItem.pszText = (LPWSTR)name.c_str();
    tItem.cchTextMax = name.size() + 1;
    trStruct.item = tItem;

    TreeView_InsertItem(hwndTV, &trStruct);
}

LRESULT CALLBACK MainWndProc(
    HWND hwnd,        // handle to window
    UINT uMsg,        // message identifier
    WPARAM wParam,    // first message parameter
    LPARAM lParam)    // second message parameter
{

    static HWND hwndEdit;

    TCHAR lpszLatin[] = L"Lorem ipsum dolor sit amet, consectetur ";
  

    switch (uMsg)
    {
    case WM_CREATE:
        //hwndEdit = CreateWindowEx(
        //    0, L"EDIT",   // predefined class 
        //    NULL,         // no window title 
        //    WS_CHILD | WS_VISIBLE | WS_VSCROLL |
        //    ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL| WS_BORDER,
        //    150, 150, 200, 200,   // set size in WM_SIZE message 
        //    hwnd,         // parent window 
        //    (HMENU)ID_EDITCHILD,   // edit control ID 
        //    (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE),
        //    NULL);        // pointer not needed 

        //// Add text to the window. 
        //SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)lpszLatin);
        return 0;

    case WM_COMMAND:
        if (wParam == BTN_BTN_1)
        {
            TCHAR lpszLatinl[500]{};
            Edit_GetText(hwndEdit, lpszLatinl, 500);
            MessageBox(0, lpszLatinl ,L"提示", MB_OK);
        }
        else if (wParam == BTN_CHECHEBOX_1)
        {
            MessageBox(0, L"CHECKBOX 被按下", L"提示", MB_OK);
        }
        else if (wParam == BTN_RADIO_1)
        {
            MessageBox(0, L"RADIO 被按下", L"提示", MB_OK);
        }

        break;
    case WM_NOTIFY:
        switch (((LPNMHDR)lParam)->code)
        {
        case NM_RCLICK:
            MessageBox(0, L"WC_TREEVIEW 被右键", L"提示", MB_OK);
            break;
        case NM_DBLCLK:

            HTREEITEM ht = TreeView_GetSelection(hwndTV);
            WCHAR namebuf[MAX_PATH]{};
            TVITEM tItem;
            tItem.mask = TVIF_TEXT;
            tItem.pszText = namebuf;
            tItem.cchTextMax = MAX_PATH;

            HTREEITEM htup = ht;
            std::wstring fpath;

            while (true)
            {
                htup = TreeView_GetParent(hwndTV, htup);
                if (htup == NULL)
                {
                    break;
                }
                tItem.hItem = htup;
                TreeView_GetItem(hwndTV, &tItem);
                fpath = L"\\" + fpath;
                fpath = tItem.pszText + fpath + L"\\";

            }

            tItem.hItem = ht;
            TreeView_GetItem(hwndTV,&tItem );

            std::wstring path = tItem.pszText;
            std::vector<std::wstring>flist = GetFileList(fpath + path);

            for (auto& var : flist)
            {
                InsrtTree(ht, 0, var);
            }

            //MessageBox(0,tItem.pszText , L"提示", MB_OK);
            break;
        }
        break;

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    return 0;
}

HTREEITEM AddItemToTree(HWND hwndTV, LPTSTR lpszItem, int nLevel)
{
    TVITEM tvi;
    TVINSERTSTRUCT tvins;
    static HTREEITEM hPrev = (HTREEITEM)TVI_FIRST;
    static HTREEITEM hPrevRootItem = NULL;
    static HTREEITEM hPrevLev2Item = NULL;
    HTREEITEM hti;

    tvi.mask = TVIF_TEXT | TVIF_IMAGE
        | TVIF_SELECTEDIMAGE | TVIF_PARAM;

    // Set the text of the item. 
    tvi.pszText = lpszItem;
    tvi.cchTextMax = sizeof(tvi.pszText) / sizeof(tvi.pszText[0]);

    // Assume the item is not a parent item, so give it a 
    // document image. 
    //tvi.iImage = g_nDocument;
    //tvi.iSelectedImage = g_nDocument;

    // Save the heading level in the item's application-defined 
    // data area. 
    tvi.lParam = (LPARAM)nLevel;
    tvins.item = tvi;
    tvins.hInsertAfter = hPrev;

    // Set the parent item based on the specified level. 
    if (nLevel == 1)
        tvins.hParent = TVI_ROOT;
    else if (nLevel == 2)
        tvins.hParent = hPrevRootItem;
    else
        tvins.hParent = hPrevLev2Item;

    // Add the item to the tree-view control. 
    hPrev = (HTREEITEM)SendMessage(hwndTV, TVM_INSERTITEM,
        0, (LPARAM)(LPTVINSERTSTRUCT)&tvins);

    if (hPrev == NULL)
        return NULL;

    // Save the handle to the item. 
    if (nLevel == 1)
        hPrevRootItem = hPrev;
    else if (nLevel == 2)
        hPrevLev2Item = hPrev;

    // The new item is a child item. Give the parent item a 
    // closed folder bitmap to indicate it now has child items. 
    if (nLevel > 1)
    {
        hti = TreeView_GetParent(hwndTV, hPrev);
        tvi.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE;
        tvi.hItem = hti;
        //tvi.iImage = g_nClosed;
        //tvi.iSelectedImage = g_nClosed;
        TreeView_SetItem(hwndTV, &tvi);
    }

    return hPrev;
}

//获取文件列表
std::vector<std::wstring> GetVolumeList()
{
    std::vector<std::wstring> voList;

    WCHAR DeviceName[MAX_PATH]{};
    HANDLE findHandle = FindFirstVolumeW(DeviceName, MAX_PATH);

    if (findHandle == INVALID_HANDLE_VALUE)
    {

        return voList;
    }
    do
    {
        WCHAR pathName[MAX_PATH]{};
        DWORD len;
        GetVolumePathNamesForVolumeName(DeviceName, pathName, MAX_PATH, &len);
        if (len>1)
        {
            voList.push_back(pathName);
        }
    } while (FindNextVolume(findHandle, DeviceName, MAX_PATH));

    FindVolumeClose(findHandle);
    return voList;
}

/// <summary>
/// path 路径
/// </summary>
std::vector<std::wstring> GetFileList(std::wstring path)
{
    std::vector<std::wstring> voList;
    path += L"\\*";

    WIN32_FIND_DATAW ffd{};

    HANDLE fileHandle = FindFirstFile(path.c_str(),&ffd);
    if (INVALID_HANDLE_VALUE == fileHandle)
    {

        return voList;
    }
    do
    {
        voList.push_back(ffd.cFileName);

    } while (FindNextFile(fileHandle,&ffd));
    FindClose(fileHandle);
    return voList;
}

int WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nShowCmd)
{


    //std::vector<std::wstring> fList = GetFileList(L"c:\\");

    //for (auto& var : fList)
    //{
    //    MessageBox(NULL, var.c_str(), L"", MB_OK);

    //}
 

	WNDCLASSEX wndcls{};
	wndcls.cbSize = sizeof(WNDCLASSEX);
	wndcls.style = CS_SAVEBITS | CS_DROPSHADOW;
    wndcls.lpfnWndProc = MainWndProc;
    wndcls.hInstance = hInstance;

    wndcls.lpszClassName = L"chao";


	RegisterClassEx(&wndcls);

    HWND m_hwnd = CreateWindowEx(0,
        wndcls.lpszClassName,
        L"Chao",
        0,
        100, 100, 600, 800,
        NULL, NULL,
        hInstance,
        NULL);
/*
    HWND hwndButton = CreateWindow(
        L"BUTTON",  // Predefined class; Unicode assumed 
        L"OK",      // Button text 
        WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,  // Styles 
        10,         // x position 
        10,         // y position 
        100,        // Button width
        50,        // Button height
        m_hwnd,     // Parent window
        (HMENU)BTN_BTN_1,       // No menu.
        (HINSTANCE)GetWindowLongPtr(m_hwnd, GWLP_HINSTANCE),
        NULL);      // Pointer not needed.

    HWND hwndCheckbox = CreateWindow(
        L"BUTTON",  // Predefined class; Unicode assumed 
        L"CHECKBOX",      // Button text 
        WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX,  // Styles 
        150,         // x position 
        10,         // y position 
        100,        // Button width
        50,        // Button height
        m_hwnd,     // Parent window
        (HMENU)BTN_CHECHEBOX_1,       // No menu.
        (HINSTANCE)GetWindowLongPtr(m_hwnd, GWLP_HINSTANCE),
        NULL);      // Pointer not needed.

    HWND hwndRADIO = CreateWindow(
        L"BUTTON",  // Predefined class; Unicode assumed 
        L"RADIO",      // Button text 
        WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON,  // Styles 
        10,         // x position 
        150,         // y position 
        100,        // Button width
        50,        // Button height
        m_hwnd,     // Parent window
        (HMENU)BTN_RADIO_1,       // No menu.
        (HINSTANCE)GetWindowLongPtr(m_hwnd, GWLP_HINSTANCE),
        NULL);      // Pointer not needed.
*/
    InitCommonControls();

    HWND hwndTV = CreateWindowEx(0,
        WC_TREEVIEW,
        TEXT("Tree View"),
        WS_VISIBLE | WS_CHILD | WS_BORDER | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_INFOTIP,
        10,
        10,
        300,
        500,
        m_hwnd,
        0,
        hInstance,
        NULL);

    std::vector<std::wstring> voList = GetVolumeList();

    for (auto& var : voList)
    {
        AddItemToTree(hwndTV, (LPTSTR)var.c_str(), 1);

        /*MessageBox(NULL, var.c_str(), L"", MB_OK);*/

    }

    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT", 1);
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-2", 2);
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-3", 3);
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-2", 2);

    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT", 1);
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-2", 2);
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-3", 3);
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-2", 2);
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-3", 3); 
    //AddItemToTree(hwndTV, (LPTSTR)L"ROOT-3", 3);

    ShowWindow(m_hwnd,SW_NORMAL);

    BOOL bRet;
    MSG msg;

    while (GetMessage(&msg, m_hwnd, 0, 0))
    {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
    }


}

学习参考:

使用Windows API将文件显示到树形目录下 【C++ Windows API】_哔哩哔哩_bilibili

posted @   cosyQAQ  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示