参考链接:https://github.com/guyaqi/backups/blob/master/notes/cpp1-2.md

Win32 API 的函数大多名称较长,如果不是有意向专门学习的话,没有太大的必要特地记住(用的时候还是会忘)

微软公司出产的各种 API,都可以在 msdn (opens new window) 找到详细的文档和简单的例子,如果你想了解某个 Win32 API 的所有用法,这是最权威的地方。

所有 Win32 API 都需要包含 Windows.h 头文件。

# system() 函数

直接执行 cmd 命令行的命令。对于在学 C++ 之前学命令行/批处理的人,这个用起来很香。

# 涉及字符串的函数

这部分可能会有点坑。因为涉及到字符串编码,所以在英文上可能没什么问题,但是换成中文就会乱码。

摘自:https://github.com/guyaqi/backups/blob/master/notes/cpp1-4.md

TCHAR 与 TEXT 宏,这两个宏在对应的情况下有不同的解释,这与你的编译器默认使用的编码方式相关。

  • 当 UNICODE 宏被定义的时候(MSVC 编译器指令 /D UNICODE),TCHAR 是 wchat_t,TEXT 宏被解释成 C 语言内部用来转换宽字符的宏 L"somestr"
  • 如果没有定义 UNICODE 宏,TCHAR 就是 char,TEXT 宏不进行任何操作。

这种设定是在 utf-8 编码没有被广泛支持的时候,为了程序代码能够兼容使用不同代码的机器设定的,比如我使用的 win7 默认使用 gb2312(还是 gbk 来着)字符集对应的编码方式,而 win10 以上和其他使用 linux 内核的系统默认使用 utf-8 编码方式。

值得一提的是 utf-8 使用 char,而通常 C++,Java,C# 对 UNICODE 的支持指的是使用 utf-16 的编码方式,在 C++ 中 utf-16 字符就是 wchar_t(宽字符)。

简单的来说,C++ 有两种编码,一种是 ASCII 与其的拓展 UTF-8,另一种是 Unicode。C++ 默认使用的 char 是 ASCII 的,也就是说,你调用 Win32 API 时使用的是 char 字符串,但是程序按 Unicode 执行(Visual Studio 默认),那么就会出现乱码。(也可能出现编译错误,因为 wchar_tchar 是不能隐式转换的)

有三种解决办法:

一是将所有设计到字符和字符串的地方都改为 Unicode 流派的 wchar_t, wstring, wiostream 等等。如下面的程序:

TCHAR title[] = TEXT("新标题");
setConsoleTitle(str);

不过,在涉及到 charwchar_t 的转换时,容易出问题。貌似没有好的转换方法。也就是,要么全部用 Unicode,要么就不用 Unicode。

方法二是将涉及到这样的函数的后面加一个 A。原因是,Visual Studio 的源码是这样实现这些函数的:

#ifdef UNICODE
#define SetConsoleTitle  SetConsoleTitleW
#else
#define SetConsoleTitle  SetConsoleTitleA
#endif // !UNICODE

可以看到,这类函数都是用宏定义将函数分为两类,然后分开执行。第二种方法就是直接去执行带 A 的版本。

第三种方法就是取消 Unicode 的宏定义。具体在 Visual Studio 的 项目属性->配置属性->常规->字符集,设置为多节字符集(多字节字符集即没有设置UNICODE宏,使用Unicode字符集就是设置了 UNICODE 宏)。

设置的效果可以去 C/C++->预处理器->预处理器定义 查看

注意 QT-Addin 生成的 Visual Studio 项目默认是加上 UNICODE 宏,只去 项目属性->配置属性->常规->字符集 没用,还要手动把 QT-Addin 设置的 UNICODE 宏删掉。参考链接 (opens new window)

# 程序信息部分

中望龙腾 C++ 岗笔试考过。

#include<iostream>
#include<direct.h>

using namespace std;

int main()
{
    cout << _getcwd(0, 0) << endl                   // 获取当前文件夹
        << "__FUNCSIG__:" << __FUNCSIG__ << endl    // 获取函数完整签名
        << "__FUNCTION__:" << __FUNCTION__ << endl  // 获取函数名
        << "__LINE__:" << __LINE__;                 // 获取行数
}

输出:

C:\Users\liu\Desktop\test\cpp
__FUNCSIG__:int __cdecl main(void)
__FUNCTION__:main
__LINE__:11

# 窗口部分

窗口部分大多可以直接通过 system() 调用命令行的命令修改。

# 修改窗口标题

一个是用 cmd 命令的版本:

system("title newTitle");

好随意的标题

还有另一个版本,调用 Win32 API:

TCHAR title[] = TEXT("新标题");
setConsoleTitle(str);

但是如果涉及到变量字符串,就会变得比较麻烦了,建议涉及到标题部分的变量的地方都使用 utf-16 的东西,如 wchar_twstringwcout
如果需要 charwchar_t 的转化。目前测试了 swprintf()mbtowc() 函数,在配合 setConsoleTitle() 函数时均出现了问题。

如果必须使用 char,可以尝试下面的方法:

char title[] = "新标题";
setConsoleTitleA(title);

强制指定使用 char。貌似挺好用的。

# 修改窗口颜色

system("color 3f");

