在写代码的过程中,我们最常做的事就是io操作,无论是对控制台,还是文件。但一段时间不写代码就忘了,这里理一下C++标准I/O库的具体类和操作。
C++的标准I/O库包括我们经常使用的iostream,fstream,以及不太经常使用的stringstream。前两者是对控制台和文件的I/O操作,stringstream则可以使用I/O操作对内存中的数据进行格式化操作。C++的标准I/O操作相对与C来说,更加的简明,安全,但执行效率会有所下降。
对于编程语言来说,在概念上,从控制终端、磁盘文件以及内存中读取数据都应该不影响I/O操作,C++为了解决支持不同设备的字符流,通过面向对象的思想(废话了,你要不用这个思想,你还是什么C++),通过继承来实现一组类,分别处理控制终端、磁盘文件,以及内存数据的I/O操作,下图是引用cplusplus官网关于输入输出流类继承体系的关系图,自己画了一下,如下:
由上图可以知道,I/O操作的基类是ios_base,各个类的用途如下:
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操作,标准定义了一系列条件状态标志和函数,用来管理流对象,比如说是否可用,以及出现的错误。对于流的状态标志是在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
using std::cout;
using std::cin;
using std::endl;
int main()
{
int a;
cin>>a;
cout<<"goodbit: "<>a;
cout<<"goodbit: "<
输入1 a后显示如下:
1
goodbit: 1 eofbit: 0 failbit: 0 badbit: 0
a
goodbit: 0 eofbit: 0 failbit: 1 badbit: 0
由上可知非法输入会导致I/O失败,failbit流标志生效。
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;
具体含义如下:
以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
inline basic_istream&
operator>>(basic_istream& __in, unsigned char& __c);
template
inline basic_istream&
operator>>(basic_istream& __in, signed char& __c);
template
inline basic_istream&
operator>>(basic_istream& __in, unsigned char* __s);
template
inline basic_istream&
operator>>(basic_istream& __in, signed char* __s);
//string类中对输入输出operator的重载,使其能支持string类型的输入输出
template
basic_istream<_CharT, _Traits>&
operator>>(basic_istream<_CharT, _Traits>& __is,
basic_string<_CharT, _Traits, _Alloc>& __str);
template
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
#include
#include
using namespace std;
int main ()
{
string fileName = "test.txt";
ofstream outOpt(fileName.c_str(), ios_base::out | ios_base::trunc);
if(!outOpt)
{
cout<>a[0]>>a[1]>>a[2]; //以空白符为分隔,从文件缓冲区中读取三个整数
inOpt>>b[0]>>b[1]; //以空白符为分隔,从文件缓冲区读取两个string数据(string类型对operator <<进行类重载,所以可以支持该操作)
cout<
在文件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
basic_istream<_CharT, _Traits>&
getline(basic_istream<_CharT, _Traits>& __is,
basic_string<_CharT, _Traits, _Alloc>& __str, _CharT __delim); //delim默认为换行
使用如下:
#include
#include
#include
using namespace std;
int main()
{
ifstream inFile("test.txt");
if(!inFile)
{
cout<<"open test.txt failed..."<
为了对文件高效的操作时常要用到文件流的定位功能,在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
#include
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文件操作一节已经说了在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
#include
#include
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<