如何初始化一个std::stringstream?

35

我需要将一个字符串与整数连接起来。为了做到这一点,我使用以下方式中的stringstream

int numPeople = 10;
stringstream ss;
ss << "Number of people is " << numPeople;

那样做起了作用。但我试图以以下方式来做:

int numPeople = 10;
stringstream ss << "Number of people is " << numPeople;

我遇到了以下错误: "在'<<'标记之前需要声明初始化程序"

为什么会出现这个错误?为什么我不能在声明同时赋值stringstream的值呢?


2
这就是事实。在声明中,你唯一被允许做的就是构造你正在声明的对象。 - Brian Bi
2
我猜这个一行代码的替代方案仍然是一个干净的选项。stringstream ss; ss << "人数为" << numPeople; - acegs
@Brian - 即使OP在问题中使用了“声明”一词,他显然并不是要限制为声明。 他的一行代码可能包含多个调用构造函数的操作(例如,stringstream ss = "Number of people is ")。 他的问题在于在这个一行代码中使用了numPeople的值。 - sancho.s ReinstateMonicaCellio
2个回答

52
stringstream ss << "Number of people is " << numPeople;

为什么我不能在声明的同时给stringstream赋值呢?
这就好像希望这样做会起作用一样...
int x + 3 + 9;

...但这不是一个变量定义,更不用说定义和赋值了。

合法的定义和初始化对象的方式

唯一的方法来定义并且使用非默认值初始化变量是(从语法角度来看 - 这不是代码):

type identifier(...args...);
type identifier{...args...};
type identifier = ...expression...;

最后的注释与第一个相等-即type identifier(arg),其中arg接收...expression...
尝试使用int和stringstream的合法表示法
对于int,您可以轻松地更正代码:
int x = 3 + 9;

...而且它有效是因为"3 + 9"可以独立地进行评估,首先给出一个合理的值存储在x中。编译器对int类型的+运算符的行为正是我们想要的:它产生了我们想要存储在x中的int结果。你可以将上述内容理解为:
        // evaluate the expression to assign first...
        ((int)3 + (int)9); // notate the implicit types
        (int)12;           // evaluate expression => value to assign
int x = (int)12;           // then think about doing the assignment
int x((int)12);            // construct a valid value

它有效!但是如果你尝试对stringstream这样做...
stringstream ss = "Number of people is " << numPeople;  // BROKEN
                  "Number of people is " << numPeople; // bitshift?!

...这样是行不通的,因为"人数是 " << numPeople需要先进行评估,但这是非法的 - 你会得到一个错误,类似于:
error C2296: '<<' : illegal, left operand has type 'const char [20]'

问题在于编译器仍然试图应用位移操作,这只对数字有意义,因为我们想要使用的<<重载要求任何“X << Y”代码的左侧部分“X”是一个ostream&或可以隐式转换为ostream&。字符串字面值无法转换。此时,编译器对表达式结果将传递给的stringstream毫不知情。
stringstream的解决方案
这是一个鸡生蛋蛋生鸡的问题,因为您需要将要在stringstream中组合的右侧值与stringstream的构造函数一起调用,但是为此您需要...一个stringstream。您实际上可以通过临时stringstream来完成这个操作:
static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople)

需要进行强制类型转换,因为 operator<< 的重载通过引用处理 stringstream,返回一个 ostream&,所以你需要手动将其转换回 stringstream 类型,以便可以调用 std::stringstream 的移动构造函数...

完整的一行构造代码如下:

std::ostringstream ss(static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople));
...or...
auto&& ss = static_cast<std::ostringstream&&>(std::ostringstream{} << "Number of people is " << numPeople);

...但这太可怕了,无法想象。

通过宏使解决方案(可以说)不那么可怕

是的,你没看错。根据你的感受,你可能会觉得宏有所帮助,或者更糟糕...

#define OSS(VALUES) \
    static_cast<std::ostringstream&&>(std::ostringstream{} << VALUES)

auto&& ss = OSS("Number of people is " << numPeople);

顺便说一句,您也可以使用宏来创建字符串...

auto&& s = OSS("Number of people is " << numPeople).str(); 

...或者创建一个专用的宏...
#define STR(VALUES) \
    static_cast<std::ostringstream&&>(std::ostringstream{} << VALUES).str()
auto&& s = STR("Number of people is " << numPeople);

一个(可以说是)更好的做法 - 分离构造和初始化
只需创建stringstream - 可选择提供一个字符串给构造函数 - 然后在第二个语句中使用operator<<。
std::stringstream ss;
ss << "Number of people is " << numPeople;

这样阅读起来要容易得多,而且不需要奇怪的宏。
另一种选择是,C++11引入了to_string()重载函数,如果你有一个整数值或两个整数值需要与string连接起来,这将非常方便。
auto&& s = "Number of people is " + std::to_string(numPeople);

这可能效率不高(如果你在意的话,可以检查一下你的编译器优化能力):每个std::to_string()都很可能为一个独立的std::string实例动态分配一个缓冲区,然后各个连接操作可能涉及额外的文本复制,并且原始动态分配的缓冲区可能需要扩大,然后大部分这些临时的std::string在销毁时需要花费时间来释放。
讨论
理想情况下,std::stringstream应该有一个接受任意数量构造参数(A, B, C...)并将其格式化到stringstream中的构造函数,就像通过后续的<< A << B << C...一样。已经有了带参数的构造函数(例如(std::ios_base::openmode, const Allocator&)),所以我们需要一个占位符来区分这些参数和我们要格式化到流中的值,或者一个更奇怪的解决方法,比如要求将要格式化到流中的值作为初始化列表传递进来。
尽管如此,使用带有,而不是<<的字符串看起来和感觉非常奇怪:
std::stringstream ss{"n:", std::setw(4), std::hex, '\n'};

然后,如果在代码维护过程中发现需要将流值移到构造后的某个点,您需要更改分隔符。首先将其拆分为两行 - 构建然后流式传输 - 简化了这种维护。

C++03版本更糟糕

C++03缺乏移动构造函数,因此需要使用临时对象上的std::ostringstream::str()成员函数来获得一个额外的std::string的深拷贝,以用于构造命名的stringsteam...

stringstream ss(static_cast<std::ostringstream&>(std::ostringstream() << "Number of people is " << numPeople).str());

使用这段C++03代码,有可能会出现重复的动态内存分配(除非字符串足够短以适应字符串对象内部,这是一种常见的称为“短字符串优化”或SSO的技术)。还存在文本内容的深拷贝。构造后进行流式处理是一种更好的方法。

1

在添加值之前,需要初始化stringbuf。stringstream是一个对象。

可以通过以下方式实现:

std::stringstream ss;

你实际上是允许应用程序分配空间来处理待添加的值。

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