如何在c++中清晰地从istream中提取字符串分隔的字符串
How to cleanly extract a string delimited string from an istream in c++
我试图从istream中提取字符串,其中字符串作为分隔符,但我还没有发现任何字符串操作,其行为接近istream中的find()
或substr()
。
下面是一个示例istream content:delim_oneFUUBARdelim_two
我的目标是让FUUBAR
变成一个字符串,尽可能少的变通。
我当前的解决方案是使用此解决方案将所有istream内容复制到字符串中,然后使用字符串操作提取。是否有一种方法可以避免这种不必要的复制,并且只从istream中读取所需的内容,以保留分隔字符串之后的所有内容,以防以类似的方式发现更多内容?
您可以轻松地创建一个使用预期分隔符或分隔符的类型:
struct Text
{
std::string t_;
};
std::istream& operator>>(std::istream& is, Text& t)
{
is >> std::skipws;
for (char c: t.t_)
{
if (is.peek() != c)
{
is.setstate(std::ios::failbit);
break;
}
is.get(); // throw away known-matching char
}
return is;
}
在ideone上查看
当前一个流提取自然停止而不使用分隔符时(例如,int
提取后跟着一个不以数字开头的分隔符),这就足够了,除非前一个提取是std::string
,否则通常是这种情况。可以将单字符分隔符指定为getline,但是假设分隔符是"</block>"
并且流包含"<black>metalic</black></block>42"
-您希望将"<black>metallic</black>
"提取为string
,丢弃"</block>"
分隔符,并在流中保留"42
":
struct Until_Delim {
Until_Delim(std::string& s, std::string delim) : s_(s), delim_(delim) { }
std::string& s_;
std::string delim_;
};
std::istream& operator>>(std::istream& is, const Until_Delim& ud)
{
std::istream::sentry sentry(is);
size_t in_delim = 0;
for (char c = is.get(); is; c = is.get())
{
if (c == ud.delim_[in_delim])
{
if (++in_delim == ud.delim_.size())
break;
continue;
}
if (in_delim) // was part-way into delimiter match...
{
ud.s_.append(ud.delim_, 0, in_delim);
in_delim = 0;
}
ud.s_ += c;
}
// may need to trim trailing whitespace...
if (is.flags() & std::ios_base::skipws)
while (!ud.s_.empty() && std::isspace(ud.s_.back()))
ud.s_.pop_back();
return is;
}
可以这样使用:
string a_string;
if (some_stream >> Until_Delim(a_string, "</block>") >> whatevers_after)
...
这个表示法可能看起来有点粗俗,但是在标准库的std::quoted()
中有先例。
您可以看到代码在这里运行
标准流配备了可以进行分类的区域设置,即std::ctype<>
facet。当下一个可用字符中不存在某种分类时,我们可以对流中的ignore()
字符使用此方面。下面是一个工作示例:
#include <iostream>
#include <sstream>
using mask = std::ctype_base::mask;
template<mask m>
void scan_classification(std::istream& is)
{
auto& ctype = std::use_facet<std::ctype<char>>(is.getloc());
while (is.peek() != std::char_traits<char>::eof() && !ctype.is(m, is.peek()))
is.ignore();
}
int main()
{
std::istringstream iss("some_string_delimiter3.1415another_string");
double d;
scan_classification<std::ctype_base::digit>(iss);
if (iss >> d)
std::cout << std::to_string(d); // "3.1415"
}