首先,我要说的是,在新的Windows项目中最好不要使用TCHAR
,而应直接使用Unicode。接下来是实际答案:
字符集
我们需要了解的第一件事是在Visual Studio中字符集的工作方式。 项目属性页面有一个选项来选择所使用的字符集:
- 未设置
- 使用Unicode字符集
- 使用多字节字符集
![Project Property Page - Character Set](https://istack.dev59.com/ncnuB.webp)
根据您选择的三个选项之一,很多定义都会改变以适应所选字符集。有三个主要类别:字符串、来自
tchar.h
的字符串例程和API函数:
- '未设置'对应于使用ANSI编码的
TCHAR = char
,其中您使用系统的标准8位代码页来处理字符串。所有tchar.h
字符串例程都使用基本的char
版本。所有与字符串相关的API函数将使用API函数的'A'版本。
- 'Unicode'对应于使用UTF-16编码的
TCHAR = wchar_t
。所有tchar.h
字符串例程都使用wchar_t
版本。所有与字符串相关的API函数将使用API函数的'W'版本。
- '多字节'对应于使用某些多字节编码方案的
TCHAR = char
。所有tchar.h
字符串例程都使用多字节字符集版本。所有与字符串相关的API函数将使用API函数的'A'版本。
相关阅读:关于Visual Studio 2010中的“字符集”选项
TCHAR.h头文件
tchar.h
头文件是使用通用名称对字符串进行C字符串操作的帮助程序,它会根据给定的字符集切换到正确的函数。例如,_tcscat
将切换到strcat
(未设置),wcscat
(Unicode)或_mbscat
(MBCS)。_tcslen
将切换到strlen
(未设置),wcslen
(Unicode)或strlen
(MBCS)。
通过将所有_txxx
符号定义为宏,可以切换到根据编译器开关评估为正确函数的函数。
其背后的思想是,您可以使用编码不可知类型
TCHAR
(或
_TCHAR
)和作用于它们的编码不可知函数(来自
tchar.h
),而不是来自
string.h
的常规字符串函数。类似地,
_tmain
被定义为
main
或
wmain
。有关详细信息,请参见:
在 C++ 中 _tmain() 和 main() 有什么区别?
还定义了一个辅助宏
_T(..)
,用于获取正确类型的字符串字面值,即
"regular literals"
或
L"wchar_t literals"
。
请参见此处提到的注意事项:
TCHAR 是否仍然相关?-- dan04 的答案
_tmain
示例
对于问题中的 main 示例,以下代码将所有作为命令行参数传递的字符串连接成一个字符串。
int _tmain(int argc, _TCHAR *argv[])
{
TCHAR szCommandLine[1024];
if (argc < 2) return 0;
_tcscpy(szCommandLine, argv[1]);
for (int i = 2; i < argc; ++i)
{
_tcscat(szCommandLine, _T(" "));
_tcscat(szCommandLine, argv[i]);
}
return 0;
}
(省略错误检查)这段代码适用于字符集的所有三种情况,因为我们在任何地方都使用了
TCHAR
、
tchar.h
字符串函数和字符串字面值的
_T
。忘记用
_T(..)
包围您的字符串字面量是编写此类
TCHAR
程序时常见的编译器错误来源。
如果我们没有做到这些事情,那么切换字符集将导致代码要么无法编译,要么更糟糕的是在运行时表现不当。
Windows API 函数
在字符串上工作的 Windows API 函数,如 CreateFile
和 GetCurrentDirectory
,在 Windows 头文件中实现为宏,就像 tchar.h
的宏一样,会切换到 'A' 版本或 'W' 版本。例如,CreateFile
是一个宏,被定义为 ANSI 和 MBCS 的 CreateFileA
,以及对于 Unicode 的 CreateFileW
。
每当你在代码中使用平台(没有'A'或'W')时,实际调用的函数将根据所选字符集而变化。你可以通过使用显式的"A"或"W"名称来强制使用特定版本。
结论是,除非你想要始终引用特定版本(独立于字符集选项),否则应始终使用未经限定的名称。
对于问题中的示例,我们想打开第一个参数给出的文件:
int _tmain(int argc, _TCHAR *argv[])
{
if (argc < 2) return 1;
HANDLE hFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
...
CloseHandle(hFile);
return 0;
}
(错误检查被省略)请注意,在这个例子中,我们不需要使用任何特定于
TCHAR
的东西,因为宏定义已经为我们考虑了这一点。
利用 C++ 字符串
我们已经看到了如何使用
tchar.h
例程来使用 C 风格的字符串操作来处理
TCHAR
,但如果我们可以利用 C++ 的
string
来处理这个问题就更好了。
我的建议首先是不要使用
TCHAR
,而是直接使用 Unicode,请参见结论部分,但如果您想使用
TCHAR
,可以按照以下步骤进行操作。
要使用
TCHAR
,我们希望得到一个使用
TCHAR
的
std::basic_string
实例。您可以通过
typedef
自己的
tstring
来实现这一点:
typedef std::basic_string<TCHAR> tstring;
对于字符串字面值,请不要忘记使用
_T
。
您还需要使用正确版本的
cin
和
cout
。 您可以使用引用实现
tcin
和
tcout
:
#if defined(_UNICODE)
std::wistream &tcin = wcin;
std::wostream &tcout = wcout;
#else
std::istream &tcin = cin;
std::ostream &tcout = cout;
#end
这将使您能够几乎做任何事情。可能会有偶尔的例外,例如
std::to_string
和
std::to_wstring
,对于这些例外,您可以找到类似的解决方法。
结论
本答案(希望)详细介绍了
TCHAR
是什么以及它如何与 Visual Studio 和 Windows 标头交织在一起。然而,我们也应该考虑是否要使用它。
我的建议是直接为所有新的 Windows 程序使用 Unicode,根本不要使用 TCHAR
!
其他人也给出了同样的建议:
Is TCHAR still relevant?
要在创建新项目后使用 Unicode,请首先确保字符集设置为 Unicode。然后,从源文件(或者从
stdafx.h
)中删除
#include <tchar.h>
。将任何
TCHAR
或
_TCHAR
更改为
wchar_t
,将
_tmain
更改为
wmain
:
int wmain(int argc, wchar_t *argv[])
非控制台项目的Windows应用程序入口点是
WinMain
,在
TCHAR
术语中会显示为:
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
应该变成
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
在此之后,只使用wchar_t
字符串和/或std::wstring
。
进一步的注意事项
- 在使用
TCHAR
数组(字符串)时,编写sizeof(szMyString)
时要小心,因为对于ANSI,这是字符和字节的大小,对于Unicode,这仅是字节大小,字符数最多为一半,对于MBCS,这是字节大小,字符数可能相等也可能不相等。Unicode和MBCS都可以使用多个TCHAR
来编码单个字符。
- 混合使用
TCHAR
和固定的char
或wchar_t
非常麻烦;您必须将字符串从一个转换为另一个,使用正确的代码页!简单的复制在一般情况下不起作用。
_UNICODE
和UNICODE
之间存在轻微差异,如果您想有条件地定义自己的函数,则相关。请参见为什么同时存在UNICODE和_UNICODE?
一篇非常好的、补充性的答案是:
Windows上MBCS和UTF-8之间的区别。