65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (26投票s)

2002年3月21日

5分钟阅读

viewsIcon

308384

downloadIcon

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 更多的操作。我们可以轻松地在十六进制、八进制和十进制之间切换,我们可以让 bools 被打印成单词(注意, BOOL 不是 bool,它是 inttypedefiostreams 总是将 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

© . All rights reserved.