学习目标
第二章是学习字符和字符串处理, 为了更好理解这一章的内容, 我自行添加了其他辅助性内容: 存储模式(大端存储和小端存储), 字符编码方案(一看就懂). 以下是这一章的学习目标:
1. 大端存储和小端存储
2. 字符编码方案
3.ANSI 和 Unicode 字符, 字符串, Windows 自定义数据类型(为了兼容 ANSI 和 Unicode)
4.Windows 的 ANSI 函数和 Unicode 函数
5.C 运行库的 ANSI 和 Unicode 函数
6.C 运行库的安全字符串函数
7.C 运行库的安全字符串函数(进阶版)
8. 字符串比较函数
9. 宽字符和 ASCII 字符之间的转换函数
必备知识 - 大端存储和小端存储
如何理解 Big Endian(大端存储)和 Little Endian(小端存储)?
举个例子:
int a = 1;
a 这个数本身的 16 进制表示是 0x00 00 00 01
在内存中怎么存储呢?
如果你的 CPU 是 intel x86 架构的(基本上就是通常我们说的奔腾 cpu), 那么就是 0x01 0x00 0x00 0x00 , 这也就是所谓的 little-endian, 低字节存放在内存的低位.
如果你的 CPU 是老式 AMD 系列的(很老很老的那种, 因为最新的 AMD 系列已经是 x86 架构了), 它的字节序就是 big-endian, 其内存存储就是 0x00 0x00 0x00 0x01 在内存中从高字节开始存放.
现在世界上绝大多数的 CPU 都是 little-endian.
字符编码
发展流程: ASCII - 扩展 ASCII-GB2312-GBK-GB18030
美国是最先开始使用计算机, 一个字节有八位二进制, 能够组合出 256 种不同的状态, 他们将控制字符, 空格, 标点符号, 数字, 大小写字母分别用连续的字节状态表示, 一直编码到了第 127 号, 这样计算机就可以用不同字节来存储英语文字了. 这时, 他们这个方案就叫做 ANSI 的 ASCII 编码. 后来计算机普及到了世界, 因为 127 种不能表示其他国家的字符, 文字, 接着他们就想扩展 ASCII, 使用 127 后面的位数来其他的字符和文字, 一直编码到状态 255, 从 126 到 255 这一页的字符集称作扩展字符集. 等到我们中国使用计算机, 那么仅仅依靠 ASCII 完全不够存储, 中国就自己想法子解决这一问题, 我们这样规定: 一个小于 127 的字符意义还是与原来的 ASCII 编码相同, 但两个大于 127 的字符连在一起时, 就表示一个汉字. 意思是说: 一个英文字母还是一个字节存储, 而一个汉字用两个字节来表示, 低 8 字节存储在低地址位置, 高 8 字节存储在高地址位置, 如果在低地址位置存储的低 8 字节的第 8 个二进制位大于 1, 在高地址位置存储的高 8 字节的第 8 个二进制位也大于 1, 那么就会被识别为一个汉字. 这样我们大概就可以组合出大约 7000 多个简体汉字了, 在这些编码种, 我们还将数学符号, 连 ASCII 原本就有的数字, 标点, 字母都统统重新编码了, 这就是我们遇到的 "全角" 字符, 而原来在 127 号以下的就叫 "半角" 字符. 之后, 中国就叫这个汉字编码方案称为 "GB2312". 后来, 中国文化博大精深, 有些汉字还没完全编码, 所以就决定干脆只要高字节代表的字符大于 127 就表示该字符为汉字, 然后称这个编码方案为 GBK 标准. 当用 GBK 解码时, 若高字节最高位为 0, 则用 ASCII 编码码解码; 若高字节最高位为 1, 则用 GBK 编码表解码. GBK 之后又有 GB18030 标准, 因 GB18030 较 GBK 又多了几千汉字, 码位不足, GB18030 使用了 2byte 与 4byte 混合编码方式, 这又给软件增加了难题, 所以虽然 GB18030 推出了近 5 年, 仍然没有得到广泛应用. 前面讲的一堆汉字编码, 我们总称为 "DBCS", 即双字节字符集. 经过前面编码的发展, 逐渐出现一个严重的问题, 就是当时各个国家都像中国这样搞出一套自己的编码标准, 结果互相之间谁的计算机都不认识对方, 谁也不支持对方的编码. 后来, 先辈们就想到, 废除所有的地区性编码方案, 重新搞一个包括了地球上所有文化, 文字和符号的编码方案, 他们称这个编码方案为 "Unicode 编码".
Unicode 编码有以下几种:
UTF-8: 一个字节一个字符, 有些字符是 2 个字节, 有的字符是 3 个字节, 还有的字符是 4 个字节.
UTF-16: 大部分字符都是 2 个字节. Windows 平台下默认的 Unicode 编码为 Little Endian 的 UTF-16.
UTF-32: 所有字符都是 4 个字节.
ANSI 和 Unicode 字符, 字符串, Windows 自定义数据类型
ANSI 字符就是 C 语言用 char 数据类型代表一个 8 位的字符. ANSI 字符串是多个 char 数据类型组成的数组, 代表多个字节的字符串. 例如:
- char a='a';//'a'这个常量字符在常量存储区存储为 1 个字节. 而 a 在栈区存储为 1 个字节.
- char szBuffer[10]="abcdefg";//"abcdefg" 这个常量字符在常量存储区存储为 8 个字节. 而 szBuffer 在栈区存储为 10 个字节.
在以前, Unicode 字符用 wchar_t 代表一个两字节的宽字符(Unicode 字符), 以前 C 头文件有这样的定义: typedef unsigned short wchar_t, 说明 wchar_t 其实也只是一个无符号短整型而已. 后来 C 编译器将 wchar_t 定义为与 Int 一样是基本数据类型, 这时候, 在高版本一点的编译器你是找不到 typedef unsigned short wchar_t 这条语句的了. 如果想表示常量字符和常量字符串为 Unicode 版本, 那么就要在前面加个 L. 例如:
- wchar_t c=L'a';//L'a'这个常量字符在常量存储区存储为 2 个字节. 而 c 在栈区存储为 2 个字节.
- wchar_t szBuffer[10]=L"abcdefg";//L"abcdefg" 这个常量字符在常量存储区存储为 16 个字节. 而 szBuffer 在栈区存储为 20 个字节.
为了与 C 语言稍微一些区分, 并且为了兼容 ANSI 和 Unicode 字符或字符串, Windows 自定义了一些数据类型: TCHAR 数据类型, TEXT 宏.
而对于 TCHAR 数据类型和 TEXT 宏的头文件定义如下:
- #ifdef UNICODE // r_winnt
- #ifndef _TCHAR_DEFINED
- typedef WCHAR TCHAR, *PTCHAR;
- typedef WCHAR TBYTE , *PTBYTE ;
- #define _TCHAR_DEFINED
- #endif /* !_TCHAR_DEFINED */
- typedef LPWCH LPTCH, PTCH;
- typedef LPCWCH LPCTCH, PCTCH;
- typedef LPWSTR PTSTR, LPTSTR;
- typedef LPCWSTR PCTSTR, LPCTSTR;
- typedef LPUWSTR PUTSTR, LPUTSTR;
- typedef LPCUWSTR PCUTSTR, LPCUTSTR;
- typedef LPWSTR LP;
- typedef PZZWSTR PZZTSTR;
- typedef PCZZWSTR PCZZTSTR;
- typedef PUZZWSTR PUZZTSTR;
- typedef PCUZZWSTR PCUZZTSTR;
- typedef PZPWSTR PZPTSTR;
- typedef PNZWCH PNZTCH;
- typedef PCNZWCH PCNZTCH;
- typedef PUNZWCH PUNZTCH;
- typedef PCUNZWCH PCUNZTCH;
- #define __TEXT(quote) L##quote // r_winnt
- #else /* UNICODE */// r_winnt
- #ifndef _TCHAR_DEFINED
- typedef char TCHAR, *PTCHAR;
- typedef unsigned char TBYTE , *PTBYTE ;
- #define _TCHAR_DEFINED
- #endif /* !_TCHAR_DEFINED */
- typedef LPCH LPTCH, PTCH;
- typedef LPCCH LPCTCH, PCTCH;
typedef LPSTR PTSTR, LPTSTR, PUTSTR, LPUTSTR;
typedef LPCSTR PCTSTR, LPCTSTR, PCUTSTR, LPCUTSTR;
- typedef PZZSTR PZZTSTR, PUZZTSTR;
- typedef PCZZSTR PCZZTSTR, PCUZZTSTR;
- typedef PZPSTR PZPTSTR;
- typedef PNZCH PNZTCH, PUNZTCH;
- typedef PCNZCH PCNZTCH, PCUNZTCH;
- #define __TEXT(quote) quote // r_winnt
- #endif /* UNICODE */// r_winnt
- #define TEXT(quote) __TEXT(quote)
从头文件定义中, 我们可以看出 TCHAR 数据类型有两种可能, 如果定义了 UNICODE, 则是 WCHAR(其实就是 wchar_t, 宽字符), 如果定义了非 UNICODE(多字节字符集), 则是 char(窄字符). 我们知道, 当我们打开 vs 编译器, 默认采取的是 Unicode 字符集, 其实这个选项代表我们写的程序序加了这句代码:#define UNICODE. 那说明我们写 TCHAR, 其实就是 wchar_t. 而如果我们在选项中更改字符集为多字节字符集, 那么就相当于定义了非 UNICODE, 那说明我们写 TCHAR, 其实就是 char. 而对于 TEXT 宏也同样道理, 如果是 UNICODE 字符集, 那么就转定义为 L##quote(代表在 quote 前面添加 L,quote 可以是字符, 也可以是字符串), 如果是多字节字符集, 那么就转定义为 quote(代表什么都不添加). 下面举个例子:
- //Unicode 字符集
- TCHAR c=TEXT('a');//TEXT('a')相当于 L'a', 在常量存储区存储为 2 个字节. 而 c 在栈区存储为 2 个字节.
- TCHAR szBuffer[10]=TEXT("abcdefg");//TEXT("abcdefg")相当于 L"abcdefg", 在常量存储区存储为 16 个字节. 而 szBuffer 在栈区存储为 20 个字节.
- // 多字节字符集
- TCHAR c=TEXT('a');//TEXT('a')相当于'a', 在常量存储区存储为 1 个字节. 而 c 在栈区存储为 1 个字节.
- TCHAR szBuffer[10]=TEXT("abcdefg");//TEXT("abcdefg")相当于 "abcdefg", 在常量存储区存储为 8 个字节. 而 szBuffer 在栈区存储为 10 个字节.
Windows 是不是很智能? 兼容了 ANSI 和 Unicode, 通过 TCHAR 和 TEXT 宏可以自动采用对应编码方式进行编码.
Windows 的 ANSI 函数和 Unicode 函数
在 windows 中, 有 UNICODE 类型的函数和 ASCII 类型的函数, 例如 CreateWindowEx 函数.
在 WinUser.h 中, 有如下定义:
- #ifdef UNICODE
- #define CreateWindowEx CreateWindowExW
- #else
- #define CreateWindowEx CreateWindowExA
- #endif // !UNICODE
根据上面头文件, 我们就知道了, CreateWindowExW 函数是支持 Unicode 字符的, 而 CreateWindowExA 是支持 ANSI 字符的. 原来 Windows 函数也会考虑到 ANSI 和 Unicode 字符串问题, 所以为了兼容这两者, 就归为 CreateWindowEx 函数了, 会自动根据情况自行选择正确的函数. 其实, 还有一个内部原理: CreateWindowExA 函数内部实现的其实只是一个转换层, 它负责分配内存, 以便将 ANSI 字符串转换为 Unicode 字符串, 然后内部代码会调用 CreateWindowExW, 并向它传递转换后的字符串, CreateWindowExW 返回时, CreateWindowExA 会释放它的内存缓冲区, 并返回窗口句柄. 这个内部原理, 总结一句话就是虽然我们调用的是 CreateWindowExA, 但实际函数内部是先将 ANSI 字符串转换为 Unicode 字符串, 再调用 CreateWindowExW, 最后释放内存, 接着 CreateWindowExA 返回内部调用的 CreateWindowExW 返回的窗口句柄.
C 运行库的 ANSI 和 Unicode 函数
C 运行库提供了一些字符串操作函数来处理 ANSI 字符和 Unicode 字符. 例如: strlen 和 wcslen 函数, 分别支持 ANSI 字符串和 Unicode 字符串.
- // 字符集为 Unicode 字符集
- char szBuffer1[5]="abcd";
- printf("%d\n", strlen(szBuffer1));
- TCHAR szBuffer2[5] = TEXT("abcd");
- printf("%d\n", wcslen(szBuffer2));
而 C 运行库为了能智能兼容 ANSI 和 Unicode, 提供了_tcslen 函数, 这个函数需要头文件 tchar.h, 并且定义了_UNICODE.
tchar.h 头文件定义了以下宏:
- #ifdef _UNICODE
- #define _tcslen wcslen
- #else
- #define _tcslen strlen
- #endif
如果包含了头文件 tchar.h, 并且字符集设置为 Unicode 字符集, 那么就已经定义了_UNICODE, 我也不知道为什么设置 Unicode 字符集就会自动定义_UNICODE, 然后就可以直接使用_tcslen, 也许是因为设置字符集这个操作内部就有 #define _UNICODE 这行代码吧. 下面举个例子:
- // 已经设置字符集为 Unicode 字符集了
- #include<windows.h>
- #include<tchar.h>
- int main()
- {
- TCHAR szBuffer3[5] = TEXT("abcd");
- printf("%d\n", _tcslen(szBuffer3));
- system("pause");
- return 0;
- }
C 运行库的安全字符串函数
我们编程的时候, 尽量使用安全字符串, 例如 strcpy 就是一个非安全函数, 当你再程序中使用这个函数的时候, 你就会发现, 编译器会出现警告, 同时给出建议, 请遵守. 编译器会提示我们使用 strcpy_s 函数, 此时, 我们可以查找这个函数, 并找到这个函数的 TCHAR.h 版. 具体使用方法不是很难, 你要使用那个字符串, 就在 MSDN 中找相应的安全字符串函数即可. 不过, 对于 strlen,wcslen 和_tcslen 等函数没有问题, 可以放心使用, 因为它们不会修改传入的字符串.
C 运行库的安全字符串函数(进阶版)
C 运行库还新增了一些函数, 用于在执行字符串处理时提供更多的控制. 例如: StringCchLength,StringCchPrintf 等函数, 更多函数请参考 MSDN.
下面是 StringCchPrintf 函数的说明:
StringCchPrintf 函数用于把格式化字符串写入指定的缓冲区中, 与 wsprintf 函数不同之处在于, 该函数还另外需要提供目标缓冲区的大小, 确保不会发生越界访问.(因为 wsprintf 函数, 若缓冲区大小不足以存储格式化字符串, 则不允许写入, 而且会发生崩溃. 但是 StringCchPrintf 函数指定了目标缓冲区大小, 意味着, 缓冲区大小不足以存储格式化字符串, 也可以截断, 只存储参数 1(缓冲区大小)的长度的字符串), 这样就避免发生了奔溃. 头文件 strsafe.h.
函数原型:
- HRESULT StringCchPrintf(
- Out LPTSTR pszDest,
- In size_t cchDest,
- In LPCTSTR pszFormat,
In ...
);
参数 1: 指定将要被写入的缓冲区
参数 2: 限制缓冲区大小
参数 3: 格式化字符串
参数 4: 可变参数
- TCHAR szBuffer[10];
- wsprintf(szBuffer, TEXT("%s"), TEXT("woainiaifbgfbfgbfgbgf"));// 当目标缓冲区不够存储源缓冲区内容, 则会溢出崩溃
- StringCchPrintf(szBuffer, 10, TEXT("%s"),TEXT("wwoainiaifbgfbfgbfgbgf"));// 新的安全字符串函数增加了一个缓冲区大小参数, 如果超过目标缓冲区大小则会自动截断, 避免了溢出崩溃
下面是 StringCchLength 函数的说明:
StringCchLength 函数用于确定字符串是否超过了规定长度. 与 lstrlen 函数的区别在于, 该函数指定了待检查的字符串的最大允许的字符数量. 注意, 如果待检查的字符串 (双引号) 长度大于最大允许的字符数量, 参数 3 置为 0. 如果待检查的字符串(单引号), 没有字符串结束符, 则无论设置 cchMax 为多少, 都会置参数 3 为 0.
* 函数原型:
- HRESULT StringCchLength(
- In LPCTSTR psz,
- In size_t cchMax,
- Out size_t pcch
- );
参数 1: 指向待检查的字符串
参数 2:psz 参数里最大允许的字符数量.
参数 3: 字符串的字符数, 不包括'\0'**
- size_t iTarget1,iTarget2,iTarget3;
- TCHAR szBuffer1[10] =TEXT("但是我依然很开心呀");
- StringCchLength(szBuffer1, 5, &iTarget1);// 如果待检查的字符串 (双引号) 长度大于最大允许的字符数量, 参数 3 置为 0. 不会报错.
- TCHAR szBuffer2[3] = { L'a', L'b', L'c' };
- StringCchLength(szBuffer2, 5, &iTarget2);// 如果待检查的字符串(单引号), 没有字符串结束符, 则无论设置 cchMax 为多少, 都会置参数 3 为 0. 不会报错.
- TCHAR szBuffer3[10] = TEXT("但是我依然很开心呀");
- StringCchLength(szBuffer3, 10, &iTarget3);// 成功了
总结, StringCch * 系列的函数是安全的, 因为可以指定如何截断, 不会发生崩溃.
字符串比较函数
- int CompareString(
- __in LCID Locale,
__in DWORD dwCmpFlags,
__in LPCTSTR lpString1,
__in int cchCount1,
__in LPCTSTR lpString2,
- __in int cchCount2
- );
- int CompareStringOrdinal(
__in LPCWSTR lpString1,
__in int cchCount1,
__in LPCWSTR lpString2,
- __in int cchCount2,
- __in BOOL bIgnoreCase
- );
CompareStringOrdina 和语言无关, 速度更快!!! 建议使用!!! 因为字符串操作函数在实际应用中可以查询 MSDN, 我以后再填补回来.
宽字符和 ASCII 字符之间的转换函数
- int MultiByteToWideChar(
- __in UINT CodePage,
__in DWORD dwFlags,
__in LPCSTR lpMultiByteStr,
__in int cbMultiByte,
__out LPWSTR lpWideCharStr,
- __in int cchWideChar
- );
- int WideCharToMultiByte(
- __in UINT CodePage,
__in DWORD dwFlags,
__in LPCWSTR lpWideCharStr,
__in int cchWideChar,
__out LPSTR lpMultiByteStr,
__in int cbMultiByte,
__in LPCSTR lpDefaultChar,
__out LPBOOL lpUsedDefaultChar
);
因为字符串操作函数在实际应用中可以查询 MSDN, 我以后再填补回来.
Windows 核心编程之核心总结(第二章 字符和字符串处理)(2018.5.27)
来源: http://www.bubuko.com/infodetail-2620510.html