在写代码的过程中,我们最常做的事就是io操作,无论是对控制台,还是文件。但一段时间不写代码就忘了,这里理一下C++标准I/O库的具体类和操作。
C++的标准I/O库包括我们经常使用的iostream,fstream,以及不太经常使用的stringstream。前两者是对控制台和文件的I/O操作,stringstream则可以使用I/O操作对内存中的数据进行格式化操作。C++的标准I/O操作相对与C来说,更加的简明,安全,但执行效率会有所下降。
标准I/O库类继承体系
对于编程语言来说,在概念上,从控制终端、磁盘文件以及内存中读取数据都应该不影响I/O操作,C++为了解决支持不同设备的字符流,通过面向对象的思想(废话了,你要不用这个思想,你还是什么C++),通过继承来实现一组类,分别处理控制终端、磁盘文件,以及内存数据的I/O操作,下图是引用cplusplus官网关于输入输出流类继承体系的关系图,自己画了一下,如下:
由上图可以知道,I/O操作的基类是ios_base,各个类的用途如下:
- <iostream>
- istream 从流中读取数据
- ostream 向流中写数据
- iostream 对流进行读写操作,派生于istream和ostream
- <fstream>
- ifstream 从文件中读取数据,派生于istream
- ofstream 向文件中写数据,派生于ostream
- fstream 读写文件, 派生于iostream
- <sstream>
- istringstream 读取string对象,派生于istream
- ostringstream 写string对象,派生于ostream
- stringstream 读写string对象,派生于iostream
C++标准对于I/O体系,定义了基本的流格式标志(hex, dec,等),文件打开模式(in, out, bin等),流的状态标志(failbit等),以及相关的函数等,如下在linux 下/usr/include/c++/4.6/bits/ios_base.h中关于这些标志的枚举定义:
- 流格式标志
enum _Ios_Fmtflags { _S_boolalpha = 1L << 0, _S_dec = 1L << 1, _S_fixed = 1L << 2, _S_hex = 1L << 3, _S_internal = 1L << 4, _S_left = 1L << 5, _S_oct = 1L << 6, _S_right = 1L << 7, _S_scientific = 1L << 8, _S_showbase = 1L << 9, _S_showpoint = 1L << 10, _S_showpos = 1L << 11, _S_skipws = 1L << 12, _S_unitbuf = 1L << 13, _S_uppercase = 1L << 14, _S_adjustfield = _S_left | _S_right | _S_internal, _S_basefield = _S_dec | _S_oct | _S_hex, _S_floatfield = _S_scientific | _S_fixed, _S_ios_fmtflags_end = 1L << 16 };
- 文件打开模式
enum _Ios_Openmode { _S_app = 1L << 0, _S_ate = 1L << 1, _S_bin = 1L << 2, _S_in = 1L << 3, _S_out = 1L << 4, _S_trunc = 1L << 5, _S_ios_openmode_end = 1L << 16 };
- 流的状态标志
enum _Ios_Iostate { _S_goodbit = 0, _S_badbit = 1L << 0, _S_eofbit = 1L << 1, _S_failbit = 1L << 2, _S_ios_iostate_end = 1L << 16 };
I/O流的状态
为了更好地管理I/O操作,标准定义了一系列条件状态标志和函数,用来管理流对象,比如说是否可用,以及出现的错误。对于流的状态标志是在ios_base类中进行定义的,所以流的状态标志管理适用于终端、文件、string流对象。流的状态定义为:ios_base::iostate,具体有哪些值在ios_base中有定义,如下:
typedef _Ios_Iostate iostate; static const iostate badbit = _S_badbit; //流被破坏 (流本身的问题) static const iostate eofbit = _S_eofbit; //流已到结尾 static const iostate failbit = _S_failbit; //I/O失败(操作本身上的逻辑问题) static const iostate goodbit = _S_goodbit; //好流...正常为了管理上述的状态标志,标准在ios类中提供了以下函数来进行处理:
iostate rdstate() const //返回流的条件状态 void clear(iostate __state = goodbit); //设置流的条件状态,默认设置为正常 void setstate(iostate __state); //设置流的状态,和clear的区别:不会清除其他标志 bool good() const //流的是否正常 bool eof() const //流是否到结尾 bool fail() const //流是否I/O失败 bool bad() const //流是否被破坏
可以通过下面代码简单进行状态标志的测试:
#include <iostream> using std::cout; using std::cin; using std::endl; int main() { int a; cin>>a; cout<<"goodbit: "<<cin.good()<<" eofbit: "<<cin.eof()<<" failbit: "<<cin.fail()<<" badbit: "<<cin.bad()<<endl; cin>>a; cout<<"goodbit: "<<cin.good()<<" eofbit: "<<cin.eof()<<" failbit: "<<cin.fail()<<" badbit: "<<cin.bad()<<endl; }输入1 a后显示如下:
1 goodbit: 1 eofbit: 0 failbit: 0 badbit: 0 a goodbit: 0 eofbit: 0 failbit: 1 badbit: 0
由上可知非法输入会导致I/O失败,failbit流标志生效。
文件I/O操作
C++提供了ifstream和ofstream类负责文件I/O的读和写操作,类fstream负责文件的读写,继承关系文章开头已说明。前面说了,在ios_base基类中定义了文件打开模式,
typedef _Ios_Openmode openmode; /// Seek to end before each write. static const openmode app = _S_app; /// Open and seek to end immediately after opening. static const openmode ate = _S_ate; /// Perform input and output in binary mode (as opposed to text mode). static const openmode binary = _S_bin; /// Open for input. Default for @c ifstream and fstream. static const openmode in = _S_in; /// Open for output. Default for @c ofstream and fstream. static const openmode out = _S_out; /// Open for input. Default for @c ofstream. static const openmode trunc = _S_trunc;
具体含义如下:
- ios_base::in 只读打开
- ios_base::out 只写打开
- ios_base::app 每次写之前移到文件尾
- ios_base::ate 打开文件后立刻定位到文件尾
- ios_base::trunc 打开文件并清空文件流
- ios_base::binary 以二进制模式进行读写操作
以ifstream方式打开文件,默认打开方式为in,以ofstream方式打开,默认打开方式为out | trunc,fstream是默认以in | out方式打开。如果要以ofstream打开文件,且同时保存文件的数据,只能通过显示的指定app打开模式。如下是fstream头文件中的open函数原型:
ifstream void open(const char* __s, ios_base::openmode __mode = ios_base::in) ofstream void open(const char* __s, ios_base::openmode __mode = ios_base::out | ios_base::trunc) fstream void open(const char* __s, ios_base::openmode __mode = ios_base::in | ios_base::out)
如果fstream打开方式是out,也会清空文件中数据。当文件以out方式打开(不包含in模式)时,如果文件不存在,会创建一个新文件。
-
输入输出操作符
istream,ostream类中都定义了用于输入输出的算法提取器(Arithmetic Extractor):‘>>‘和‘<<’操作符,可以提取内置数据类型的数据,例如,输入操作符>>用于从流缓冲区streambuf中提取数据,并输出到特定类型的变量中,例如istream头文件中具体定义如下:
//类内定义的,用于提取 __istream_type& operator>>(bool& __n); __istream_type& operator>>(short& __n); __istream_type& operator>>(unsigned short& __n); __istream_type& operator>>(int& __n); __istream_type& operator>>(unsigned int& __n); __istream_type& operator>>(long& __n); __istream_type& operator>>(unsigned long& __n); __istream_type& operator>>(long long& __n); __istream_type& operator>>(unsigned long long& __n); __istream_type& operator>>(float& __f) __istream_type& operator>>(double& __f) __istream_type& operator>>(long double& __f) __istream_type& operator>>(void*& __p) //类外定义的,用于提取字符 template<class _Traits> inline basic_istream<char, _Traits>& operator>>(basic_istream<char, _Traits>& __in, unsigned char& __c); template<class _Traits> inline basic_istream<char, _Traits>& operator>>(basic_istream<char, _Traits>& __in, signed char& __c); template<class _Traits> inline basic_istream<char, _Traits>& operator>>(basic_istream<char, _Traits>& __in, unsigned char* __s); template<class _Traits> inline basic_istream<char, _Traits>& operator>>(basic_istream<char, _Traits>& __in, signed char* __s); //string类中对输入输出operator的重载,使其能支持string类型的输入输出 template<typename _CharT, typename _Traits, typename _Alloc> basic_istream<_CharT, _Traits>& operator>>(basic_istream<_CharT, _Traits>& __is, basic_string<_CharT, _Traits, _Alloc>& __str); template<typename _CharT, typename _Traits, typename _Alloc> inline basic_ostream<_CharT, _Traits>& operator<<(basic_ostream<_CharT, _Traits>& __os, const basic_string<_CharT, _Traits, _Alloc>& __str)
由于ifstream和ofstream派生于istream和ostream,所以fstream同样支持对文件流filebuf的输入输出操作。例如下例通过输入输出操作符读取文件中的数据:
#include <iostream> #include <fstream> #include <string> using namespace std; int main () { string fileName = "test.txt"; ofstream outOpt(fileName.c_str(), ios_base::out | ios_base::trunc); if(!outOpt) { cout<<fileName<<" open failed..."<<endl; return -1; } outOpt<<"1 2 3 test text"; outOpt.close(); int a[3]; string b[2]; ifstream inOpt(fileName.c_str()); inOpt>>a[0]>>a[1]>>a[2]; //以空白符为分隔,从文件缓冲区中读取三个整数 inOpt>>b[0]>>b[1]; //以空白符为分隔,从文件缓冲区读取两个string数据(string类型对operator <<进行类重载,所以可以支持该操作) cout<<a[0]<<' '<<a[1]<<' '<<a[2]<<endl; cout<<b[0]<<' '<<b[1]<<endl; inOpt.close(); return 0; }
-
getline操作
在文件I/O的过程中,我们经常要处理的是按行对数据进行分析处理,istream类内定义了getline成员函数支持以特定的size或delimiter(默认为换行符)来提取定长或以特定分隔符分开的数据。同样,string头文件中也提供了getline全局函数,支持从istream类中,以特定的size或delimiter(默认为换行符)来提取数据。定义如下:
//istream类内定义,提供从输入流中读取一行数据到char*中 __istream_type& getline(char_type* __s, streamsize __n, char_type __delim); //delim默认为换行 //string头文件定义(实际在basic_string.h中), 提供从输入流中读取一行数据到string中 template<typename _CharT, typename _Traits, typename _Alloc> basic_istream<_CharT, _Traits>& getline(basic_istream<_CharT, _Traits>& __is, basic_string<_CharT, _Traits, _Alloc>& __str, _CharT __delim); //delim默认为换行
使用如下:
#include <iostream> #include <fstream> #include <string> using namespace std; int main() { ifstream inFile("test.txt"); if(!inFile) { cout<<"open test.txt failed..."<<endl; return -1; } char str1[256] = ""; inFile.getline(str1, 255, '\n'); string str2; getline(inFile, str2); cout<<str1<<endl; cout<<str2<<endl; }
-
获取文件的全部内容的seekg和tellg
为了对文件高效的操作时常要用到文件流的定位功能,在ios_base类中,定义了如下的成员,
typedef _Ios_Seekdir seekdir; static const seekdir beg = _S_beg; //文件流开头 static const seekdir cur = _S_cur; //当前所在文件流的位置 static const seekdir end = _S_end; //文件流尾部
在istream中定义了tellg和seekg,ostream中定义了tellp和seekp,分别用来获取当前操作所在文件流的位置和进行文件流的操作的定位。如下是通过seek操作来读取整个文件的内容到内存中:
#include <iostream> #include <fstream> int main () { std::ifstream is ("test.txt", std::ios_base::binary); if (is) { //获取文件的大小 is.seekg (0, std::ios_base::end); //定位到文件结尾 int length = is.tellg(); //获取当前所在文件流的位置,由于已经定位文件结尾,所以是获取文件流大小 is.seekg (0, is.beg); //重新将pos定位到文件流开始 char * buffer = new char [length]; is.read (buffer,length); is.close(); // 输出到屏幕 std::cout.write (buffer,length); delete[] buffer; } return 0; }
内存数据的 I/O操作
C++ I/O标准库支持内存数据的I/O的操作,在sstream头文件中定义了 istringstream,ostringstream,stringstream,只需要将流与内存string对象绑定,就可以进行I/O操作。上面三个I/O类分别用来读取内存string对象到指定对象中,将指定对象中的数据写写入string对象,stringstream可用来读写string对象。
在上面I/O文件操作一节已经说了在istream和ostream中定义的标准输入输出操作,由派生关系可知,对于istringstream,ostringstream同样可以进行标准输入输出操作,对stringstream的stringbuf进行操作(与iostream的streambuf,fstream的filebuf类似,这里暂不介绍)。对于stringstream类常用的操作如下:
stringstream ss(mystr)//创建ss对象,stream流中存放string类型的mystr的数据; ss.str() //返回ss流中的数据,返回类型为string类型 ss.str(mystr) //设置ss流中数据为mystr的内容
具体使用如下:
#include <iostream> #include <sstream> #include <string> using namespace std; int main() { istringstream istream; istream.str("1 2 3 hello"); int a, b, c; string str1; istream>>a>>b>>c>>str1; //从流中读取数据到对象中(以空白符为分割符) cout<<a<<' '<<b<<' '<<c<<' '<<str1<<endl; ostringstream ostream; ostream<<a<<' '<<b<<' '<<c<<' '<<str1; //向流中写数据 string out = ostream.str(); cout<<out<<endl; }