――By: Neicole(2013.05.24)
《Windows程序设计》的第2章,主要内容为Unicode的历史由来简述(书本讲得很好啊,一点不觉得枯燥,看上去更像是在讲故事),以及宽字符在C语言中的使用,宽字符在Windows程序设计时的基础知识及应用。书本的讲述由浅入深,学得很轻松,不过在总结时候却感觉这章做笔记没有想像中轻松,如何将它们整理成一个个知识模块是一个最大的问题。通过总结,形成自己的一个知识模块,有自己的知识框架,我个人觉得这才是有意义的学习,学有所思,学有所获。
前期:[1824年,盲文(6位编码)]-> [1854年,电报代码] -> [1903年,电传码(5位编码)]
发展:[BCDIC,6位编码] -> [EBCDIC,8位编码] -> [ASCII,7位编码]-> [DBCS,字符集内字符长度不统一,8位或16位] -> [Unicode,16位编码,可代表65536个字符]
概括:Unicode是一种在计算机上使用的字符编码,可实现跨语言、跨平台的文本转换及处理。
注意:宽字符并不一定是Unicode,Unicode只是宽字符编码的一种实现。
“大写字母L(表示长整型)紧接左引号”,这向编译器表明这个字符串将用宽字符存储。
// wchar_t测试.cpp
#include
#include
int main()
{
// 在 WCHAR.H 中,typedef unsigned short wchar_t,因此,wchar_t数据类型和无符号整型一样,都是16位宽。
// 定义
wchar_t c = 'A';
wchar_t c2 = L'A';
wchar_t * str = L"HaHa"; // 加L代表宽字符存储
// 字符串长度测量
// 测长不能用strlen(...),可以用wcslen(...),函数原型:size_t __cdecl wcslen(const wchar_t *);
size_t strLen = wcslen(str);
wchar_t arr[] = L"HaHa";
size_t arrStrLen = sizeof(arr) / sizeof(wchar_t) - 1 ; // 还有一位是结束符
return 0;
}
04.01在TCHAR.H头文件中,_UNICODE标识符的作用。
04.01.01 关于字符串测长的函数名称
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#endif
04.01.02 关于字符是否采用宽字符编译
#ifdef _UNICODE
#define __T(x) L##x
#else
#define __T(x) x
#endif
04.01.03 关于字符串是否采用宽字符编译
#ifdef _UNICODE
#define _T(x) __T(x)
#define _TEXT(x) __T(x)
#else
...
#endif
04.02 WINNT.H中的宽字符
04.02.01WINNT.H中CTYPE.H定义CHAR与WCHAR
typedef char CHAR
typedef wchar_t WCHAR
04.02.02 WINNT.H中指针类型
typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, * LPSTR, *PSTR;
typedef CONST CHAR * LPCCH, * PCCH, * LPCSTR, * PCSTR;
typedef WCHAR * PWCHAR, * LPWCH, * PWCH, * NWPSTR, * LPWSTR, * PWSTR;
typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR;
04.02.03 WINNT.H中没有下划线的UNICODE
#ifdef UNICODE
typedef WCHAR TCHAR, * PTCHAR;
typedef LPWSTR LPTCH, PTCH, PTSTR, LPTSTR;
typedef LPCWSTR LPCTSTR;
#else
typedef char TCHAR, * PTCHAR;
typedef LPSTR LPTCH, PTCH, PTSTR, LPTSTR;
typedef LPCSTR LPCTSTR;
#endif
04.02.04 WINNT.H中的TEXT
#ifdef UNICODE
#define __TEXT(quote) L##quote
#else
#define __TEXT(quote) quote
#endif
#define TEXT(quote) __TEXT(quote)
04.03 WINUSER.H中的宽字符函数
04.03.01 MESSAGEBOX的函数原型
WINUSERAPI int WINAPI MessageBoxA(HWND hWnd,LPCSTR lpText, LPCSTR lpCaption, UNIT uType);
WINUSERAPI int WINAPI MessageBoxW(HWND hWnd,LPCSWTR lpText, LPCWSTR lpCaption, UNIT uType);
关键在于第二个参数和第三个参数的不同,它们是否接受宽字符。
04.03.02 MESSAGEBOX的选择
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
05.01 效果
05.01 源码
/*-----------------------------------------------------
SCRNSIZE.C -- Displays screen size in a message box
(c) Charles Petzold, 1998
-----------------------------------------------------*/
#include
#include
#include
int CDECL MessageBoxPrintf (TCHAR * szCaption, TCHAR * szFormat, ...)
{
TCHAR szBuffer [1024] ;
va_list pArgList ;
// The va_start macro (defined in STDARG.H) is usually equivalent to:
// pArgList = (char *) &szFormat + sizeof (szFormat) ;
va_start (pArgList, szFormat) ;
// The last argument to wvsprintf points to the arguments
_vsntprintf (szBuffer, sizeof (szBuffer) / sizeof (TCHAR), szFormat, pArgList) ;
// The va_end macro just zeroes out pArgList for no good reason
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) ;
cyScreen = GetSystemMetrics (SM_CYSCREEN) ;
MessageBoxPrintf (TEXT ("ScrnSize"),
TEXT ("The screen is %i pixels wide by %i pixels high."),
cxScreen, cyScreen) ;
return 0 ;
}
05.02 解释
要读懂这程序并不困难,主要得弄懂CDECL是什么,va_arg、va_start、va_end是什么。主函数调用了库函数获取显示器的宽度和高度,然后调用自定义函数,将宽度和高度转化为TCHARSTR,再创建TCHAR字符串,并调用MessageBox函数将结果显示出来。
05.02.01 CDECL(来自MSDN)
了解__cdecl即可了解CDECL,__cdecl为Microsoft 专用,这是调用 C 和 C++ 程序的默认调用约定。 由于调用方清理堆栈,可以执行 vararg 功能。
1.参数在列表中,从右到左调用。
2.堆栈维护职责,调用函数将从堆栈的参数。
3.名称修饰约定, 下划线字符 (_) 前缀的名称,但,在导出使用 C 链接的 __cdecl 功能。
4.大小写转换约定,不执行的大小写转换。
示例:
//Example of the __cdecl keyword on function
int__cdecl system(const char *);
//Example of the __cdecl keyword on function pointer
typedefBOOL (__cdecl *funcname_ptr)(void * arg1, const char * arg2, DWORD flags, ...);
05.02.02 va_arg、va_start、va_end(来自MDSN)
实例:
// testArg.cpp
#include
#include
#include
void testArg ( int i, ...)
{
va_list argptr;
va_start(argptr, i);
while ( i--) {
char *s = va_arg( argptr, char* ); // 读取参数
printf( "%s\n", s); // 输出参数
}
va_end(argptr);
}
int main()
{
testArg( 3, "Ha", "HaHa", "HaHaHa" ); // 结果为六个Ha
system("pause");
return 0;
}
MSDN说明:
va_arg, va_end,和va_start宏提供访问函数的参数时该函数采用的参数数目可变的可移植的方式。
va_start设置arg_ptr第一个参数列表中的可选参数传递给函数。 该参数arg_ptr必须具有va_list类型。该参数prev_param是前面的第一个可选参数,参数列表中所需的参数的名称。如果prev_param与寄存器存储类,该宏的行为未定义声明。
va_start之前,必须使用va_arg用于第一次。
va_arg检索一个值的type位置从arg_ptr和增量arg_ptr指向下一个参数的列表中,使用的大小type来确定下一个参数的开始位置。 va_arg可以是用于检索参数列表中任意数量的函数内的时间。
已检索所有参数后, va_end指针,将重置空。
原理:
typedefchar * va_list;
#define_INTSIZEOF(n) \
((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#defineva_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#defineva_arg(ap,t) \
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#defineva_end(ap) ( ap = (va_list)0 )
定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址。
其实呢,本章主要收获是使用Unicode可以使程序更趋于国际化,若想使用宽字符时,include头文件TCHAR或者Windows都可以,定义UNICODE后,在用字符串的时候用T宏。另外,如果想传不定数目的参数,还可以使用Windows的CDECL,能不定数目,主要是因为它们连续存储,可以按顺序读取。