OStringStream,或如何停止担忧并再也不使用 sprintf






4.71/5 (26投票s)
2002年3月21日
5分钟阅读

308384

1330
来自 std 库的 sprintf 的类型安全替代品。
引言
人们使用 CString
而不是 std:string
主要有两个原因。第一个是 CString
可以轻松地在 BSTR
和 char *
之间进行转换。解决这个问题的方法是 #include<comdef>
,并将 std::string.c_str()
转换为 _bstr_t
。第二个问题需要更详细的解释……
CString
有一个非常方便的方法叫做 Format。这个方法的工作方式与 sprintf
相同,这不足为奇,因为内部它会推测新的缓冲区大小,然后调用 _vstprintf
。使用 sprintf
本身或依赖执行此操作的函数存在两个问题。首先,您需要进行超出所需内存的分配,以尽量减少缓冲区溢出的可能性,而这种可能性是您无法完全消除的。其次,因为您需要单独提供变量和格式化信息,所以没有类型安全,这会是一个真正的错误原因。
这些问题并非完全是致命的,而且我相信许多商业应用程序在使用 sprintf
时也没有遇到问题。但是,这确实不是最佳实践。被严重低估的 iostream
库提供了一个更好的选择,即 ostringstream
。这个类提供了一个缓冲流,并将我们流式传输给它的信息作为 std::string
返回。因为它是一个 iostream
,所以只要存在流处理程序,我们就可以将其传递给任何类型。我不会在这篇文章中介绍如何编写自己的流处理程序,但这确实非常容易做到,然后您就可以流式传输任何您创建的类型。
所以基本思路是,我们创建一个 ostringstream
,然后像这样将信息传递给它:
ostringstream strstrm;
strstrm << "This is how I pass in text, but I can pass in numbers as easily - "
<< 42 << endl;
然后,我们可以使用 str()
方法访问底层字符串,如果需要,可以将其链接到字符串的 c_str()
方法以获取 const char *。 endl
是一个流修改器的示例 - 它添加了一个换行符并刷新了流。
cout << strstrm.str();
str
方法也可以传递一个新的值给缓冲区,所以我们可以像这样清除它:
strstrm.str("");
现在我们已经掌握了基本知识,让我们看看如何实现 sprintf
类型行为。显然,我们可以传递数字,但是如果我们想格式化它们呢?嗯,如以下代码所示,我们可以做比 sprintf
更多的操作。我们可以轻松地在十六进制、八进制和十进制之间切换,我们可以让 bool
s 被打印成单词(注意, BOOL
不是 bool
,它是 int
的 typedef
。iostreams
总是将 BOOL
视为 int
,这是您使用 BOOL
的错,Microsoft 为 C 编程创建了 BOOL
,因为 C 没有 bool
类型),我们可以将浮点数格式化为科学计数法等。请注意,当我们使用定点数时,我们得到 6 位小数;如果我们只流式传输一个浮点数,我们会得到总共六位有效数字。也就是说,如果您在小数点左侧添加一个额外的有效数字,您将失去小数点右侧的一位数字。
以下是示例代码及其输出。
string str; ostringstream strstrm; strstrm << boolalpha << true << " is a bool value" << noboolalpha << ", so is " << false; cout << strstrm.str() << endl; strstrm.str(""); strstrm << "100 decimal = " << dec << 100 << ", octal = " << 100 << ", hex = " << hex << 100 << endl; cout << strstrm.str(); strstrm.str(""); float f = 199.9273f; strstrm << "A float: " << f << ", scientific notation " << f << " " << scientific << f << " fixed notation " << fixed << f << endl; cout << strstrm.str(); strstrm.str(""); strstrm << "Formatted to two decimal places " << setprecision(2) << f << ", formatted to %2.5f " << setprecision(5) << leftprec(f, 2) << endl; cout << strstrm.str();
true is a bool value, so is 0
100 decimal = 100, octal = 144, hex = 64
A float: 199.927, scientific notation 199.927 1.999273e+002 fixed notation 199.927307
Formatted to two decimal places 199.93, formatted to %2.5f 99.92731
现在,这里有一个小技巧。boolalpha
/noboolalpha
修饰符开启/关闭布尔值的文本表示。dec
/octal
/hex
更改数字基数(setbase(8/10/16)
也是如此);scientific
/fixed
更改浮点数的显示方式(uppercase
/nouppercase
允许您设置科学计数法中 E 的大小写,而 showpos
/noshowpos
允许您强制为正数添加 + 号),而 setprecision
允许您设置小数点右侧的精度。但是,没有提供设置小数点左侧精度的方法。
我提供的这种方法的第一个想法是使用 % 运算符,但是它不能与浮点数一起使用(为什么?)。所以为了提供这种方法,我不得不编写以下宏。
#define leftprec(x,y) (x - (((int)floor(x) - (((int)floor(x))% ((int)(pow(10,y)))))))
人们问我为什么觉得宏很难看……总之,您可以使用 setprecision
来设置小数点右侧的精度,然后传入值以及您想要小数点左侧的字符数。这不会进行四舍五入,只会截断多余的值。另请注意,您需要 #include<math.h>
我使用的是 VS.NET Beta 2 构建的此项目,因此提供的项目将无法与 VC 6 一起使用。但是,创建一个空的控制台应用程序并将 main() 函数从提供的 cpp 文件复制到其中是一项简单的任务,所以我认为这也不是什么大问题。希望这篇文章让您认识到 iostream
库提供的强大功能,同时也消除了您认为没有 CString
就无法生活的任何理由。标准字符串可以完成 CString
的所有功能,只是有时不太清楚,因为 std::string
通过 iostreams
提供了一些功能,并通过它是 STL 容器的事实提供了另一些功能,这意味着 STL 提供的所有算法都可以与它一起使用。实际上,如果您知道在哪里寻找,std::string
的功能远远超过 CString
。