字符编码

字符编码方式

  • 字符编码从上个世纪到现在出现了大致4种编码:
    • ASC编码:7位二进制代表一个字符串,这128位字符包含了字母数字以及一些符号。
    • ASCII编码:为了在欧洲推广计算机,所以又出现了ASCII编码,8位代表一个字符。并且前128个与前面的ASC编码一样。出现了一个代码页ID,根据代码页ID的不同将后面的128个字符换成对应国家的文字
    • DBCS编码:主要是适配亚洲的文字编码,单双字节混合编码,英文字母按照1字节编码,象形文字等按照2字节编码
    • UNICODE编码:被称为万国码,基本上把全世界所有语言都收录了。
  • 这里主要详细说明DBCSUNICODE编码,其中UNICODEUTF-8UTF-16UTF-32,如果在Windows下一般是UTF-16,而在Linux下一般是UTF-8

image-20250916144319067

宽字节字符

windows编程中有一个新的数据类型,该类型为宽字节字符,关键字为wchar_t每个字符占2个字节

  • char:每个字符占1个字节
  • wchar_t:实际是unsigned short类型,定义时,需要增加L,通知编译器按照双字节编译字符串,采用UNICODE编码。(无论是英文字母还是汉字都是占用两个字节)

需要使用支持wchar_t函数操作宽字节字符串。例如:

1
2
3
wchar_t* pwszText = L"Hello wchar"; // 宽字节字符
wprintf(L"%s\n",pwszText); // 宽字节字符
// 注意:需要使用专门处理宽字节字符串相关的函数

TCHAR宏定义

  • 由于宽字节字符和正常的字节字符有的时候会混淆,所以为了统一微软做了一个TCHAR的宏定义,该宏定义如下:
1
2
3
4
5
6
7
8
9
10
// 如果定义了UNICODE这个宏,那么TCHAR就会被宏定义为wchar_t类型
// 如果没有定义UNICODE这个宏,那么TCHAR就会被定义为char类型

#ifdef UNICODE
typedef wchar_t TCHAR;
#define __TEXT(quote) L## quote
#else
typedef char TCHAR;
#define __TEXT(quote) quote
#endif

UNICODE字符打印

wprintfUNICODE字符打印支持不完善,在Windows下使用WriteConsole API打印UNICODE字符GetStdHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<Windows.h>
#include<stdio.h>

void PrintUnicode() {
const wchar_t* pszText = L"阿斯代理商打马赛克代码卢萨卡代码";
wprintf(L"%s\n", pszText);
}
int main()
{

PrintUnicode();
return 0;
}

image-20250917222510026

  • 下面就使用WriteConsole()打印中文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<Windows.h>
#include<stdio.h>
void PrintUnicode() {
const wchar_t* pszText = L"阿斯代理商打马赛克代码卢萨卡代码";
//wprintf(L"%s\n", pszText);
// Linux有三个标准文件描述符0、1、2,
// 而Windows也有三个标准句柄标准输入句柄、标准输出句柄、标准错误句柄
// 对于Windows标准句柄值是比较大的而且还不确定,需要使用GetStdHandle()这个函数去获取标准句柄
// 该函数只有一个参数,可以是STD_OUTPUT_HANDLE、STD_INPUT_HANDLE、STD_ERROR_HANDLE,返回值就是标准句柄的值
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(hOut,pszText,wcslen(pszText),NULL,NULL);


}
int main()
{
//C_char();
//W_char();
//T_char();
PrintUnicode();
return 0;
}

出现如下乱码的原因是选用多字节字符,如果使用默认的UTF就能正确输出。

image-20250917223326957

例子

  • 现在约定一下创建一个新项目的基本步骤,先创建一个新的项目,然后将其设置为启动项

image-20250917155038576

  • 修改该项目的属性,选择高级,使用多字节字符集

image-20250917154418524

如果是设置为Unicode编译器会默认有Unicode宏定义,如果使用多字节字符集编译器就不会默认Unicode宏定义,这主要是因为Window是有大量的系统调用函数的参数是TCHAR*const TCHAR*类型。

image-20250917154501016

  • 例子1——正常的字符处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<Windows.h>
#include<stdio.h>
void C_char()
{
char* pszText = "hello char";
printf("%s\n", pszText);
}
int main()
{
C_char();
getchar();
return 0;
}

image-20250917155115719

  • 例子2——宽字节字符串:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<Windows.h>
#include<stdio.h>
void C_char()
{
const char* pszText = "hello char";
printf("%s\n", pszText);
}

void W_char()
{
const wchar_t* pszText = L"hello wchar";
int len = wcslen(pszText);
wprintf(L"%s %d\n", pszText, len);
}

int main()
{
//C_char();
W_char();
getchar();
return 0;
}
// wcslen()这个函数求的不是该字符占多少个字节,而是求的该字符有多少个。

image-20250917155451240

  • 例子3——TCHAR:
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
#include<Windows.h>
#include<stdio.h>

void C_char()
{
const char* pszText = "hello char";
printf("%s\n", pszText);
}

void W_char()
{
const wchar_t* pszText = L"hello wchar";
int len = wcslen(pszText);
wprintf(L"%s %d\n", pszText, len);
}

void T_char()
{
const TCHAR * pszText = __TEXT("hello txt");
// 如果没有定义unicode就会根据定义编译为char* pszText = "hello txt";
// 如果定义了unicode就会根据定义编译为wchar_t * pszText = L"hello wchar";
// 后面__TEXT("hello txt");主要就是根据宏定义#define __TEXT(quote) L## quote ,#define __TEXT(quote) quote来判断的
#ifdef UNICODE
wprintf(L"%s\n", pszText);
#else
printf("单:%s\n", pszText);
#endif
}
int main()
{
//C_char();
//W_char();
T_char();
getchar();
return 0;
}

image-20250917161824499

Windows窗口程序

  • 接下来手敲一个Windows窗口程序,完全了解一下Windows窗口程序的创建过程。
  • 总体概括一下Window窗口程序的创建过程(这个过程会接触到一些比较陌生的名词,之后会详细的说明):
    • 定义WinMain函数
    • 定义窗口处理函数(自定义,处理消息)
    • 注册窗口类(向操作系统写入一些数据)
    • 创建窗口(内存中创建窗口)
    • 显示窗口(绘制窗口的图像)
    • 消息循环(获取/翻译/派发消息)
    • 消息处理

简单创建一个Windows程序

  • 先创建一个Winmain函数,作为入口函数
1
2
3
4
5
6
#include<windows.h>
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR IpCmdLine, int nCmdShow)
{
return 0;
}
  • 接下来就是创建一个窗口处理函数
1
2
3
4
5
6
7
8
9
10
11
#include<windows.h>
//窗口处理函数(用户自定义,处理消息)
LRESULT CALLBACK WndProce(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM IParam)
{
return DefWindowProc(hWnd, msgID, wParam, IParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR IpCmdLine, int nCmdShow)
{
return 0;
}
  • 接下来就是在WinMain函数中注册一个窗口,也就是填写一些关于窗口的数据,并将数据写入到内核里面去。
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
#include<windows.h>
//窗口处理函数(用户自定义,处理消息)
LRESULT CALLBACK WndProce(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM IParam)
{
return DefWindowProc(hWnd, msgID, wParam, IParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR IpCmdLine, int nCmdShow)
{
//注册窗口类(向系统的内核中写入数据),先创建一个结构体将这个结构体的值都设置好
WNDCLASS wc = { 0 }; //初始化结构体
wc.cbClsExtra = 0; //申请缓冲区
wc.cbWndExtra = 0; //申请缓冲区
// 上面是申请两种不同的缓冲区
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 3);// 注册窗口背景色 + 3是黑色,+1是白色
wc.hCursor = NULL; // 光标,给NULL是默认光标
wc.hIcon = NULL; // 图标,默认图标
wc.hInstance = hIns;// 将WinMain的第一个参数赋值给他
wc.lpfnWndProc = WndProce;// 用上面定义的窗口处理函数的名称赋值
wc.lpszClassName = "Main";// 窗口类名称随便起一个
wc.lpszMenuName = NULL;// 菜单,NULL是没有菜单
wc.style = CS_HREDRAW | CS_VREDRAW; // 当窗口水平或者垂直方向发生变化,重新画窗口图画

// 使用一个系统调用函数,将这个结构体直接写入到操作系统的内核中去
RegisterClass(&wc); //将以上所有赋值全部写入操作系统
return 0
}
  • 接下来就是在内存创建窗口,也就是申请一块内存,将窗口的数据放到里面。
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
#include<windows.h>
//窗口处理函数(用户自定义,处理消息)
LRESULT CALLBACK WndProce(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM IParam)
{
return DefWindowProc(hWnd, msgID, wParam, IParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR IpCmdLine, int nCmdShow)
{
//注册窗口类(向系统的内核中写入数据),先创建一个结构体将这个结构体的值都设置好
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 8);
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hInstance = hIns;
wc.lpfnWndProc = WndProce;
wc.lpszClassName = "Main";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;

// 使用一个系统调用函数,将这个结构体直接写入到操作系统的内核中去
RegisterClass(&wc); //将以上所有赋值全部写入操作系统

// 在内存中创建窗口(申请一块内存,将窗口的各种数据存储进去),使用的是CreateWindow()函数
// 第一个参数就是wc.lpszClassName = "Main";中所写的"Main"即窗口名称
// 第二个参数是标题栏的信息,随便写
// 第三个参数是创建窗口的基本风格
// 第四个、第五个参数表示窗口的位置,即窗口左上角对应的平屏幕位置
// 第六个、第七个参数表示窗口的大小,即打开窗口的默认大小
HWND hWnd = CreateWindow("Main", "window", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, hIns, NULL);
return 0;
}
  • 接下来就是根据CreateWindow()写入到内存中的数据在电脑屏幕上显示窗口。
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
#include<windows.h>
//窗口处理函数(用户自定义,处理消息)
LRESULT CALLBACK WndProce(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM IParam)
{
return DefWindowProc(hWnd, msgID, wParam, IParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR IpCmdLine, int nCmdShow)
{
//注册窗口类(向系统的内核中写入数据),先创建一个结构体将这个结构体的值都设置好
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 8);
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hInstance = hIns;
wc.lpfnWndProc = WndProce;
wc.lpszClassName = "Main";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;

// 使用一个系统调用函数,将这个结构体直接写入到操作系统的内核中去
RegisterClass(&wc); //将以上所有赋值全部写入操作系统

// 在内存中创建窗口(申请一块内存,将窗口的各种数据存储进去)
HWND hWnd = CreateWindow("Main", "window", WS_OVERLAPPEDWINDOW, 500, 100, 500, 500, NULL, NULL, hIns, NULL);

// 显示窗口,按照窗口的数据在屏幕上显示窗口ShowWindow()函数
// 借助一个Windows api
// 第一个参数是CreateWindow()的返回值
// 第二个参数是以什么方式显示,SW_SHOW是以原样显示
ShowWindow(hWnd, SW_SHOW);
// 还需要调用UpdateWindow(),其实不用调用也可以,但是微软建议调用
// UpdateWindow()功能就是刷新窗口
UpdateWindow(hWnd);
return 0;
}
  • 然后就是消息循环,窗口处理完肯定要保持显示状态,像上面那个代码窗口一画完就退出程序,窗口就会消失。
  • 而正常来说窗口一般都有输入框、点击按钮、鼠标移动到那个图标上,这些都是用户对窗口的一些操作,消息处理基本上就是处理这些。
  • 在处理消息之前首先需要接收消息,而接收消息肯定要窗口要一直显示用户才能对窗口操作,所以需要使用一个while循环,然窗口一直显示,消息一直在接收和翻译,最后会给用户自定义的窗口处理函数来处理一些用户对窗口的操作。
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
#include<windows.h>
//窗口处理函数(用户自定义,处理消息)
LRESULT CALLBACK WndProce(HWND hWnd, UINT msgID, WPARAM wParam, LPARAM IParam)
{
return DefWindowProc(hWnd, msgID, wParam, IParam);
}
// 入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR IpCmdLine, int nCmdShow)
{
//注册窗口类(向系统的内核中写入数据),先创建一个结构体将这个结构体的值都设置好
WNDCLASS wc = { 0 };
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 8);
wc.hCursor = NULL;
wc.hIcon = NULL;
wc.hInstance = hIns;
wc.lpfnWndProc = WndProce;
wc.lpszClassName = "Main";
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;

// 使用一个系统调用函数,将这个结构体直接写入到操作系统的内核中去
RegisterClass(&wc); //将以上所有赋值全部写入操作系统

// 在内存中创建窗口(申请一块内存,将窗口的各种数据存储进去)
HWND hWnd = CreateWindow("Main", "window", WS_OVERLAPPEDWINDOW, 500, 100, 500, 500, NULL, NULL, hIns, NULL);

// 显示窗口,按照窗口的数据在屏幕上显示窗口
// 借助一个Windows api
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);

// 消息循环
MSG nMsg = { 0 }; // MSG这个结构体就是保存GetMessage抓出来的消息

// nMsg保存Getmessage抓取过来的参数
while (GetMessage(&nMsg, NULL, 0, 0))
{
TranslateMessage(&nMsg); // 翻译消息
DispatchMessage(&nMsg); // 将消息交给窗口处理函数来处理,也就是我们自己定义的消息来处理。
}

return 0;
}
  • 最后一步就是消息处理了(最后一步这里不多说),就举个例子比如你对窗口的某个按钮做了点击操作,此时自定义的函数就要做相关的一些操作。或者是点击关闭按钮,表示程序退出,这时需要调用一个窗口退出函数来退出,否则该程序就算你关了窗口,程序也会在后台一直运行。
  • 最终的效果如下:

image-20250918111643146

注册窗口类