我正在努力学习C++Win32。我为游戏环境创建了一个计时器类。典型的游戏计时器,它运行良好。我有这个功能,我想显示当前的游戏时间。
std::wstring Time::DisplayGameTime()
{
std::wstring Label = L"Current Time: ";
std::wstring daysText = L" " + std::to_wstring(Days()); //Returns int
std::wstring monthsText = L", " + std::to_wstring(Months()); //Returns int
std::wstring yearsText = L", " + std::to_wstring(Years()); // Returns int
std::wstring hoursText = L" " + std::to_wstring(Hours()); // Returns int
std::wstring minutesText = L" : " + std::to_wstring(Minutes()); // Returns int
std::wstring message = Label + daysText + monthsText + yearsText + hoursText + minutesText;
return message;
}
这也很好,除了分钟整数只打印一个数字,直到它达到10。我想在它上面加一个前导零,就像你在普通时钟上看到的那样。由于我使用的是std::to_wstring,所以我不能像在swprintf_s缓冲区中那样使用格式说明符。我试图弄清楚如何使用wstringstream,但我所尝试的都没有奏效。
目标是稍后使用此函数转换为LPCWSTR,以便DrawText显示在窗口中。
您可以使用传统的命令行"暴力";解决方法:
int DaysVal = Days();
std::wstring W = (DaysVal < 10 ? L"0" : L"") + std::to_wstring(DaysVal);
或者。。将std::stringstream
与std::setw
和std::setfill
一起使用(来自<iomanip>
(。
std::wstringstream WS;
WS << std::setw(2) << std::setfill(L'0') << Days();
std::wstring W = WS.str();
PS:<iomanip>
的功能也适用于std::cout
和其他流,因此std::(w)stringstream
在I/O方面是不必要的!
编辑:如果有人正在寻找利用snprintf()
:的解决方案
char CB[128];
std::snprintf(CB, sizeof(CB), "%.*i", NumDigits, Num);
使用精度(%.*i
(优于使用宽度(%*i
(,因为第二个运算的是字符总数,而不是位数!
编辑(2(:为了经得起未来的考验,我还包括了一个使用std::format()
:的解决方案
我已经很久没有玩{fmt}了,所以我不知道如何获得与printf()
相同的结果。目前,它应该足够了(但预计负数少一位数(:
std::wstring W = fmt::format(L"{:+0{}}", Num, NumDigits);
将输出写入std::wstringstream
对象与写入std::wcout
流非常相似;您使用连续呼叫<<
操作员;然后,完成后,可以使用.str()
成员函数获得底层wstring
对象的副本。
您可以使用std::setw
和std::setfill
函数分别控制每个字段的宽度和填充字符——如果需要,您可以在要调整输出的每个字段之前调用这些函数。
在您的情况下,您可以对所有字段使用默认格式,但最后一个字段除外,您可以将宽度设置为2,将填充字符设置为L'0'
:
#include <sstream>
#include <string>
#include <iomanip>
std::wstring Time::DisplayGameTime()
{
std::wstringstream message;
message
<< L"Current Time: "
// D,M,Y,H using default format ...
<< L" " << Days()
<< L", " << Months()
<< L", " << Years()
<< L" " << Hours()
// Add Minute using zero-padded, fixed width of 2 ...
<< L" : " << std::setw(2) << std::setfill(L'0') << Minutes();
return message.str();
}
正如您所发现的,to_wstring
不允许您提供格式化选项。如果你想继续使用to_wstring
,你必须自己应用格式。有很多选择,所以我会随机选择一个:
- 始终准备
"0"
- 然后返回最右边的2位数字
类似这样的东西:
auto const minutesText = []() {
// Always prepend a "0"
auto const minute { L"0" + std::to_wstring(Minutes()) };
// Return the right-most 2 digits
return minute.substr(minute.length() - 2);
}();
如果你能使用C++20,事情会容易得多,通常很多也会更快。C++20引入了一个格式化库,它将简洁的格式化描述语言(类似于Python(与C++I/O流的类型安全性相结合。
它使以下实现成为可能:
#include <format>
std::wstring Time::DisplayGameTime()
{
return std::format(L"Current Time: {}, {}, {} {} : {:02}",
Days(), Months(), Years(),
Hours(), Minutes());
}
std::format
与printf
函数族类似,采用格式化字符串,外加零个或多个要插值的参数。格式化字符串描述了最终输出,以及参数的放置位置和格式。
前四个占位符不包含任何格式规范,并且产生类似于to_wstring
的结果。最后的占位符{:02}
对字段宽度(2
(和填充字符(0
(进行编码,以便在输出没有产生足够的字符时使用。它可以拼写为{:0>2}
来指定右对齐的文本,但这是默认的整数值,所以我省略了它
使用C++20格式化库需要一个C++20编译器(实现它(。Visual Studio 2019和Visual Studio 2022都需要。两者都需要设置/std:c++-latest
编译器选项。此时,C++20格式化库不会出现在/std:c++20
编译器选项下。C++语言标准也可以从IDE中进行设置。
奖励材料:为什么std::format会这样做?-Charlie Barto-CppCon 2021:看看微软的实现,它解释了为什么std::format
比I/O流快几个数量级。这不是介绍性材料。