宽字符
1.关于字符编码
1)ASCII
1】ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符。
2】标准 ASCII 码使用 7 位二进制数来表示所有的大写和小写字母,数字 0 到 9、标点符号,以及在美式英语中使用的特殊控制字符。
3】扩展 ASCII 码允许将每个字符的第 8 位用于确定附加的 128 个特殊符号字符、外来语字母和图形符号。
标志ascii表的部分:
拓展ascii表:
2)gb2312
标准的ascii占一个字节8位,且最高位为0;
拓展ascii表8位,最高位为1;
如果要表是中文字符,ascii不够用,因为无法表示数量庞大的汉字;
为了解决这一问题,中国的专家使用gb2312;
gb2312用两个字节表示一个汉字;
为了不至于无法区分标准ascii表,两个字符的最高位都是1;
gb2312编码中去掉了拓展ascii表,也就是使用gb2312编码时不要拓展ascii表了;
gb2312编码规则:
计算机发明之处及后面很长一段时间,只用应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。
但是当天朝也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。
天朝专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,
但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节
(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。
在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都
统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
部分gb2312表:
如图:保存在内存中的字符
2.宽字符
使用ascii可能出现的问题
1】乱码
对于中文来说,编码方式为gb2312,将2个拓展ascii字符合成1个汉字;
但其它编码方式也使用同样的方式,例如日文、韩文之类的;
也就是说,同样的两个字节,在使用不同的编码方式时表示的是不同的字符;因此可能出现乱码;
乱码的本质就是多个符号对应同一个二进制数据;
2】字符个数的计算问题
例如:hello世界
该字符串有7个字符;
实际计算出来的是9个字符;
因为一个汉字占了2个字符,5+2x2=9;
为了解决这一问题,联合国的某一组织推出了宽字符Unicode;
Unicode每一个字符都用2个字节来表示;
Unicode只有一个字符集,将所有国家的常用符号都记录在这个字符集中;
ps:
字符集本质上就是一张表,用来记录某个符号对应某个二进制数;
Unicode记录的只是所有国家常用符号,无法解决所有问题;因为2个字节最多记录65536个符号,不可能将所有符号完全包括;
因为Unicode只有一张表,使用Unicode不会出现一个二进制数对应多个符号的问题,从而解决了乱码;
例如常用的utf-8就是在Unicode的基础上扩展而来的字符集;
3.c语言中的宽字符
1)宽字符的使用
"中"字的编码:
ASCII:d6 d0 //拓展的ascii也就是gb2312
UNICODE:4e 2d
观察内存中的值,为什么会出现这种情况?
char x = '中'; //内存中只会保存d0,因为char只能存一个字节,而中文字符占2个字节,只保存一半;
wchar_t x1 = '中'; //内存中为 d6 d0,也就是对应gb2312表的d0d6,windows中是反着存的;wchar_t宽度是2个字节,足够存中文字符;
如何告诉编译器我们要使用的是Unicode的那张表呢?
wchar_t x1 = L'中'; //内存中是4e2d,字符前面加L告诉编译器查Unicode表;
2)宽字符串的使用
char x[] = "中国"; //d6 d0 b9 fa 00 使用拓展ASCII编码表 以00(\0)结尾 wchar_t x1[] = L"中国"; //2d 4e fd 56 00 00 使用UNICODE编码表 以00 00(\0\0)结尾
3)在控制台中输出宽字符
char x[] = "中国"; wchar_t x1[] = L"中国"; printf("%s\n",x); //使用控制台默认的编码 wprintf(L"%s\n",x1); //默认使用英文 ,可能无法正常输出中文
告诉编译器,使用控制台默认的编码格式
1】 包含头文件 #include <locale.h>
2】 setlocale(LC_ALL,""); //使用控制台默认的编码 ,第二个参数为""将区操作系统的语言区,也就是中文语言区
4)取宽字符串长度
需要引入头文件:#include"string.h"
char x[] = "中A国"; wchar_t x1[] = L"中A国"; strlen(x); //取得多字节字符串中字符长度,不包含 00 ;结果是5,中文占2字节,英文占1字节,不算结尾符00 wcslen(x1); //取得多字节字符串中字符长度,不包含 00 00 ;结果是3,unicode中英文都占2字节,将2字节当做1个字符,不包含结尾的0000,也就是3个字符返回3
5)字符串复制
strcpy(a,b)
->将字符串b复制到字符串a处,会将b的结束标记00也复制过去
->本质上就是封装后的memcpy(),只不过是会自动判断遇到字符串结束符00后结束复制,而不需要指定复制的字节数;
char x[] = "china"; char x1[] = "123"; strcpy(x,x1); //结果是x处的字符串变成了"123" wchar_t y[] = L"中国"; wchar_t y1[] = L"好"; wcscpy(y,y1);
ascii和宽字符关键字和常用字符串操作函数的对应:
4.Win32的API中的宽字符
1)什么是Win32 API?有哪些?在哪里?
主要是存放在 C:\WINDOWS\system32 下面所有的dll
2)非常重要的几个DLL
Kernel32.dll:最核心的功能模块,比如管理内存、进程和线程相关的函数等.
User32.dll:是Windows用户界面相关应用程序接口,如创建窗口和发送消息等.
GDI32.dll:全称是Graphical Device Interface(图形设备接口),包含用于画图和显示文本的函数
比如要显示一个程序窗口,就调用了其中的函数来画这个窗口
这些dll并不提供内核函数,只是一个壳,通过这里dll提供的函数来调用真正的内核函数;
3)Win32 API中的宽字符和多字节字符
Windows是使用C语言开发的,Win32 API同时支持宽字符与多字节字符.
为了加强可读性,Windows的API中定义了很多类型的宏,在vc6中可以按F12跟进去看到底是什么类型;
(1) 字符类型 (2) 字符串指针
char CHAR PSTR(LPSTR) 指向多字节字符串
wchar_t WCHAR PWSTR(LPWSTR) 指向宽字符串
宏 TCHAR 宏 PTSTR(LPTSTR)
字符数组赋值
CHAR cha[] = "中国"; WCHAR chw[] = L"中国"; TCHAR cht[] = TEXT("中国");
为字符串指针赋值:
PSTR pszChar = "china"; //多字节字符 PWSTR pszWChar = L"china"; //宽字符 PTSTR pszTChar = TEXT("china"); //如果项目是ASCII的 相当于"china" UNICODE 相当于L"china"
windows底层处理字符串都是用宽字符;
但为了兼容也会提供多字节字符的函数;
实际上,多字节字符的函数是将多字节字符转为宽字节字符来实现的;
TCHAR是根据环境类型来决定的,如果环境是宽字节那么TCHAR就是查UNICODE表;如果环境是多字节字符,那么TCHAR就是查ASCII表;
这样有个问题,无法确定该怎么给TCHAR类型的变量赋值,也就是到底要不要在字符串前面加L;
为了解决这个问题,通常会用TEXT("字符串")函数来给TCHAR类型赋值;
使用windows的api时最好用宏类型TCHAR;
4)各种版本的MessageBox
MessageBoxA(0,"内容多字节","标题",MB_OK); MessageBoxW(0,L"内容宽字节",L"标题",MB_OK); MessageBox(0,TEXT("根据项目字符集决定"),TEXT("标题"),MB_OK);
Windows提供的API 凡是需要传递字符串参数的函数,都会提供两个版本和一个宏.
WIN32 API只提供了MessageBoxA和MessageBoxW;
MessageBox实际上是一个宏,会根据当前项目的编码格式来决定到底是MessageBoxA还是MessageBoxW;
4.Win32的入口函数
win32控制台程序的入口函数是main;
win32程序的入口函数时WinMain;
控制台程序和win32程序本质上没什么区别,只是引入的头文件和入口函数不一样而已;
WinMain:
int CALLBACK WinMain( _In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { return 0; }
windows API所有函数的调用约定都是__stdcall;参数从右至左入栈,函数自身用ret x平衡堆栈;
5.win32程序调试
1)win32控制台输出
win32程序中不能使用printf;
为了调试需要定义一个函数能在控制台输出;
.h
void __cdecl OutputDebugStringF(const char *format, ...); #ifdef _DEBUG #define DbgPrintf OutputDebugStringF //如果是debug版的程序定义该函数,如果发布成release版则不定义;也就是为了正式版发布是不用删除调试信息; #else #define DbgPrintf #endif
.cpp
void __cdecl OutputDebugStringF(const char *format, ...) { va_list vlArgs; char *strBuffer = (char*)GlobalAlloc(GPTR, 4096); va_start(vlArgs, format); _vsnprintf(strBuffer, 4096 - 1, format, vlArgs); va_end(vlArgs); strcat(strBuffer, "\n"); OutputDebugStringA(strBuffer); GlobalFree(strBuffer); return; }
2)GetLastError的使用
该函数会返回一个DWORD类型的错误代码;
通过错误代码可以知道到底哪里出错;
将该函数放到可能出问题的函数下面即可;
例如:
TCHAR szContent[] = TEXT("内容"); TCHAR szTitle[] = TEXT("标题"); MessageBox((HWND)0,szContent,szTitle,MB_OK); DWORD errorCode = GetLastError();