3 和 f 分别是用 16 进制数字表示的 foreground color 和 background color。具体对应关系可以自己在命令行中输入 color /?

# 修改窗口大小

system("mode con: cols=120 lines=35");

mode 语句有更多的用处不过看不懂

# 光标部分

# 获取光标位置

命令行上的光标位置是用 HANDLE 定义的。关于 HANDLE 是什么,可以自行 Google 或看这篇博客

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);

注意到这个 handle 是随着输出的位置动态变化的,因此不需要在输入前后反复获取。

# 设置光标的颜色——以后输出内容的颜色

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(handle, (fc<<4) + bc);

fc 和 bc 分别是用 16 进制数字表示的 foreground color 和 background color。具体对应关系可以自己在命令行中输入 color /?

顺便附 https://github.com/guyaqi/backups/blob/master/notes/cpp1-2.md 的作业 CPP 文件

下面是摘自 https://github.com/guyaqi/backups/blob/master/notes/cpp1-2.md 的一个 C++ 程序。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
int main()
{
    int fc, bc;
    HANDLE handle;

    handle = GetStdHandle(STD_OUTPUT_HANDLE);
    srand(time(0));

    while (true) {
        fc = rand() % 16;
        bc = rand() % 16;
        SetConsoleTextAttribute(handle, (fc<<4) + bc);
        putchar('#');
    }
    return 0;
}

# 设置光标的位置——设置以后输出内容的位置

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord = {6,6};
SetConsoleCursorPosition(handle, coord);

其中 COORD 类型包含两个成员 XY 表示坐标。cursor 翻译为光标。

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;

# 不显示光标

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor = {100,FALSE};
SetConsoleCursorInfo(handle, &cursor);

CONSOLE_CURSOR_INFO 有两个成员:第一个 dwSize 表示光标占格子(高度)的百分比,范围是 1-100;第二个 bVisible 表示是否可见。

# 键盘按键

# 获取按键状态

SHORT GetKeyState(int nVirtKey);
SHORT GetAsyncKeyState(int nVirtKey);

首先,这个东西是获取执行函数时,某按键的状态,而不是类似于 cmdchoice。要是要等待输入的话,貌似要使用 for(;;) 的死循环语法(但是实测不会像 for(;;); 一样占用过高 CPU),个人感觉不是一个好的输入的形式(先挖个坑,以后来填)。

如果一定要用该方式进行输入,为防止语句运行过快导致某次短按按键被 getKeyState() 读多次,有两种输入形式:

  1. 可以在成功读入一个字符以后,应进行 Sleep(100)(100ms 是玄学调参的结论)。这样做的话,长按的按钮会每 100ms 被识别一次,但是短按后 100ms 以内其他操作无效。
  2. 可以用 while(getKey() < 0) 读到按键结束为止。此方法把长按、短按都视为一次按键。

其次,这两个函数的区别也有点迷。理论上,前者是获取的 Windows message queue 里的信息(至于这是什么,我也不懂),后者是获取即时的硬件状态,Stack Overflow (opens new window) 上也建议使用后者,但是我在 Visual Studio 上测试的效果,其实是差不多的。(又挖坑)

再次,对于数字和(大写)字母,参数 nVirtKey 即是它的 ASCII 码。对于其他按键,可以查阅 MSDN 文档的虚拟键码部分 (opens new window)

最后,对于其返回值,由于按键有上(up)、下(down)和按住(toggled)、没有按住(untoggled),两两组合,共四种状态,因此表示也一共有四种状态。

参考链接:https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeystate

把 MSDN 的文档翻译过来就是,返回值的最高位是 1,表示该按键是按下的(down),反之则没有被按下(up);返回值的最低位是 1,表示该按键被按住(toggled),如果最低位是0,则既没有被按下(up),也没有被按住(untoggled)。

下表是按键状态和对应的返回值:

按键状态 返回值
按下并按住(可理解为长按) 1000 0001(-127)
按下但不按住(可理解为短按) 1000 0000(-128)
不按下且没有按住 0000 0000(0)
不按下但按住(有点迷) 0000 0001(1)

关于按住(toggle)这个状态的理解有点迷。

但是若只需要判断是不是按下,那就可以忽略低位。一种实现是 GetKeyState(nVirtKey) & 0x80,第二种是判断 GetKeyState(nVirtKey) >= 0

注意这里的

对于这段的理解还是挺迷的,不过以后需要的时候再来深究吧。

# 计时、获取系统时间戳

另一篇博客

# 文件管理

# Directory Management

就是命令行中获取当前文件夹、更改当前文件夹(即cd)等操作。

微软官方文档链接:Directory Management (opens new window)

DWORD GetCurrentDirectory(DWORD BUFSIZE, TCHAR[] Buffer);
bool SetCurrentDirectory(TCHAR[])

具体详解请看微软官方文档 (opens new window)

# 遍历文件

需要 FindFirstFile FindNextFileFindClose 函数。

具体详解请看微软官方文档 (opens new window)

# 得到文件大小

需要上面遍历文件得到的 File Handle。

DWORD GetFileSize(
  HANDLE  hFile,
  LPDWORD lpFileSizeHigh
);

第二参数可省。

具体详解请看[微软官方文档](https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfilesize)