I/O
数据的输入和输出(input/output简写为I/O),对标准输入设备和标准输出设备的输入输出简称为标准I/O。对在外存磁盘上的文件输入输出简称为文件I/O。对内存中指定的字符串存储空间的输入输出简称为串I/O。
流
数据输入输出的过程,可以形象地看成流。从流中获取数据地操作称为“提取”(输入操作)。向流中添加数据的操作称为“插入”(输出)操作。
流类库继承体系
- 流类库具有两个平行的基类:streambuf和ios类,所有流类均以两者之一作为基类。
- streambuf类提供对缓冲区的低级操作:设置缓冲区、对缓冲区指针操作、向缓冲区存/取字符。
- ios_base、ios类记录流状态,支持对streambuf的缓冲区输入/输出的格式化或非格式化转换。
- strstreambuf:使用串保存字符序列。扩展streambuf在缓冲区提取和插入操作。
- filebuf:使用文件保存字符序列。包括打开文件、读/写、查找字符。
四个输入输出对象
C++为用户进行标准I/O操作定义了四个类对象:cin,cout,cerr和clog。cin为istream流类的对象,代表标准输入设备键盘,后三个为ostream流类的对象。cout代表标准输出设备显示器。cerr和clog含义相同,均代表错误信息输出设备显示器。
ostream流的操作
1.operator<<
为什么可以连续使用<<操作?<<返回值是一个ostream的引用,可以连续使用<<重载。<<提供了基本类型的重载。
2.put
put操作输出单个字符,返回一个ostream的引用。
cout.put('A').put('B').put('C');
3.write
write操作输出缓冲区指定的长度,返回一个ostream的引用。
char buf[] = "testing!!!";
cout.write(buf, 8);
istream流的操作
1.operator>>
返回值是一个istream的引用,可以连续使用>>重载。>>提供了基本类型的重载。
2.get
get操作读取单个字符,返回一个整数为字符的ASCII码。
int ch = cin.get();
cout << ch << endl;
还可以接收一个引用。
char ch, ch2;
cin.get(ch).get(ch2);
cout << ch << " " << ch2 << endl;
3.getline
getline操作读取一行(遇到回车键)。返回istream对象的引用。getline()操作与>>的区别:getline获取一个整行,>>遇到空格停止。
char buf[10] = { 0 };
cin.getline(buf, 10);
cout << buf << endl;
char buf2[10] = { 0 };
cin >> buf2;
cout << buf2 << endl;
4.read
read操作返回一个istream对象的引用,对空白字符照度不误,读取完指定数量的字符才结束。
char buf[10] = { 0 };
cin.read(buf, 5);
cout << buf << endl;
5.peek与putback
peek查看而不读取;putback将一个字符添加到流。
char c[10], c2, c3;
c2 = cin.get();
c3 = cin.get();
cin.putback(c2);
cin.getline(&c[0], 9);
cout << c << endl;
文件流
ofstream,由ostream派生而来,用于写文件。
ifstream,由istream派生而来,用于读文件。
fstream,由iostream派生而来,用于读写文件。
打开文件
说明了流对象之后,可使用函数open()打开文件。文件的打开即是在流与文件之间建立一个连接。函数原型:
void open(const char* filename,int mode = ios::out,int prot = _SH_DENYNO);
mode表示文件打开模式,prot保护模式。判断文件打开成功有5种方式:
#include <iostream>
#include <fstream>
#include <cassert>
using namespace std;
int main()
{
ofstream fout;
fout.open("test.txt");
//1
if (fout.is_open())
{
cout << "succ" << endl;
}
else
cout << "failed" << endl;
//2
if (fout.good())
{
cout << "succ" << endl;
}
else
cout << "failed" << endl;
//3
if (fout)
{
cout << "succ" << endl;
}
else
cout << "failed" << endl;
//4
if (!fout)
{
cout << "failed" << endl;
}
else
cout << "succ" << endl;
//推荐使用断言方式
assert(fout);
fout.close();
return 0;
}
文件打开模式
打开方式 | 描述 |
---|---|
iso::in | 打开一个供读取的文件(ifstream的默认值) |
iso::out | 打开一个供写入的文件(ofstream的默认值) |
iso::app | 在写之前找到文件尾 |
iso::ate | 打开文件后立即将文件定位在文件尾 |
iso::trunc | 废弃当前文件内容 |
iso::nocreate(已不再支持) | 如果要打开的文件并不存在,那么以此函数调用open()函数将无法进行 |
iso::noreplace(已不再支持) | 如果要打开的文件已存在,试图用open()函数打开时将返回一个错误 |
iso::binary | 以二进制的形式打开一个文件,默认为文本文件 |
保护模式
#define _SH_DENYRW 0x10 /* deny read/write mode */拒绝对文件进行读写
#define _SH_DENYWR 0x20 /* deny write mode */拒绝写入文件
#define _SH_DENYRD 0x30 /* deny read mode */拒绝文件的读取权限
#define _SH_DENYNO 0x40 /* deny none mode */读取和写入许可
#define _SH_SECURE 0x80 /* secure mode */共享读取,独占写入
文件打开的几点说明
- 文件打开也可以通过构造函数打开,例如:
ofstream fout("test.txt,ios::out");
-
文件的打开方式可以为上述的一个枚举常量,也可以为多个枚举常量构成的按位或表达式。
-
使用open成员函数打开一个文件时,若由字符指针参数所指定的文件不存在,则建立该文件。
-
当打开方式中不含有ios::ate或ios::app选项时,则文件指针被自动移到文件的开始位置,即字节地址为0的位置。
-
从效果上看ofstream指定out模式等同于指定了out和trunc模式。
-
默认情况下,fstream对象以in和out模式同时打开。
-
当文件同时以in和out打开时不会清空。
-
如果只使用out模式,而不指定in模式,则文件会清空现有数据。
-
如果同时指定了out与app,不会清空。
-
如果打开文件时指定了trunc模式,则无论是否同时制定了in模式,文件同样会被清空。
流状态
打开方式 | 描述 |
---|---|
ios::goodbit | 一切正常,没有错误发生,也没有输入结束 |
ios::eofbit | 输入结束 |
ios::failbit | I/O操作失败,主要原因是非法数据(例如试图读数字时遇到字母)。流可以继续使用,输入结束时也将设置failbit位。 |
ios::badbit | 发生了(或许是物理上的)致命性错误。流将不能使用。 |
对应于这个标志字各状态位,ios类还提供了以下成员函数来检测或设置流的状态:
bool rdstate(); //返回流的当前状态标志字
bool eof(); //返回非0值表示到达文件尾
bool fail(); //返回非0值表示操作失败
bool bad(); //返回非0值表示出现错误
bool good(); //返回非0值表示流操作正常
bool clear(int flag=0); //将流的状态设置为flag
为提高程序的可靠性,应在程序中检测I/O流的操作是否正常。当检测到流操作出现错误时,可以通过异常处理来解决问题。
文件关闭
每个文件流类中都提供有一个关闭文件的成员函数close()。功能:当打开的文件操作结束后,就需要关闭它,使文件流与对应的物理文件断开联系,并能够保证最后输出到文件缓冲区中的内容,无论是否已满,都将立即写入到对应的物理文件中。文件流对应的文件被关闭后,还可以利用该文件流调用open成员函数打开其他的文件,最好先clear 一下。
文件读写
1.>>和<<
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
ofstream fout("test.txt");
fout << "abcd" << " " << 200;
fout.close();
ifstream fin("test.txt");
string s;
int n;
fin >> s >> n;
cout << s << " " << n << endl;
return 0;
}
2.put和get
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
using namespace std;
int main()
{
ofstream fout("test2.txt");
char ch;
assert(fout);
for (int i = 0; i < 26; i++)
{
ch = 'A' + i;
fout.put(ch);
}
fout.close();
ifstream fin("test2.txt");
while (fin.get(ch))
{
cout << ch;
}
return 0;
}
3.read和write
后面二进制文件读写介绍。
4.文本模式与二进制模式打开区别
如果以文本打开文件,写入字符的时候,遇到\n会作转换。windows平台\n会转为\r\n,linux平台保留不变,mac系统\n转换为\r。\r不做转换。如果以二进制方式打开文件写入字符时不做转换。以文本方式打开文件,也可以写入二进制数据,以二进制方式打开文件,也可以写入文本。写入的数据是二进制还是文本,与打开方式无关,与写入使用的函数有关。要写入二进制数据应该用write,相应的读要用read。
二进制文件的读写
二进制文件不同于文本文件,它可用于任何类型的文件(包括文本文件)。对二进制文件的读写可采用从istream类继承下来的成员函数read()和ostream类继承下来的成员函数write()。文件打开操作时使用枚举常量ios::binary,例如:
ofstream fout("binary.dat",ios::out|ios::binary);
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
using namespace std;
struct Test
{
int a;
int b;
};
int main()
{
Test test = { 100,200 };
ofstream fout("binary.txt", ios::out | ios::binary);
fout.write(reinterpret_cast<char*>(&test),sizeof(Test));
fout.close();
Test test2;
ifstream fin("binary.txt", ios::in | ios::binary);
fin.read(reinterpret_cast<char*>(&test2),sizeof(Test));
cout << test2.a << " " << test2.b << endl;
return 0;
}
当字符串足够长时文件读写
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
using namespace std;
struct Test
{
int x;
string a;
string b;
};
int main()
{
Test t1;
t1.x = 100;
t1.a = "asdfsdafasfdasdfasdffsdfsdafadsadsfdfsfdsafdasfdsadfasdsfaasfddsfsadfsdfafdsasadffads";
t1.b = "qwerqwerqrwewqrrqewewrewrqrqewerwqrewqqwerrweqrewqreqwerqwrqewrqweqrwerweqqrwerqwerwesfadsfafdfadsasfasdafsqrw";
ofstream fout("test4.txt", ios::out | ios::binary);
fout.write((char*)(&t1.x),sizeof(int));
int len;
len = t1.a.length();
fout.write((char*)&len, sizeof(int));
fout.write(t1.a.data(), t1.a.length());
len = t1.b.length();
fout.write((char*)&len, sizeof(int));
fout.write(t1.b.data(), t1.b.length());
fout.close();
ifstream fin("test4.txt", ios::in | ios::binary);
Test t2;
fin.read((char*)(&t2.x),sizeof(int));
fin.read((char*)(&len), sizeof(int));
t2.a.resize(len);
fin.read(&t2.a[0], len);
fin.read((char*)(&len), sizeof(int));
t2.b.resize(len);
fin.read(&t2.b[0], len);
cout << t2.x << " " << t2.a << " " << t2.b << endl;
return 0;
}
当前文件流活动指针
文件流指针用以跟踪发生I/O操作的位置。每当从流中读取或写入一个字符,当前活动指针就会向前移动。当打开方式中不含有ios::ate或ios::app选项时,则文件指针被自动移到文件的开始位置,即字节地址为0的位置。
文件的随机读写seekp和seekg
- seekp:设置输出文件流的文件流指针位置。
- seekg:设置输入文件流的文件流指针位置。
函数参数:pos新的文件流指针位置,off需要偏移的值,dir搜索的起始位置。
文件的随机读写tellp和tellg
- tellp:获得输出的文件流指针的当前位置,以字节为单位。
- tellg:获得输入的文件流指针的当前位置,以字节为单位。
函数返回值实际上是一个long类型。
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
using namespace std;
struct Test
{
int x;
string a;
string b;
};
int main()
{
ifstream fin("test5.txt");
assert(fin);
fin.seekg(2);
char ch;
fin.get(ch);
cout << ch << endl;
fin.seekg(-1, ios::end);
fin.get(ch);
cout << ch << endl;
fin.seekg(0, ios::end);
streampos pos = fin.tellg();
cout << pos << endl;
return 0;
}
seek_dir
dir参数用于对文件流指针的定位操作上,代表搜索的起始位置。在ios中定义的枚举类型:
enum seek_dir{beg,cur,end};
beg表示文件流的起始位置,cur表示文件流的当前位置,end表示文件流的结束位置。
输出流的格式化
数据输入输出的格式控制使用系统头文件中提供的操纵符。把它们作为插入操作符<<的输出对象即可。如setiosflags、setw、setfill、setprecision、hex、oct等。
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int n = 64;
double d = 123.45;
double d2 = 0.0187;
//通过操纵子方式进行格式化输出
cout << "====================宽度控制====================" << endl;
cout << n << "#" << endl;
cout << setw(10) << n << "#" << endl; //默认右对齐
cout << setw(10) << n << "#" << n << endl; //宽度控制不会影响下一个输出
cout << "====================对齐控制====================" << endl;
cout << setw(10) << setiosflags(ios::left) << n << "#" << endl; //设置左对齐
cout << setw(10) << n << "#" << endl; //对齐控制影响下一个输出
cout << setw(10) << setiosflags(ios::right) << n << "#" << endl; //设置右对齐
cout << setw(10) << resetiosflags(ios::left) << n << "#" << endl; //取消左对齐
cout << "====================对齐控制====================" << endl;
cout << setw(10) << setfill('?') << n << "#" << endl;
cout << setw(10) << n << "#" << endl; //填充控制影响下一个输出
cout << setw(10) << setfill(' ') << n << "#" << endl;
cout << "====================精度控制====================" << endl;
cout << setprecision(4) << d << endl; //保留四位有效数字
cout << setprecision(2) << d2 << endl;
cout << setiosflags(ios::fixed);
cout << setprecision(4) << d << endl; //保留小数点后四位
cout << setprecision(2) << d2 << endl;
cout << "====================进制输出====================" << endl;
cout << n << endl;
cout << dec << n << endl;
cout << oct << n << endl;
cout << hex << n << endl;
cout << endl;
cout << setiosflags(ios::showbase);
cout << dec << n << endl;
cout << oct << n << endl;
cout << hex << n << endl;
cout << endl;
cout << setbase(10) << n << endl;
cout << setbase(8) << n << endl;
cout << setbase(16) << n << endl;
return 0;
}
通过调用流的成员函数控制格式,如setf、unsetf、width、fill、precision等。优点是在设置格式同时,可以返回以前的设置,便于恢复原来的设置。
#include <iostream>
using namespace std;
int main()
{
int n = 64;
double d = 123.45;
double d2 = 0.0187;
cout << "====================宽度控制====================" << endl;//宽度控制不会影响下一个输出
cout << n << "#" << endl;
cout.width(10);
cout << n << "#" << endl;
cout << n << "#" << endl;
cout << "====================对齐控制====================" << endl;//对齐控制影响下一个输出
cout.width(10);
cout.setf(ios::left);
cout << n << "#" << endl;
cout.width(10);
cout << n << "#" << endl;
cout.width(10);
cout.unsetf(ios::left);
cout << n << "#" << endl;
cout.width(10);
cout.setf(ios::right);
cout << n << "#" << endl;
cout << "====================对齐控制====================" << endl;//填充控制影响下一个输出
cout.width(10);
cout.fill('?');
cout << n << "#" << endl;
cout.width(10);
cout << n << "#" << endl;
cout.width(10);
cout.fill(' ');
cout << n << "#" << endl;
cout << "====================精度控制====================" << endl;
cout.precision(4);//保留四位有效数字
cout << d << endl;
cout.precision(2);
cout << d2 << endl;
cout.setf(ios::fixed);
cout.precision(4);//保留小数点后四位
cout << d << endl;
cout.precision(2);
cout << d2 << endl;
cout << "====================进制输出====================" << endl;
cout.setf(ios::showbase);
cout << n << endl;
cout.unsetf(ios::dec);
cout.setf(ios::oct);
cout << n << endl;
cout.unsetf(ios::showbase);
cout << n << endl;
return 0;
}