C++的WCHAR操作

6
我正在开发一个使用C++编写的Win32小应用程序。我很久以前就学过C++的基础知识,所以现在对C++中的字符字符串感到非常困惑。它们没有 WCHARTCHAR,只有charString
经过一番调查,我决定不使用 TCHAR。我的问题非常简单,但我找不到清晰的指南来操作C++中的字符串。受PHP编码的影响,我希望能够轻松地进行字符串操作,但事实证明我错了!
简单来说,我需要做的就是将新数据放入一个字符字符串中。
    WCHAR* cs = L"\0";
    swprintf( cs, "NEW DATA" );

这是我的第一次尝试。在调试我的应用程序时,我调查发现swprintf只将前两个字符放入了我的cs变量中。我以以下方式解决了我的问题:

    WCHAR cs[1000];
    swprintf( cs, "NEW DATA" );

但通常这个技巧可能会失败,因为在我的情况下,新数据不是恒定值,而是另一个变量,它可能比1000个字符长。我的代码看起来像这样:

    WCHAR cs[1000];
    WCHAR* nd1;
    WCHAR* nd2;
    wcscpy(nd1, L"Some value");
    wcscpy(nd2, L"Another value"); // Actually these vars stores the path for user selected folder
    swprintf( cs, "The paths are %s and %s", nd1, nd2);

在这种情况下,nd1和nd2的字符总数可能大于1000个字符,因此关键数据将丢失。
问题是如何将我需要的所有数据复制到声明为WCHAR* wchar_var;的WCHAR字符串中而不会丢失任何内容?
附言:由于我是俄罗斯人,所以问题可能不太清楚。让我知道,我会尽力更清晰地解释我的问题。

你应该使用 wchar_tstd::wstring。并且需要更多地了解指针,因为你最后的代码片段使用未初始化的指针复制数据,这意味着你可能会覆盖进程内存中的任何内容。 - Some programmer dude
我一定会阅读关于指针的内容! - Geradlus_RU
我已经添加了标签 string - Mr.C64
那么你应该知道使用未初始化的指针是不好的,对吗? - Some programmer dude
当然可以!我在学校学习时学过指针,但那是很久以前的事了,所以我需要重新温习一下。非常感谢! - Geradlus_RU
3个回答

7
在现代的Windows编程中,可以忽略TCHAR并使用wchar_t(WCHAR)和Unicode UTF-16。TCHAR是过去的模型,当您想要拥有单个代码库并生成ANSI/MBCS和Unicode构建时,可以通过更改一些预处理器开关(例如_UNICODE和UNICODE)来实现。无论如何,您都应该使用C++和方便的字符串类来简化代码。您可以使用ATL :: CString(在Unicode构建中对应于CStringW,自VS2005以来为默认值),或STL的std :: wstring。使用CString,您可以执行以下操作:
CString str1 = L"Some value";
CString str2 = L"Another value";
CString cs;
cs.Format(L"The paths are %s and %s", str1.GetString(), str2.GetString());
还提供了适当的重载以连接字符串(因此您不必计算结果字符串的总长度,动态分配目标字符串的缓冲区或检查现有缓冲区大小,调用、,不要忘记释放缓冲区等)。
并且您可以简单地将实例传递给期望< const wchar_t*>()参数的Win32 API,因为提供了一个隐式转换运算符到。

我目前不熟悉ATL。我决定在Windows应用程序中进一步使用.NET。我想在.NET中有另一个字符串例程,对吗? - Geradlus_RU
2
你不需要熟悉ATL就可以使用CString。你只需要#include <atlstr.h>并使用CString的便捷功能(包括从应用程序资源中加载字符串)。CStringstd::wstring更好地集成在Win32编程中。 - Mr.C64

2

当你使用WCHAR*时,因为你有一个指针但没有让它指向任何有效的内容,所以你会引发未定义的行为。你需要找出结果字符串的长度并动态分配空间来存储这个字符串。例如:

WCHAR* cs;
WCHAR* nd1;
WCHAR* nd2;

nd1 = new WCHAR[lstrlen(L"Some value") + 1]; // +1 for the null terminator
nd2 = new WCHAR[lstrlen(L"Another value") + 1];
cs = new WCHAR[lstrlen(L"The paths are  and ") + lstrlen(nd1) + lstrlen(nd2) + 1];

wcscpy(nd1, L"Some value");
wcscpy(nd2, L"Another value"); // Actually these vars stores the path for user selected folder
swprintf( cs, L"The paths are %s and %s", nd1, nd2);

delete[] nd1;
delete[] nd2;
delete[] cs;

但这样做非常丑陋且容易出错。如前所述,您应该使用std::wstring,类似于以下内容:

std::wstring cs;
std::wstring nd1;
std::wstring nd2;

nd1 = L"Some value";
nd2 = L"Another value";
cs = std::wstring(L"The paths are ") + nd1 + L" and " + nd2;

我曾经考虑过“丑陋的方式”,但因为这实在是太丑陋了,你提供的第二种方式对我来说就像是黄金城一样!谢谢! - Geradlus_RU

1
建议使用ATL的CStringW类而不是原始的WCHAR,它更加方便。CString是动态分配C字符串的包装器。它将在每个操作后适当地管理字符串长度和分配的内存缓冲区,因此您不需要担心这些问题。
典型用法:
#include <atlstr.h>

CStringW s;
s.Format(L"The paths are %s and %s", L"Some value", L"Another value");
const WCHAR* wstr = s.GetString(); // To pass to some API that need WCHAR

或者

#include <atlstr.h>

CStringW s(L"The paths are ");
s += L"Some value";
s += L" and ";
s += L"Another value";
const WCHAR* wstr = s.GetString(); // To pass to some API that need WCHAR

@Mr.C64 当然可以,但最好使用显式的 GetString() - Rost
不,CString::GetString() 只有在类似于 swprintf() 的上下文中才有意义,该函数使用 printf 样式的格式字符串和 %s。我会直接将 CString 实例作为参数传递给带有 LPCWSTR 参数的函数。 - Mr.C64
CString 可以与 printf() 类似的函数一起使用,但这种方式是一种“hack”,不是健壮的代码。即使 MSDN 不鼓励这种用法并建议进行显式转换(但我发现调用 str.GetString()static_cast<const wchar_t*>(str) 更好)。此外,将 CString 传递给 const wchar_t* 参数是“完全没问题”的(对我来说,CString str; ... SetWindowText(hWnd, str); 是可以的,但 SetWindowText(hWnd, str.GetString()); 是丑陋的代码)。 - Mr.C64
这不是黑客行为,CString 是专门设计来支持这种操作的。但当然不应该这样使用。对于隐式转换和 GetString(),同样适用。即使有点丑陋,明确表达你正在做什么总是更好的选择。 - Rost
我在这里特别谈论CString编程。我不同意您在printf()类似的%s函数参数中直接使用CString(我更喜欢在此处使用GetString()),以及在将CString实例传递给const wchar_t* Win32 API参数时使用显式调用GetString()(我更喜欢直接传递CString实例)。我将停止这个讨论;我认为我已经清楚地表达了我的立场。 - Mr.C64
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接