C++引入了ostringstream、istringstream、stringstream这三个类,要使用他们创建对象就必须包含sstream.h头文件。

  istringstream类用于执行C++风格的串流的输入操作。 
ostringstream类用于执行C风格的串流的输出操作。 
strstream类同时可以支持C风格的串流的输入输出操作。

   istringstream类是从istream和stringstreambase派生而来,ostringstream是从ostream和 stringstreambase派生而来, stringstream则是从iostream类和stringstreambase派生而来。

  他们的继承关系如下图所示:

stringstream

  istringstream是由一个string对象构造而来,istringstream类从一个string对象读取字符。 
istringstream的构造函数原形如下: 
istringstream::istringstream(string str);

 

stringstream
#include <iostream>
#include <sstream>

using namespace std;

int main()
{
    istringstream istr;
    istr.str("1 56.7");
    //上述两个过程可以简单写成 istringstream istr("1 56.7");
    cout << istr.str() << endl;
    int a;
    float b;
    istr >> a;
    cout << a << endl;
    istr >> b;
    cout << b << endl;
    return 0;
}
stringstream

上例中,构造字符串流的时候,空格会成为字符串参数的内部分界,例子中对a,b对象的输入"赋值"操作证明了这一点,字符串的空格成为了整型数据与浮点型数据的分解点,利用分界获取的方法我们事实上完成了字符串到整型对象与浮点型对象的拆分转换过程。

 

  str()成员函数的使用可以让istringstream对象返回一个string字符串(例如本例中的输出操作(cout<<istr.str();)。

  ostringstream同样是由一个string对象构造而来,ostringstream类向一个string插入字符。 
ostringstream的构造函数原形如下: 
ostringstream::ostringstream(string str); 

示例代码如下:

stringstream
#include <iostream>
#include <sstream>
#include <string>
#include<cstdlib>
using namespace std;
int main()
{
ostringstream ostr;
    //ostr.str("abc");
    //如果构造的时候设置了字符串参数,那么增长操作的时候不会从结尾开始增加,而是修改原有数据,超出的部分增长
ostr.put('d');
ostr.put('e');
ostr<<"fg";

    string gstr = ostr.str();
    cout<<gstr;
    system("pause");
}
stringstream

在上例代码中,我们通过put()或者左移操作符可以不断向ostr插入单个字符或者是字符串,通过str()函数返回增长过后的完整字符串数据,但值 得注意的一点是,当构造的时候对象内已经存在字符串数据的时候,那么增长操作的时候不会从结尾开始增加,而是修改原有数据,超出的部分增长。
[ basic_stringbuf::str : 
Sets or gets the text in a string buffer without changing the write position. ]

  对于stringstream了来说,不用我多说,大家也已经知道它是用于C++风格的字符串的输入输出的。 
stringstream的构造函数原形如下:

  stringstream::stringstream(string str);

  示例代码如下:

stringstream
#include <iostream>
#include <sstream>
#include <string>
#include<cstdlib>
using namespace std;

int main()
{
stringstream ostr("ccc");
ostr.put('d');
ostr.put('e');
ostr<<"fg";
string gstr = ostr.str();
cout<<gstr<<endl;

char a;
ostr>>a;
cout<<a;
system("pause");
}
stringstream

除此而外,stringstream类的对象我们还常用它进行string与各种内置类型数据之间的转换。 

示例代码如下:

stringstream
#include <iostream>
#include <sstream>
#include <string>
#include<cstdlib>
using namespace std;

int main()
{
stringstream sstr;
//--------int转string-----------
int a=100;
string str;
sstr<<a;
sstr>>str;
cout<<str<<endl;
//--------string转char[]--------
sstr.clear();//如果你想通过使用同一stringstream对象实现多种类型的转换,请注意在每一次转换之后都必须调用clear()成员函数。
string name = "colinguan";
char cname[200];
sstr<<name;
sstr>>cname;
cout<<cname;
system("pause");
}
stringstream
使用stringstream对象简化类型转换
 
 

C++标准库中的<sstream>提供了比ANSI C的<stdio.h>更高级的一些功能,即单纯性、类型安全和可扩展性。在本文中,我将展示怎样使用这些库来实现安全和自动的类型转换。

为什么要学习

如果你已习惯了<stdio.h>风格的转换,也许你首先会问:为什么要花额外的精力来学习基于<sstream>的类型转换呢?也许对下面一个简单的例子的回顾能够说服你。假设你想用sprintf()函数将一个变量从int类型转换到字符串类型。为了正确地完成这个任务,你必须确保证目标缓冲区有足够大空间以容纳转换完的字符串。此外,还必须使用正确的格式化符。如果使用了不正确的格式化符,会导致非预知的后果。下面是一个例子:

int n=10000;

chars[10];

sprintf(s,”%d”,n);// s中的内容为“10000”

到目前为止看起来还不错。但是,对上面代码的一个微小的改变就会使程序崩溃:

int n=10000;

char s[10];

sprintf(s,”%f”,n);// 看!错误的格式化符

在这种情况下,程序员错误地使用了%f格式化符来替代了%d。因此,s在调用完sprintf()后包含了一个不确定的字符串。要是能自动推导出正确的类型,那不是更好吗?

进入stringstream

由于ns的类型在编译期就确定了,所以编译器拥有足够的信息来判断需要哪些转换。<sstream>库中声明的标准类就利用了这一点,自动选择所必需的转换。而且,转换结果保存在stringstream对象的内部缓冲中。你不必担心缓冲区溢出,因为这些对象会根据需要自动分配存储空间。

你的编译器支持<sstream>吗?

<sstream>库是最近才被列入C++标准的。(不要把<sstream>与标准发布前被删掉的<strstream>弄混了。)因此,老一点的编译器,如GCC2.95,并不支持它。如果你恰好正在使用这样的编译器而又想使用<sstream>的话,就要先对它进行升级更新。

<sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本。简单起见,我主要以stringstream为中心,因为每个转换都要涉及到输入和输出操作。

注意,<sstream>使用string对象来代替字符数组。这样可以避免缓冲区溢出的危险。而且,传入参数和目标对象的类型被自动推导出来,即使使用了不正确的格式化符也没有危险。

string到int的转换

string result=”10000”;
int n=0;
stream<<result;
stream>>n;//n等于10000

重复利用stringstream对象

如果你打算在多次转换中使用同一个stringstream对象,记住再每次转换前要使用clear()方法;

在多次转换中重复使用同一个stringstream(而不是每次都创建一个新的对象)对象最大的好处在于效率。stringstream对象的构造和析构函数通常是非常耗费CPU时间的。

在类型转换中使用模板

你可以轻松地定义函数模板来将一个任意的类型转换到特定的目标类型。例如,需要将各种数字值,如int、long、double等等转换成字符串,要使用以一个string类型和一个任意值t为参数的to_string()函数。to_string()函数将t转换为字符串并写入result中。使用str()成员函数来获取流内部缓冲的一份拷贝:

template<class T>

void to_string(string & result,const T& t)

{

 ostringstream oss;//创建一个流

oss<<t;//把值传递如流中

result=oss.str();//获取转换后的字符转并将其写入result
}

这样,你就可以轻松地将多种数值转换成字符串了:

to_string(s1,10.5);//double到string

to_string(s2,123);//int到string

to_string(s3,true);//bool到string

可以更进一步定义一个通用的转换模板,用于任意类型之间的转换。函数模板convert()含有两个模板参数out_type和in_value,功能是将in_value值转换成out_type类型:

template<class out_type,class in_value>

out_type convert(const in_value & t)

{

stringstream stream;

stream<<t;//向流中传值

out_type result;//这里存储转换结果

stream>>result;//向result中写入值

return result;

}

这样使用convert():

double d;

string salary;

string s=”12.56”;

d=convert<double>(s);//d等于12.56

salary=convert<string>(9000.0);//salary等于”9000”

结论

 

在过去留下来的程序代码和纯粹的C程序中,传统的<stdio.h>形式的转换伴随了我们很长的一段时间。但是,如文中所述,基于stringstream的转换拥有类型安全和不会溢出这样抢眼的特性,使我们有充足得理由抛弃<stdio.h>而使用<sstream>。<sstream>库还提供了另外一个特性—可扩展性。你可以通过重载来支持自定义类型间的转换。

一些实例:

stringstream通常是用来做数据转换的。

相比c库的转换,它更加安全,自动和直接。

 

例子一:基本数据类型转换例子 int转string

stringstream
#include <string>
#include <sstream>
#include <iostream> 

int main()
{
    std::stringstream stream;
    std::string result;
    int i = 1000;
    stream << i; //将int输入流
    stream >> result; //从stream中抽取前面插入的int值
    std::cout << result << std::endl; // print the string "1000"
} 
stringstream

运行结果:

stringstream

 

例子二:除了基本类型的转换,也支持char *的转换。

stringstream
#include <sstream>
#include <iostream> 

int main()
{
    std::stringstream stream;
    char result[8] ;
    stream << 8888; //向stream中插入8888
    stream >> result; //抽取stream中的值到result
    std::cout << result << std::endl; // 屏幕显示 "8888"
} 
stringstream

stringstream

 

例子三:再进行多次转换的时候,必须调用stringstream的成员函数clear().

stringstream
#include <sstream>
#include <iostream>
int main()
{
    std::stringstream stream;
    int first, second;
    stream<< "456"; //插入字符串
    stream >> first; //转换成int
    std::cout << first << std::endl;
    stream.clear(); //在进行多次转换前,必须清除stream
    stream << true; //插入bool值
    stream >> second; //提取出int
    std::cout << second << std::endl;
} 
stringstream

运行clear的结果

stringstream

没有运行clear的结果

stringstream

 

iostream 的用途与局限
 
 

本文主要考虑 x86 Linux 平台,不考虑跨平台的可移植性,也不考虑国际化(i18n),但是要考虑 32-bit 和 64-bit 的兼容性。本文以 stdio 指代 C 语言的 scanf/printf 系列格式化输入输出函数。本文注意区分“编程初学者”和“C++初学者”,二者含义不同。
摘要:C++ iostream 的主要作用是让初学者有一个方便的命令行输入输出试验环境,在真实的项目中很少用到 iostream,因此不必把精力花在深究 iostream 的格式化与 manipulator。iostream 的设计初衷是提供一个可扩展的类型安全的 IO 机制,但是后来莫名其妙地加入了 locale 和 facet 等累赘。其整个设计复杂不堪,多重+虚拟继承的结构也很巴洛克,性能方面几无亮点。iostream 在实际项目中的用处非常有限,为此投入过多学习精力实在不值。
stdio 格式化输入输出的缺点
1. 对编程初学者不友好


看看下面这段简单的输入输出代码。
#include <stdio.h>


int main()
{
  int i;
  short s;
  float f;
  double d;
  char name[80];


  scanf("%d %hd %f %lf %s", &i, &s, &f, &d, name);
  printf("%d %d %f %f %s", i, s, f, d, name);
}
注意到其中
输入和输出用的格式字符串不一样。输入 short 要用 %hd,输出用 %d;输入 double 要用 %lf,输出用 %f。
输入的参数不统一。对于 i、s、f、d 等变量,在传入 scanf() 的时候要取地址(&),而对于 name,则不用取地址。
读者可以试一试如何用几句话向刚开始学编程的初学者解释上面两条背后原因(涉及到传递函数不定参数时的类型转换,函数调用栈的内存布局,指针的意义,字符数组退化为字符指针等等),如果一开始解释不清,只好告诉学生“这是规定”。
缓冲区溢出的危险。上面的例子在读入 name 的时候没有指定大小,这是用 C 语言编程的安全漏洞的主要来源。应该在一开始就强调正确的做法,避免养成错误的习惯。正确而安全的做法如 Bjarne Stroustrup 在《Learning Standard C++ as a New Language》所示:
#include <stdio.h>


int main()
{
  const int max = 80;
  char name[max];


  char fmt[10];
  sprintf(fmt, "%%%ds", max - 1);
  scanf(fmt, name);
  printf("%s\n", name);
}
这个动态构造格式化字符串的做法恐怕更难向初学者解释。
2. 安全性(security)


C 语言的安全性问题近十几年来引起了广泛的注意,C99 增加了 snprintf() 等能够指定输出缓冲区大小的函数,输出方面的安全性问题已经得到解决;输入方面似乎没有太大进展,还要靠程序员自己动手。
考虑一个简单的编程任务:从文件或标准输入读入一行字符串,行的长度不确定。我发现没有哪个 C 语言标准库函数能完成这个任务,除非 roll your own。
首先,gets() 是错误的,因为不能指定缓冲区的长度。
其次,fgets() 也有问题。它能指定缓冲区的长度,所以是安全的。但是程序必须预设一个长度的最大值,这不满足题目要求“行的长度不确定”。另外,程序无法判断 fgets() 到底读了多少个字节。为什么?考虑一个文件的内容是 9 个字节的字符串 "Chen\000Shuo",注意中间出现了 '\0' 字符,如果用 fgets() 来读取,客户端如何知道 "\000Shuo" 也是输入的一部分?毕竟 strlen() 只返回 4,而且整个字符串里没有 '\n' 字符。
最后,可以用 glibc 定义的 getline(3) 函数来读取不定长的“行”。这个函数能正确处理各种情况,不过它返回的是 malloc() 分配的内存,要求调用端自己 free()。
3. 类型安全(type-safe)


如果 printf() 的整数参数类型是 int、long 等标准类型, 那么 printf() 的格式化字符串很容易写。但是如果参数类型是 typedef 的类型呢?
如果你想在程序中用 printf 来打印日志,你能一眼看出下面这些类型该用 "%d" "%ld" "%lld" 中的哪一个来输出?你的选择是否同时兼容 32-bit 和 64-bit 平台?
clock_t。这是 clock(3) 的返回类型
dev_t。这是 mknod(3) 的参数类型
in_addr_t、in_port_t。这是 struct sockaddr_in 的成员类型
nfds_t。这是 poll(2) 的参数类型
off_t。这是 lseek(2) 的参数类型,麻烦的是,这个类型与宏定义 _FILE_OFFSET_BITS 有关。
pid_t、uid_t、gid_t。这是 getpid(2) getuid(2) getgid(2) 的返回类型
ptrdiff_t。printf() 专门定义了 "t" 前缀来支持这一类型(即使用 "%td" 来打印)。
size_t、ssize_t。这两个类型到处都在用。printf() 为此专门定义了 "z" 前缀来支持这两个类型(即使用 "%zu" 或 "%zd" 来打印)。
socklen_t。这是 bind(2) 和 connect(2) 的参数类型
time_t。这是 time(2) 的返回类型,也是 gettimeofday(2) 和 clock_gettime(2) 的输出结构体的成员类型
如果在 C 程序里要正确打印以上类型的整数,恐怕要费一番脑筋。《The Linux Programming Interface》的作者建议(3.6.2节)先统一转换为 long 类型再用 "%ld" 来打印;对于某些类型仍然需要特殊处理,比如 off_t 的类型可能是 long long。
还有,int64_t 在 32-bit 和 64-bit 平台上是不同的类型,为此,如果程序要打印 int64_t 变量,需要包含 <inttypes.h> 头文件,并且使用 PRId64 宏:
#include <stdio.h>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>


int main()
{
  int64_t x = 100;
  printf("%" PRId64 "\n", x);
  printf("%06" PRId64 "\n", x);
}
muduo 的 Timestamp 使用了 PRId64 http://code.google.com/p/read-taobao-code/source/browse/trunk/tair/src/storage/kdb/kyotocabinet/kcfile.cc),等于把两份独立的代码写到了同一个文件里边。
相比之下,Google leveldb 的做法更高明一些。
小结
在 C++ 项目里边自己写个 File class,把项目用到的文件 IO 功能简单封装一下(以 RAII 手法封装 FILE* 或者 file descriptor 都可以,视情况而定),通常就能满足需要。记得把拷贝构造和赋值操作符禁用,在析构函数里释放资源,避免泄露内部的 handle,这样就能自动避免很多 C 语言文件操作的常见错误。
如果要用 stream 方式做 logging,可以抛开繁重的 iostream 自己写一个简单的 LogStream,重载几个 operator<<,用起来一样方便;而且可以用 stack buffer,轻松做到线程安全。

相关文章:

  • 2021-10-09
  • 2022-12-23
  • 2021-06-27
  • 2021-07-20
  • 2021-12-29
  • 2021-09-06
猜你喜欢
  • 2021-12-13
  • 2021-05-28
  • 2021-07-10
  • 2021-06-21
相关资源
相似解决方案