概述
总的来讲,由于历史和发展的原因,产生了 Unicode 和 多字节字符集(MBCS、DBCS、ANSI)。在早期,使用Unicode占用内存多,使用多字节字符集又无法国际化。所以使用 WindowsAPI 以及C\C++ 进行Windows编程时有两种字符集,如一些WindowsAPI都会存在两个版本(比如MessageBoxA
和MessageBoxW
),C\C++语言有char
和wchar_t
两种字符类型。学习Windows编程,了解Unicode和 多字节字符集之间的区别是必不可少的。
现在内存没那么紧张了,尽量用Unicode字符集。
WINUSERAPI int WINAPI MessageBoxA(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
WINUSERAPI int WINAPI MessageBoxW(
_In_opt_ HWND hWnd,
_In_opt_ LPCWSTR lpText,
_In_opt_ LPCWSTR lpCaption,
_In_ UINT uType);
#include <windows.h>
#include <iostream>
int main()
{
char char1 = '1';
char char2 = '中';
wchar_t wchar_t1 = '1';
wchar_t wchar_t2 = '中';
std::cout << char1 << std::endl;//1
std::cout << char2 << std::endl;//无法显示
std::cout << wchar_t1 << std::endl;//49
std::cout << wchar_t2 << std::endl;//54992
char str1[] = "1111";//长度5 包含 \0
char str2[] = "中中中中";//长度9 包含 \0
wchar_t wstr1[] = L"1111";//5个双字符,必须带L
wchar_t wstr2[] = L"中中中中";//5个双字符,必须带L
std::cout << str1 << std::endl;//1111
std::cout << str2 << std::endl;//中中中中
std::cout << wstr1 << std::endl;//00000033D22FF7B8
std::cout << wstr2 << std::endl;//00000033D22FF7E8
return 0;
}
为了方便编程,写一份代码就可以编译成ASCII 或 Unicode两种版本,使用下面说的通用解决方案。
《Windows核心编程》
缓冲区溢出错误(这是处理字符串时的典型错误)已成为针对应用程序乃至操作系统的各个组件发起安全攻击的媒介。这几年,Microsoft 从内部和外部两个方面主动出击,倾尽全力提升 Windows世界的安全水平。本章将介绍 Microsoft在C运行库中新增的函数。我们应该使用这些新函数来防止应用程序在处理字符串时发生缓冲区溢出。
本章的位置之所以如此靠前,是由于我极力主张在应用程序始终使用Unicode字符串,而且始终应该通过新的安全字符串函数来处理这些字符串。如果源代码尚未使用Unicode,最好能将源代码迁移至Unicode,这会增强应用程序的执行性能,并为本地化工作奠定基础。理由如下:1.Unicode有利于应用程序的本地化。
2.使用Unicode,只需发布一个二进制(.exe或 DLL)文件,即可支持所有语言。Unicode提升了应用程序的效率,因为代码执行速度更快,占用内存更少。Windows内部的一切工作都是使用Unicode字符和Unicode字符串来进行的。所以,假如我们坚持传入ANSI字符或字符串,Windows就会被迫分配内存,并将ANSI字符或字符串转换为等价的Unicode形式。
3.使用Unicode,应用程序能轻松调用所有尚未弃用(nondeprecated)的 Windows函数,因为一些 Windows函数提供的版本只能处理 Unicode字符和字符串。
4.使用Unicode,应用程序的代码很容易与COM集成(后者要求使用 Unicode字符和Unicode字符串)。
5.使用 Unicode,应用程序的代码很容易与.NET Framework集成(后者要要求使用Unicode字符和 Unicode字符串)。
6.使用Unicode,能保证应用程序的代码能够轻松操纵我们自己的资源(其中的字符串总是 Unicode形式的)。在 Windows Vista中,每个Unicode 字符都使用UTF-16 编码。UTF-16将每个字符编码为2个字节(或者说16位)。在本书中,在谈到Unicode时,除非专门声明,否则一般都是指UTF-16编码。Windows之所以使用UTF-16,是因为全球各地使用的大部分语言中,每个字符很容易用一个16位值来表示。这样一来,应用程序很容易遍历字符串并计算出它的长度。但是,16位不足以表示某些语言的所有字符。对于这些语言,UTF-16支持使用代理(surrogate),后者是用32位(或者说4个字节)来表示一个字符的一种方式。由于只有少数应用程序需要表示这些语言中的字符,所以 UTF-16在节省空间和简化编码这两个目标之间,提供了一个很好的折衷。注意,.NET Framework 始终使用UTF-16来编码所有字符和字符串,所以在我们开发的Windows应用程序中,如果需要在本机代码(native code)和托管代码(managed code)之间传递字符或字符串,使用UTF-16能改进性能和减少内存消耗。
字符集发展简史
ASCII字符集定义了0x00 - 0x7F范围内的字符。还有许多其他字符集,主要是欧洲字符集,它们与ASCII字符集一样定义0x00 - 0x7F范围内的字符,也定义0x80 - 0xFF范围内的扩展字符集。8位**单字节字符集(SBCS)足以表示ASCII字符集和许多欧洲语言的字符集。但是,一些非欧洲字符集(如日语汉字)包含的字符比单字节编码方案所能表示的要多得多,因此需要多字节字符集(MBCS)**编码。并且Windows在所有平台上支持一种称为双字节字符集(DBCS)的多字节字符集(MBCS)形式。
ASCII-单字节
计算机是由美国搞出来的,他们觉得一个字节(可以表示256个编码)表示英语世界里所有的字母、数字和常用特殊符号已经绰绰有余了(其实ASCII只用了前127个编码)。
美国标准
7位码 128个字符。其中 33 个字符无法显示;可显示的字符为 95 个,编号范围是 32-126(0x20-0x7E)
扩展ASCII-单字节 欧洲字符集
后来欧洲人不干了,法国人说:我需要在小写字母加上变音符号(如:é),德国人说:我也要加几个字母(Ä ä、Ö ö、Ü ü、ß)。于是,欧洲人就将ASCII没用完的编码(128-255)为自己特有的符号编码(后来称之为“扩展字符集”)。
8位码 256个字符
多字节字符集(MBCS)、双字节字符集(DBCS)
等到我们中国人开始使用计算机的时候,尼玛,256个编码哪够?我泱泱大中华,汉字起码也得N多万吧,就连小学生都得要求掌握两三千字。国标局最后拍板:一个字节不够,那我们就用多个字节来为汉字编码吧,但是,国情那么穷,字节那么贵,三个字节伤不起,那就用俩字节吧,先给常用的几千汉字编个码,等以后国家强盛了人民富裕了,咱再扩展呗—于是GB2312就产生了。
台湾同胞一看,尼玛,全是简体字,还让不让我们写繁体字的活了,于是台湾同胞也自己弄了个繁体字编码—大五码(Big-5)。同时,其它国家也在为自己的文字编码。最后,微软苦逼了:顾客就是上帝啊,你们的编码我都得满足啊.
GB2312 和 Big5 就是多字节字符集。因为Windows中用得最多的字符是双字节字符,所以MBCS通常被**双字节字符集(DBCS)**代替。双字节字符集(DBCS)是多字节字符集(MBCS)的一种形式。
DBCS前128个代码是ASCII。但是,后面的128个代码中有些还跟随有第二个字节。这两个字节(称为前导字节和尾随字节)在一起代表一个单独的字符,常常是一个复杂的象形文字。128+128*256 = 32896
双字节字符集的问题不在于字符是由两个字节组成的。问题是有些字符是由一个字节组成的,有些是两个。这就导致了奇怪的编程问题。例如,一个字符串的字符长度不能因字节数量而决定。字符串的长度要解析后才能判断,每个字节都要被检查是不是双字节的前导字节。如果你有一个指针指向一个 DBCS字符串的中间,那前一个字符的地址是什么呢?常规做法是回到字符串的开始,一直解析到指针的位置!
Unicode的解救方案
这里存在的一个基本问题是世界上的书面语言文字根本无法用256个8位代码来表示。以前那些涉及代码页和 DBCS 的解决方法已经被证明是不足和笨拙的。那么什么是真正的解救方案呢?
作为程序员,我们有处理这类问题的经验。与其用很容易混淆的多个256字符的代码映射或者是混合使用有1字节的代码和2字节代码的双字节字符集,还不如使用Unicode一种统一的16位系统,它可以代表65536个字符。这对世界上的所有书面语言的所有字符和象形文字来说都已经足够了,其中还可以包括一批数学、符号以及装饰标志的集合。(这里说的Unicode当作16位 是因为微软默认把Unicode 当做 UTF-16 LB,在之前字符集学习中讲过,历史问题)
了解Unicode和 DBCS之间的区别是必不可少的。Unicode被认为是(特别是在C编程语言环境中)“宽字符”。每一个在 Unicode里的字符是16位宽。与此相反,在双字节字符集中我们仍在处理8位值,某些单字节本身就定义了一个字符,而另一些字节则需要一个额外字节才能完全定义一个字符。
Unicode最棒的地方是,它只有一个字符集。这就避免了二义性,适合国际化。Unicode是通过个人计算机行业几乎所有重要公司间的合作而出现的,而且代码的编制是与ISO 10646-1标准相似的。
然而Unicode是否就尽善尽美了呢?非也。Unicode字符的字符串占用的内存比 ASCII字符串大两倍。但也许最严重的缺点是Unicode仍然相对地还很少被使用。作为程序员,这正是我们要改变的。现今内存大了。
ANSI 代码页
其实ANSI并不是某一种特定的字符编码,而是在不同的系统中,ANSI表示不同的编码。你的美国同事Bob的系统中ANSI编码其实是ASCII编码(ASCII编码不能表示汉字,所以汉字为乱码),而你的系统中(“汉字”正常显示)ANSI编码其实是GBK编码,而韩文系统中(“한국어”正常显示)ANSI编码其实是EUC-KR编码。在日文Windows操作系统中,ANSI 编码代表 JIS 编码
这样吧,卖给美国国内的系统默认就用ASCII编码吧,卖给中国人的系统默认就用GBK编码吧,卖给韩国人的系统默认就用EUC-KR编码,但是为了避免你们误会我卖给你们的系统功能有差异,我就统一把你们的默认编码都显示成ANSI吧。—本故事纯属虚构,但“ANSI编码”确实只存在于Windows系统,不同的语言和区域设置可能使用不同的代码页。
Windows系统如何区分ANSI背后的真实编码?
微软用一个叫“Windows code pages”(在命令行下执行chcp命令可以查看当前code page的值)的值来判断系统默认编码,比如:简体中文的code page值为936(它表示GBK编码,win95之前表示GB2312,详见:Microsoft Windows’ Code Page 936),繁体中文的code page值为950(表示Big-5编码)。
缺点
为减少乱码问题,应用软件可以把字符页信息与文件一起保存,设计代码页之间的转换工作。
代码页的数量剧增。不统一标准,使用情况糟糕。
修改代码页
默认的“ANSI”编码方式可以通过修改Windows的区域来修改:
“控制面板” =>“时钟、语言和区域”=>“区域和语言”=>“管理”=>“更改系统区域设置…”
当系统的locale为简体中文,意味着当前“ANSI编码”实际是GBK编码。当你把它改成Korean(Korea)时,“ANSI编码”实际是EUC-KR编码,“한국어”就能正常显示了;当你把它改成English(US)时,“ANSI编码”实际是ASCII编码,“汉字”和“한국어”都成乱码了。(改了之后需要重启系统)
Windows中的Code Page,按照引用领域来划分,可以分为两类:ANSI Code Page和 OEM Code Page。
Windows编程
Windows NT从底层支持Unicode。在Windows NT 上,既可执行为 ASCII、Unicode单写的程序,也可执行为ASCII和 Unicode混合编写的程序。其实这是通过Windows NT支持可以接受8位和16位的字符串的API函数调用来实现的。Windows 98对 Unicode的支持比Windows NT少得多。只有几个Windows 98的API函数支持宽字符串。
为了在将来需要发行支持 Unicode的程序时,你能处于更有利的位置,你应该试着只拥有一个版本的源代码,它既可以被编译成ASCII,也可被编译成Unicode。这就是本书中所有程序的编写方式。
C/C++
两个版本字面量
char str1[] = "1111";//5字符,占5字节,包含 \0
char str2[] = "中中中中";//9字符,占9字节,包含 \0
wchar_t wstr1[] = L"1111";//5个双字符,占10字节,字符串字面量必须带L
wchar_t wstr2[] = L"中中中中";//5个双字符,占10字节,字符串字面量必须带L
两个版本字符类型
作为一个C语言程序员,使用16位字符的整个想法一定让人寒意顿生。
ANSI C标准通过一种叫“宽字符”的概念来支持用多个字节代表一个字符的字符集。宽字符并不一定是Unicode。Unicode只是宽字符编码的一种实现。然而,这里重点是Windows,在Windows中宽字符 和 Unicode作为同义词。
ANSI C还支持多字节字符集,如那些在中文、日语和韩语版本的Windows中支持的字符集。然而,这些多字节字符集被当作单字节值的字符串,在那些字符串里一些字符改变了后续字符的含义。多字节字符集主要影响C语言运行库函数。与此相反,宽字符不仅比正常字符宽,而且涉及编译器的一些问题。
C++提供了两种种字符类型,字符串可以用字符数组和字符串指针。
类型 | 大小 | 说明 |
---|---|---|
char | 1字节 | char是8位字符类型,最多只能包含256种字符,许多外文字符集所含的字符数目超过256个,char型无法表示。 |
wchar_t | 2字节 | 一种扩展的字符存储方式,主要用在国际化程序的实现中。 wchar_t数据类型一般为16位或32位,不同的C++库有不同的规定 |
#include <windows.h>
#include <iostream>
int main()
{
char char1 = '1';
char char2 = '中';
wchar_t wchar_t1 = '1';
wchar_t wchar_t2 = '中';
std::cout << char1 << std::endl;//1
std::cout << char2 << std::endl;//无法显示
std::cout << wchar_t1 << std::endl;//49
std::cout << wchar_t2 << std::endl;//54992
char str1[] = "1111";//5字符,占5字节,包含 \0
char str2[] = "中中中中";//9字符,占9字节,包含 \0
wchar_t wstr1[] = L"1111";//5个双字符,占10字节,字符串字面量必须带L
wchar_t wstr2[] = L"中中中中";//5个双字符,占10字节,字符串字面量必须带L
std::cout << str1 << std::endl;//1111
std::cout << str2 << std::endl;//中中中中
std::cout << wstr1 << std::endl;//00000033D22FF7B8
std::cout << wstr2 << std::endl;//00000033D22FF7E8
std::cout << sizeof(char1) << sizeof(char2) << sizeof(wchar_t1) << sizeof(wchar_t2) << std::endl;//1 1 2 2
std::cout << sizeof(str1) << std::endl;//5
std::cout << sizeof(str2) << std::endl;//9
std::cout << sizeof(wstr1) << std::endl;//10
std::cout << sizeof(wstr2) << std::endl;//10
return 0;
}
typedef char CHAR;
typedef wchar_t WCHAR;
CHAR和 WCHAR是写Windows程序时推荐使用的数据类型,这样,我们就有了数据类型CHAR和WCHAR,以及指向CHAR和 WCHAR的各种指针。
两个版本字符指针
//6种 CHAR 指针
typedef CHAR *PCHAR, *LPCH, *PCH;//字符指针
typedef _Null_terminated_ CHAR *NPSTR, *LPSTR, *PSTR;//字符串指针
//4种常量 CHAR 指针
typedef CONST CHAR *LPCCH, *PCCH;//常量字符指针
typedef _Null_terminated_ CONST CHAR *LPCSTR, *PCSTR;//常量字符串指针
//6种 WCHAR 指针
typedef WCHAR *PWCHAR, *LPWCH, *PWCH;
typedef _Null_terminated_ WCHAR *NWPSTR, *LPWSTR, *PWSTR;
//4种常量 WCHAR 指针
typedef CONST WCHAR *LPCWCH, *PCWCH;
typedef _Null_terminated_ CONST WCHAR *LPCWSTR, *PCWSTR;
前缀N和L代表着“近”(near)和“远”(long),指的是16位Windows系统中的两种大小不同的指针。但在 Win32中near和 long指针则没有区别。
Null_terminated
代表空终止字符串,字符串标准库提供的头文件中包含操作以空字符结尾的字符串(null-terminated string)的函数。它们是C++标准特有的一组函数。即为在字符串结尾加上’\0’的字符串。
两个版本库函数
比如,找出一个字符串的长度。如果我们定义了一个指向字符串的指针,我们可以调用库函数strlen,
char * pc = "Hello!";
int iLength = strlen (pc) ;//变量 iLength会被设成6,也就是字符串中字符的个数。
wchar_t *pw = L"Hello! ";
int iLength = strlen (pw) ;//1 错误
现在问题出现了。首先,C编译器会给你一个警告消息,大概是下面这个意思: VS中没有警告
' function' : incompatible types - from 'unsigned short *' to 'const char *
这是在告诉你strlen函数被定义为接受一个指向char 的指针,但这里收到的是一个指向无符号短整型的指针。仍然可以编译运行程序,但你会发现 iLength变成了1。发生了什么呢?
字符串“Hello!”中的6个字符包含的16进制值如下:
0×0048 0x0065 0x006C 0x006c 0x006F Ox0021
这些值被Intel处理器以下面这种方式存储在内存中:
48 00 65 00 6c 00 6c 00 6F 00 21 00
strlen函数,假设它试图找到字符串的长度,计算第一个字节为字符,但然后会认为第二字节00是一个表明字符串结尾的零字节。
这个小小的实验清楚地表明了C语言自身和运行库函数的细微差别?意思时编程时无法检测到误???。编译器将字符串L"Hello! "解释为一个16位短整型的集合并把它们存储在 wchar_t数组。编译器还会处理所有数组索引和sizeof操作符,因此这些都会正常工作。但是,程序运行时,运行库函数(如strlen)是在链接时被增加进去的。这些函数期望收到由单字节字符构成的字符串。因此在遇到宽字符串时,它们不会像我们预期那样执行。
你说,哦,太倒霉了,现在每一个C库函数都要被重写,以接受宽字符。嗯,并非每一个C 库函数。只有那些有字符串参数的。而且你并不需要重写它们,因为重写已经完成了。
宽字符版本的strlen 函数被称为wcslen,也定义在string.h(也就是strlen被定义的地方)和 wchar.h中。函数的声明如下:
size_t __cdecl strlen (const char*);
size_t __cdecl wcslen (const wchar_t* );
请记住,在使用宽字符的时候,字符串的字符长度并没有改变,改变的只是字节长度。
所有你喜爱的C语言中那些使用字符串参数的运行库函数都有宽字符的版本。例如,wprintf 是宽字符版本的printf。这些函数都被定义在 WCHAR.H 和定义正常函数的头文件中。
通用解决方案-维护一个源代码文件
为了方便编程,写一份代码就可以编译成ASCII 或 Unicode两种版本,通用解决方案是使用通用字符类型 TCHAR
、通用字符字面量TEXT()
、通用函数定义、加上字符集切换。
#ifdef UNICODE
typedef wchar_t TCHAR;
#else
typedef unsigned char TCHAR;
#endif
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif // !UNICODE
当然,使用Unicode肯定也有某些缺点。首先同时也是最重要的是,程序中的每一个字符串会占用两倍的空间。此外,你会看到,宽字符运行库函数比正常的函数要大。为此,你可能希望创建两个版本的程序,一个用ASCII字符串而另一个使用Unicode字符串。而最好的办法则是维护一个单一的源代码文件,但可以编译成ASCII 或 Unicode。
但这是有问题的,因为运行库函数具有不同名称,字符变量的定义也不同,还有那个讨厌的在字符串字面之前必须要加的L。
一个答案是使用包含在 Microsoft Visual C++中的TCHAR.H头文件。这个头文件并不是ANSI C标准的一部分,所以其中定义的每一个函数和宏都有一个下划线前缀。TCHAR.H为那些需要字符串参数的普通运行库函数(例如,_tprintf 和_tcslen)提供了一系列的替代名称。这些函数有时被称为“通用”的函数名字,因为它们可以指Unicode或非 Unicode版本的函数。
通用函数
比如字符串处理函数_tcslen
如果一个命名为_UNICODE
的标识符被定义了并且TCHAR.H
头文件被包含在你的程序中,_tcslen就被定义为wcslen,否则_tcslen就被定义为strlen::
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#endif
TCHAR* pw = _TEXT("Hello! ");
int iLength2 = _tcslen(pw);
通用字符类型 TCHAR
以此类推。TCHAR.H也用一个命名为TCHAR的新的数据类型解决了两个字符数据类型的问题。如果_UNICODE标识符被定义了﹒TCHAR就是wchar_t,否则TCHAR就是一个简单的char:
#ifdef UNICODE
typedef WCHAR TCHAR, *PTCHAR;
#else
typedef char TCHAR, *PTCHAR;
#endif
//x86 使用Unicode字符集
TCHAR c1 = _T('a');//97 'a'
int a = sizeof(c1);//2
TCHAR* pc = _T('a');//0x00000070 读取字符串时出错
a = sizeof(pc);//4
TCHAR s1= _TEXT("Hello!");//这被当做一个字符 31536'笰'
a = sizeof(s1);//2
TCHAR* ps = _TEXT("Hello!");//0x00c67b30 "Hello!"
a = sizeof(ps);//4
//x86 使用多字节字符集
TCHAR c1 = _T('a');//97 'a'
int a = sizeof(c1);//1
TCHAR* pc = _T('a');//0x00000070 读取字符串时出错
a = sizeof(pc);//4
TCHAR s1= _TEXT("Hello!");//这被当做一个字符 48'0'
a = sizeof(s1);//1
TCHAR* ps = _TEXT("Hello! ");//0x00c67b30 "Hello!"
a = sizeof(ps);//4
通用字符指针
//解决指针的
#ifdef UNICODE
typedef WCHAR TCHAR,*PTCHAR;
typedef LPWCH LPTCH, PTCH;
typedef LPCWCH LPCTCH, PCTCH;
typedef LPWSTR PTSTR, LPTSTR;
typedef LPCWSTR PCTSTR, LPCTSTR;
#else
typedef char TCHAR, *PTCHAR;
typedef LPCH LPTCH, PTCH;
typedef LPCCH LPCTCH, PCTCH;
typedef LPSTR PTSTR, LPTSTR, PUTSTR, LPUTSTR;
typedef LPCSTR PCTSTR, LPCTSTR, PCUTSTR, LPCUTSTR;
#endif
通用字符字面量 _T _TEXT TEXT
char a = '1';
wchar_t a = L'1';//
现在是时候来解决字符字面量 L这一棘手的问题。有三个宏效果一样,一般用TEXT
-
_T 定义于tchar.h
-
_TEXT 同样定义于tchar.h
-
TEXT 定义于winnt.h (推荐)
为什么有三个?估计是历史遗留问题,兼容问题,Jeffrey Richter在《Windows核心编程》中说,_UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件.当编译源代码模块时,通常必须同时定义这两个宏.
//tchar.h
#ifdef _UNICODE//如果_UNICODE标识符被定义
#define __T(x) L ## x
#else//如果_UNICODE标识符没有被定义,_T宏就简单地如下定义:
#define __T(x) x
#endif
//无论_UNICODE是否被定义
#define _T(x) __T(x)
#define _TEXT(x) _T(×)
--------------------------------------------------
//winnt.h
#define TEXT(quote) __TEXT(quote) // r_winnt
#ifdef UNICODE
#define __TEXT(quote) L##quote
#else
#define __TEXT(quote) quote
#endif
这样做的结果是,如果_UNICODE标识符被定义了,字符串就被解释为是由宽字符组成的,如果没有被定义,它则被解释为8位的字符串。
字符集切换
作用:让通用方案起作用。
Windows中使用printf
有个坏消息是:不能在 Windows程序中使用printf
函数。虽然可以在Windows 中使用绝大多数的C语言运行库函数—实际上,相较于Windows中相应的函数,很多程序员更愿意使用C的内存管理和文件I/O函数─—但是Windows不存在标准输入和标准输出的概念。所以你可以在Windows程序中使用 fprintf
函数,但不能使用printf
函数。
而好消息则是:你仍然可以使用sprintf
和 sprintf系列的其他函数来显示文本。除了将格式化后的字符串内容输出到函数第一个参数所提供的字符串缓冲区以外,这些函数的功能和 printf函数一样。然后就可以用这个字符串做任何事(例如,可以将输出字符串传给MessageBox)。
-
printf
格式化并输出到标准输出
-
sprintf
格式化并返回一个字符串,不直接输出
-
fprintf
格式化并输出到指定的输出流(文件、标准输出等)
int printf (const char * szFormat, ...);//第一个参数是一个格式字符串,后面是与格式字符串中的代码相对应的不同类型的多个参数。
int sprintf (char* szBuffer,const char * szFormat, ...);//第一个参数是一个字符缓冲区;后面是一个格式字符串。sprintf并不是将格式化结果写到标准输出,而是将其存入 szBuffer。该函数返回该字符串的长度。
几乎每个人都有这样的经验,一旦格式字符串与被格式化的变量不合,就可能会导致printf出错并可能造成程序崩溃。使用sprintf时,除此之外还要考虑一点:定义的字符串缓冲区必须足够大以存放结果。Microsoft的专用函数_snprintf
解决了这一问题,此函数引进了另一个参数来指定字符缓冲区的大小。
sprintf还有一个变形函数 vsprintf
,它只有三个参数。当需要对可变参数执行像printf一样的格式化时,可以用vsprintf来实现你自己的函数。vsprintf 的前两个参数与sprintf相同:一个用于保存结果的字符缓冲区和一个格式字符串。第三个参数是指向待格式化的参数数组的指针。
因为很多早期的Windows程序使用了sprintf和vsprintf,最终导致Microsoft在WindowsAPI中增添了两个相似的函数。Windows版的 wsprintf
和 wvsprintf
函数在功能上与sprintf和 vsprintf相同,不同的是它们不能处理浮点格式。
当然,随着宽字符的引入,sprintf 系列的函数增加了许多,使得函数名让人困惑。下表列出了Microsoft版的C语言运行库和 Windows所支持的所有sprintf类函数。
格式化你的消息框
图2-3所示的SCRNSIZE程序展示了如何实现MessageBoxPrintf函数,该函数能像printf那样接收各种各样大量的参数并对它们进行格式化。
新版本中已经不能运行。把 _vsntprintf 换成 _vsntprintf_s 即可。
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int CDECL MessageBoxPrintf(TCHAR* szCaption, TCHAR* szFormat, ...)
{
TCHAR szBuffer[256];
va_list pArgList;
// va_start宏(在STDARG.H中定义)通常等价于:
// pArgList = (char *) &szFormat + sizeof (szFormat) ; 取常量字符串指针szFormat的指针
va_start(pArgList, szFormat);
// wvsprintf的最后一个参数指向这些参数
_vsntprintf_s(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), 256,szFormat, pArgList);
// va_end宏只是把pArgList归零
va_end(pArgList);
return MessageBox(NULL, szBuffer, szCaption, 0);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
int cxScreen, cyScreen;
cxScreen = GetSystemMetrics(SM_CXSCREEN);//1920
cyScreen = GetSystemMetrics(SM_CYSCREEN);//1080
MessageBoxPrintf(TEXT("屏幕大小"),
TEXT("The screen is %i pixels wide by %i pixels high."),
cxScreen, cyScreen);
return 0;
}
国际化
当你为准备国际市场而编写Windows程序时,它不仅仅只是使用Unicode那么简单。国际化已经超出了本书的范围,在 Nadine Kano所著的Developing International Software forWindows 95 and Windows NT(Microsoft Press,1995)一书中囊括了更多内容。文章来源:https://www.toymoban.com/news/detail-758436.html
这里只讨论怎样通过定义UNICODE 标识符来编译程序。这包括使用TCHAR定义所有的字符和字符串,对字符串文本使用TEXT宏,以及注意不要混淆字节和字符。例如,注意SCRNSIZE 中的_vsntprintf调用。它的第二个参数是字符缓冲区的长度。通常,你会使用sizeof(szBuffer),但如果缓冲区中有宽字符,那么它返回的将不是缓冲区的字符长度,而是缓冲区按字节计算机的长度。所以必须把返回值除以sizeof(TCHAR)以得到正确的字符串长度。文章来源地址https://www.toymoban.com/news/detail-758436.html
到了这里,关于Windows编程学习笔记04-Windows编程字符集的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!