字符串流的作用是什么?
stringstring/字符串流:就是一种没有提供I/O通道的"流",它并没有实际执行I/O操作,但和文件I/O流一样提供了相同读/写操作,所以可以将stringstream看作文件I/O一样,那我们为什么要这样做呢?好些关于stringsream文章认为使用字符串的格式化函数来处理流,stringstream可以在发送输出数据之前或从输入读取数据之后提供一个友好的数据格式操作,这一定程度上是误导读者的。
在我的理解stringstream仅仅提供了">>"读取或者抽取操作和"<<"输出操作这两项基本的机制比较实用外,其他特性没什么没卵用。它的token标记化过程,是按照内置的空格字符逐词抽取或逐词输出这个特性本身就很鸡肋.复杂的格式化操作根本上需要其他string的辅助函数来扩展的,下面举的例子会逐一说明。
内置的空白字符分隔符和token标记化机制,仅适用于以下类型的应用
- 过滤/抽取特定字符
- 统计字符流中特定关键字出现的次数
- 数字的文本形式转换成int/float/double的数字类型
常用成员方法
- clear() - 清除流
- str() - 获取并设置其内容存在于流中的字符串对象。
- << 将字符串添加到stringstream对象。
- >> 从stringstream对象中读取一些内容,
token和分隔符
在字符串流中有一个非常重要的概念就是token(标记),token就是一组字符(如单词),由分隔符分隔。 分隔符是将标记彼此分开的符号,通常是'\t','\n',' '。 标记化的过程是从字符串或流中提取标记。

如果您将分隔符定义为单个空格' ',那么您有三个标记"a","b"和"cde"。 使用空格作为分隔符可以轻松实现标记。 字符串流和输入流具有基于此进行标记化的能力。
string s = "Hello My name is Jim";
stringstream ss(s);
string tmp;
cout << "s is: " << s << endl;
while (ss >> tmp) {
cout << tmp << endl;
}
输出:
$ ../app/mytest
s is: Hello My name is Jim
Hello
My
name
is
Jim
分隔符不是空格字符怎么办呢?
面对一些复杂的字符串过滤操作,stringstream显得有些鸡肋,通常有个我们可以先将string头文件的getline函数.
对象传入stringstream对象,然后使用getline去读取,还记得getline有一个很重要的函数原型。第三个参数是char类型,我们可以传入指定的字符作为分隔符.
istream& getline ( istream &is , string &str , char delim );
应用示例:处理非空白字符分隔符的情况
#include <iostream>
#include <sstream>
#include <string>
int main(int argc, char const *argv[]) {
string s(
"Hello:My:name:is:Jim:Her:name:is:Lisa:Lisa:and:Kim:is:my:good:friends."),
word;
stringstream istr(s);
while (getline(istr, word, ':')) {
cout << word << endl;
}
return 0;
}
输出:

应用示例:统计高频词汇
这个示例主要统计类似的英文字符串中的高频词汇,基本的思路是通过将原始的字符串读入stringstream对象,并在while循环中逐词抽出到一个临时的字符串对象word,然后在一个哈系表的结构中分别统计每个单词出现的次数。但我们知道在英语文法中,英式的标点符号“.”, “,”, “!”, “~”,, “?”等会跟英文单词都会从stringsream中传递到word变量.例如Hello!和Hello会被分别统计一次,但从语意上说,应该是出现2次的.所以我们每次在读取统计word之前需要过滤字符串中的标点符合或其他特殊字符。
#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
void word_counter(string &s)
{
map<string, int> keyword;
char skips[] = {'.', ',', '!', '~', '_', '?'};
vector<char>::iterator it;
stringstream iws(s);
string word, temp;
while (iws >> word)
{
size_t pos;
int SKIP_LENGTH = 5;
for (int i = 0; i < SKIP_LENGTH; i++)
{
pos = word.rfind(skips[i]);
if (pos != string::npos)
{
temp = word.substr(0, pos);
keyword[temp]++;
word.erase(0, pos + 1);
}
}
keyword[word]++;
}
map<string, int>::iterator w;
for (w = keyword.begin(); w != keyword.end(); w++)
{
cout << w->first << "->" << w->second << "\n";
}
}
但上面的时间成本是比O(n)还要略大的,因为每次循环都要执行一次for循环,这种二次嵌套循环的情况能避免尽量不要遇上.
将数字形式的字符串转换成数字类型
stringstream这是将数字字符串转换为整数,浮点数或双精度数的简单方法,不需要另外使用sscanf(),stoi()或者atoi()这些C风格的函数,这也是支持批量转换的最简单形式,例如当你从网络上读取某些统计数据是以空格符分隔的json数据。 以下是使用stringstream将字符串转换为int的示例程序。
using namespace std;
int main(int argc, char const *argv[])
{
string s = "723 453.723 666.772";
stringstream geek(s);
int x = 0;
double y, z;
geek >> x;
geek >> y;
geek >> z;
cout << "Value of x :" << ++x << endl;
cout << "Value of y :" << ++y << endl;
cout << "Value of z :" << ++z << endl;
return 0;
}
最后,stringstream+栈这个数据结构可以实现一个复合计算的公式编辑器,留给读者自行实现,例如我们可以综合上面的各个示例。
基本思路:就是将类似12+43*(123-74)的字符串读入stringstream,然后在while循环中根据“ * ”,“/”,“+”,“-”,“(”,“)”这些作为分隔符分别将stringstream中的数字逐个押入栈,括号里面的特殊优先计算结果。
总结:
stringstream的token标记化并不是说的那么强大,很多复杂的字符串格式化的操作是需要其他字符串对象API去实现的,当然对C/C++基本数据类型的格式化还是值的一用.
网友评论