1 基本概念
1.1 流/流数据(Stream)
计算机里所有的数据(二进制数据、文本、图形、音视频等)都是字节序列形式的数据,
它们犹如流水一般,从一个对象流向另一个对象,称为流/流数据。
分为输出流、输入流。
1.2 输出流(Output Stream)
数据从内存对象流向表示输出设备(显示器、打印机、磁盘文件等)的对象:
如:cout << student;
1.3 输入流(Input Stream)
数据从表示输入设备(键盘、磁盘文件等)的对象流向内存对象:
如:cin >> student;
1.4 流缓冲区(Steam Buffer)
介于各种I/O设备和内存对象之间的内存缓冲区。也就是流对象所维护的缓冲区。
1)当向显示器输出时,数据首先通过流操作符<<,从内存对象进入输出流缓冲区,直到缓冲区满或遇到换行符,才将其中的数据灌注到显示器上显示出来:
2)当从键盘输入时,数据首先进入到键盘缓冲区,直到按下回车键,才将键盘缓冲区中的数据灌注到输入流缓冲区,之后通过流操作符>>,进入内存对象:
1.5 流对象(Stream Object)
表示各种输入输出设备的对象,如键盘、显示器、打印机、磁盘文件等,
因其皆以流的方式接收或提供数据,故称为流对象。
三个预定义的标准流对象:
cin:标准输入设备——键盘
cout:标准输出设备——显示器
cerr:标准错误输出设备——显示器(不带缓冲)
1.5 流类(Stream Class)
用于定义流对象的类。
流类 istream_withassign 的对象是 cin
流类 ostream_withassign 的对象是 cout
1.6 流类库(Stream Class Library)
C++以继承的方式定义了一组流类,并将其作为C++标准库的一部分,提供给用户。
基于流类库可以构建三种形式的流对象:
面向控制台的I/O流:cin cout cerr
面向文件的I/O流: 自己定义
面向内存的I/O流: 自己定义
1)只有蓝色和红色共9个类,针对具体目标执行具体操作
2)其中蓝色的3个类已经预定义了cin/cout/cerr流对象
3)实际编程中主要使用红色的6个类实现针对文件和内存的I/O
4)出于某些原因,所有I/O流类都不支持拷贝构造和拷贝赋值(私有了)
#include <iostream> // 最熟悉、最常加的头文件
ios
istream ostream iostream
istream_withassign ostream_withassign iostream_withassign
#include <fstream>
ifstream 读文件 ofstream 写文件 fstream
#include <strstream>
istrstream 读内存 ostrstream 写内存 strstream
#include <sstream>
istringstream 读内存 ostringstream 写内存 stringstream
2 类类型转换为bool
任何基本类型的数据,都可隐式转换(编译自帮我们转)为bool类型。
任何符合类型的数据(类对象),需要通过手写类型转换操作符函数转换为bool类型。
C++程序经常将类对象放在6种bool上下文中:
class A { ... }; A a;
bool b = a; // 初始化
bool c; c = a; // 赋值
if(a) {...} // if语句的判定条件
while(a) {...} // while循环的判定条件
for(; a; ) {...} // for循环的判定条件
!a // 逻辑反
一旦出现上述6种bool上下文,编译器会将 类对象 转换为 bool类型。
C++标准库中封装的流对象(cout、cin等)允许我们将其放在bool上下文中,来判断IO操作(打开文件、读文件、写文件等)是否成功。
// objbool.cpp 类对象 转换为 bool类型
#include <iostream>
#include <cstdio>
using namespace std;
class Integer {
public:
Integer( int i ) : m_i(i) {
//【int m_i=i;】
}
operator bool( /* const Integer* this */ ) const {
cout << "Integer类的类型转换操作符函数被调用" << endl;
return this->m_i;
}
private:
int m_i;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
Integer ix(8888), iy(0);
bool a = ix; // 初始化 bool a = ix.operator bool()
cout << "a=" << a << endl;
bool b;
b = iy; // 初值 b=iy.operator bool()
cout << "b=" << b << endl;
if( ix ) { // ix.operator bool()
cout << "if语句判定条件为true" << endl;
}
while( ix ) { // ix.operator bool()
cout << "while循环的判定条件为true" << endl;
break;
}
for( ; ix ; ) { // ix.opereator bool()
cout << "for循环的第二个判定条件为true" << endl;
break;
}
cout << !ix << endl; // ix.operator bool()
return 0;
}
3 IO流打开和关闭
3.1 通过构造函数打开I/O流
打开输入流: ifstream (const char* filename, openmode mode) ;
打开输出流: ofstream (const char* filename, openmode mode) ;
打开输入输出流: fstream (const char* filename, openmode mode) ;
3.2 打开模式(输出文件流,文件用来写)
ios::out 适用于ofstream(缺省) / fstream
打开文件用于写入,不存在则创建,存在则清空
ios::app 适用于ofstream / fstream
打开文件用于追加,不存在则创建,存在不清空
ios::trunc 适用于ofstream / fstream
打开时清空原内容,(同ios::out)
ios::binary 适用于ifstream / ofstream / fstream
以二进制模式读写
// ofstream.cpp
#include <iostream>
#include <fstream>
using namespace std;
// C++标准库中设计的 ofstream(输出文件流类) 类
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
ofstream ofs1("./ofs", ios::out );
if( !ofs1 ) { // ! ofs1.operator bool()
cerr << "ofs1流对象状态错误--打开文件失败" << endl;
}
ofs1 << 1234 << ' ' << 56.78 << ' ' << "hello" << '\n';
if( !ofs1 ) { // ! ofs1.operator bool()
cerr << "ofs1流对象状态错误--写文件失败" << endl;
}
ofs1.close();
ofstream ofs2("./ofs", ios::app);
if( !ofs2 ) { // ! ofs2.operator bool()
cerr << "ofs2流对象状态错误--打开文件失败" << endl;
}
ofs2 << "world" << endl;
if( !ofs2 ) { // ! ofs2.operator bool()
cerr << "ofs2流对象状态错误--写文件失败" << endl;
}
ofs2.close();
return 0;
}
3.3 打开模式(输入文件流,文件用来读)
ios::in 适用于ifstream(缺省) / fstream
打开文件用于读取,不存在则失败,存在不清空
ios::ate 适用于ifstream / fstream
打开文件时定位文件尾(拿到文件大小,方便开缓冲区来接)
ios::binary 适用于ifstream / ofstream / fstream
以二进制模式读写
// ifstream.cpp
#include <iostream>
#include <fstream>
using namespace std;
// C++标准中设计的 ifstream(输入文件流类) 类
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
ifstream ifs1("./ofs", ios::in);
if( !ifs1 ) { // ! ifs1.operator bool()
cerr << "ifs1流对象状态错误--打开文件失败" << endl;
}
int i; double d; string s1, s2;
ifs1 >> i >> d >> s1 >> s2;
if( !ifs1 ) { // ! ifs1.operator bool()
cerr << "ifs1流对象状态错误--读文件失败" << endl;
}
cout << i << ' ' << d << ' ' << s1 << ' ' << s2 << endl;
ifs1.close();
ifstream ifs2("./ofs", ios::ate);
if( !ifs2 ) { // ! ifs2.operator bool()
cerr << "ifs2流对象状态错误--打开文件失败" << endl;
}
ifs2.seekg(0,ios::beg); // 调整读写位置
int ii; double dd; string ss1,ss2;
ifs2 >> ii >> dd >> ss1 >> ss2;
if( !ifs2 ) { // ! ifs2.operator bool()
cerr << "ifs2流对象状态错误--读文件失败" << endl;
}
cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl;
ifs2.close();
return 0;
}
3.4 关闭IO流对象
IO流对象名.close()
4 IO流对象的状态
4.1 0 1 2 4
I/O流类对象内部保存当前状态,其值为以下常亮的位或:
ios::goodbit 0 一切正常
ios::badbit 1 发生致命错误
ios::eofbit 2 遇到文件尾
ios::failbit 4 打开文件失败 或 实际读写字节数未达预期
4.2 to bool
I/O流类对象支持到bool类型的隐式转换:
发生1 2 4等情况,返回false,否则返回true
将I/O流对象直接应用到布尔上下文中,即可实现转换
4.3 复位
处于1或4状态的流,在复位前无法工作。
// iostate.cpp
#include <iostream>
#include <fstream>
using namespace std;
// C++标准中设计的 ifstream(输入文件流类) 类
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
ifstream ifs2("./ofs", ios::ate);
if( !ifs2 ) { // ! ifs2.operator bool()
cerr << "ifs2流对象状态错误--打开文件失败" << endl;
}
int ii; double dd; string ss1,ss2;
cout << "--------------------------第一次读取数据------------------------" << endl;
ifs2 >> ii >> dd >> ss1 >> ss2;
if( !ifs2 ) { // ! ifs2.operator bool()
cerr << "ifs2流对象状态错误--读文件失败" << endl;
cerr << "ifs2具备0状态吗?" << ifs2.good() << ", ifs2具备1状态吗?" << ifs2.bad()
<< ", ifs2具备2状态吗?" << ifs2.eof() << ", ifs2具备4状态吗?" << ifs2.fail() << endl;
cerr << "ifs2具体的状态值: " << ifs2.rdstate() << endl;
}
cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl;
ifs2.clear(); //先clear为正常状态,seekg才工作,后续读取才正常
ifs2.seekg( 0, ios::beg );
cout << "--------------------------第二次读取数据------------------------" << endl;
ifs2 >> ii >> dd >> ss1 >> ss2;
if( !ifs2 ) { // ! ifs2.operator bool()
cerr << "ifs2流对象状态错误--读文件失败" << endl;
cerr << "ifs2具备0状态吗?" << ifs2.good() << ", ifs2具备1状态吗?" << ifs2.bad()
<< ", ifs2具备2状态吗?" << ifs2.eof() << ", ifs2具备4状态吗?" << ifs2.fail() << endl;
cerr << "ifs2具体的状态值: " << ifs2.rdstate() << endl;
}
cout << ii << ' ' << dd << ' ' << ss1 << ' ' << ss2 << endl;
ifs2.close();
return 0;
}
5 二进制IO(原样,不做修改)
读取二进制数据:istream& istream::read (char* buffer, streamsize num);
1)从输入流中读取num个字节到缓冲区buffer中 !!!
2)返回流对象本身,其在bool上下文中的值,成功(读满)为true,失败(没读满)为false
3)如果没读满num个字节,函数就返回了,比如遇到文件尾,
最后一次读到缓冲区buffer中的字节数,可以通过istream::gcount()函数获得。
获取读长度:streamsize istream::gcount (void);
1)返回最后一次从输入流中读取的字节数
写入二进制数据:ostream& ostream::write (const char* buffer, streamsize num);
1)将缓冲区buffer中的num个字节写到输出流中 !!!
2)返回流本身,其在bool上下文中的值,成功(写满)为true,失败(没写满)为false
// binaryIO1.cpp
#include <iostream>
#include <fstream>
using namespace std;
// C++标准中设计的 / ifstream(输入文件流类) 类
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
ofstream ofs("./zjw", ios::out);
if( !ofs ) {
cerr << "ofs流对象状态错误--打开文件失败" << endl;
}
ifstream ifs("./getline", ios::in);
if( !ifs ) {
cerr << "ifs流对象状态错误--打开文件失败" << endl;
}
char buf[3];
while(1) {
ifs.read(buf,3);
if( ifs ) {
// 读满3个字节
ofs.write(buf,3);
} else {
// 没读满3个字节
int len = ifs.gcount();
ofs.write(buf,len);
break;
}
}
ofs.close();
ifs.close();
return 0;
}
6 非格式化IO(按个、按行)
6.1 字符
写入字符:ostream& ostream::put (char ch);
一次向输出流写入一个字符,返回流对象本身
刷输出流:ostream& ostream::flush (void);
将输出流缓冲区中的数据刷到设备上,返回流对象本身
读取字符:int istream::get (void); // 无参
成功返回读到的字符,失败返回EOF
istream& istream::get (char& ch); // 单参
返回输入流本身。其在bool上下文中的值,成功为true,失败或遇到文件尾为false
// noformat.cpp
#include <iostream>
#include <fstream>
using namespace std;
// C++标准中设计的 ofstream(输出文件流类) / ifstream(输入文件流类) 类
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
ofstream ofs("./noformat", ios::out);
if( !ofs ) {
cerr << "ofs流对象状态错误--打开文件失败" << endl;
}
for( char c=' '; c<='~'; c++ ) {
ofs.put(c).flush();
}
ofs.close();
ifstream ifs("./noformat", ios::in);
if( !ifs ) {
cerr << "ifs流对象状态错误--打开文件失败" << endl;
}
char c;
// 单参get
while(1) {
ifs.get(c);
if( ifs ) {
cout << c;
} else {
break;
}
}
cout << endl;
ifs.clear();
ifs.seekg(0, ios::beg);
// 无参get
while(1) {
c = ifs.get();
if( c!=EOF ) {
cout << c;
} else {
break;
}
}
cout << endl;
ifs.close();
return 0;
}
6.2 行
读取行:istream& istream::getline (char* buffer, streamsize num, char delim = '\n');
1)读取字符(至定届符)到buffer中
2)若读取了num个字符还未读取定界符,第num个字符设置为空字符'\0',返回(输入流对象
状态为4 :( )。
3)如果因为遇到定界符(缺省为'\n')返回(输入流对象状态为0 :) ),
定界符被读取并丢弃,追加结尾空字符'\0',
读指针停在该定界符的下一个位置,即第二行行首。
4)遇到文件尾,返回(输入流对象状态为6 :( )。
// getline.cpp 预置:getline文件,aa\n bbbb\n cccccc\n dddddddd\n 0123456789\n
#include <iostream>
#include <fstream>
using namespace std;
// C++标准中设计的 / ifstream(输入文件流类) 类
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
ifstream ifs("./getline", ios::in);
if( !ifs ) {
cerr << "ifs流对象状态错误--打开文件失败" << endl;
}
char buf[256];
while(1) {
ifs.getline(buf,256,'\n');
if( ifs ) {
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; //0
cout << buf << endl;
} else {
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; //6
break;
}
}
/*
ifs.getline(buf,256,'\n');
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; //0
cout << buf << endl;
ifs.getline(buf,256,'\n');
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; //0
cout << buf << endl;
ifs.getline(buf,256,'\n');
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; //0
cout << buf << endl;
ifs.getline(buf,256,'\n');
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; //0
cout << buf << endl;
ifs.getline(buf,256,'\n');
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; //0
cout << buf << endl;
ifs.getline(buf,256,'\n');
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; // 6
cout << buf << endl;
*/
// ifs.clear()
ifs.close();//对象关闭后,会将文件尾2变为文件首0,对象保留4状态。建议先clear()
cout << "ifs流对象的状态值: " << ifs.rdstate() << endl; // 4
return 0;
}
7 格式化IO
7.1 流函数(一组成员函数)
I/O流类(ios)定义的一组用于控制输入输出格式的公有成员函数,
调用这些函数可以改变I/O流对象内部的格式状态,
进而影响后续输入输出的格式化方式。
// 流函数 示例代码
#include <iostream>
#include <cmath> // sqrt()
using namespace std;
int main(void){
cout.precision (10);
cout << sqrt (200) << '\n'; // 14.14213562
cout << cout.precision () << '\n' ; //10
cout.setf (ios::scientific, ios::floatfield);
cout << sqrt (200) << '\n'; // 1.4142135624e+01
cout.width (10);
cout.fill ('-');
cout.setf (ios::internal, ios::adjustfield);
cout.setf (ios::showpos);
cout << 12345 << '\n'; // +----12345
return 0;
}
7.2 流控制符(一组全局函数)
标准库提供的一组特殊的全局函数,
有的带参(在iomanip头文件中声明),有的不带参(在iostream头文件中声明)。
因可被直接嵌入到输入输出表达式中,影响后续输入输出格式,称为流控制符。
// 流控制符 示例代码
#include<iostream>
#include<iomanip>
#include<cmath> // sqrt()
using namespace std;
int main(void){
cout << setprecision (10) << sqrt (200) << endl; // 14.14213562
cout << cout.precision () << endl; // 10
cout << scientific << sqrt (200) << endl; // 1.4142135624e+01
cout << setw (10) << setfill ('-') << internal << showpos << 12345 << endl;
// +----12345
return 0;
}
8 读写指针 和 随机访问
8.1 设置读/写指针位置
istream& istream::seekg (off_type offset, ios::seekdir origin); // 读指针
ostream& ostream::seekp (off_type offset, ios::seekdir origin); // 写指针
1)origin表示偏移量offset的起点:
iso::beg 从文件的第1个字节
ios::cur 从文件的当前位置
ios::end 从文件最后一个字节的下一个位置
2)offset为 负 / 正 表示想文件 头 / 尾 的方向偏移。
3)读/写指针被移到文件头之前或文件尾之后,则失败。
8.2 获取读/写指针位置
pos_type istream::tellg (void);
pos_type ostream::tellp (void);
1)返回读/写指针当前位置 相对于 文件头 的字节偏移量
// binaryIO2.cpp
#include <iostream>
#include <fstream>
using namespace std;
// C++标准中设计的 / ifstream(输入文件流类) 类
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {
ofstream ofs("./zjw", ios::out);
if( !ofs ) {
cerr << "ofs流对象状态错误--打开文件失败" << endl;
}
ifstream ifs("./getline", ios::ate); //文件尾,用来得到文件大小
if( !ifs ) {
cerr << "ifs流对象状态错误--打开文件失败" << endl;
}
int len = ifs.tellg(); //获取读指针位置->尾,即文件大小
char buf[len];
ifs.seekg(0, ios::beg); //设置读指针位置->首
ifs.read(buf,len); // 只读1次硬盘 :)
ofs.write(buf,len); // 只读1次硬盘 :)
ofs.close();
ifs.close();
return 0;
}
9 内存流(字符串流)
上述章节为文件流,本章为内存流(字符串流)。
9.1 输出字符串流
#include <sstream>
ostringstream oss;
oss << 1234 << ' ' << 5.67 << 'A' << "BCD" ; // 类似cout,用来输出 <<
string os = oss.str() ;
9.2 输入字符串流
#include <sstream>
string is ("1234 5.67 ABCD") ;
istringstream iss (is) ;
int i ; double d ; string s ;
iss >> i >> d >> s ; // 类似cin,用来输入 >>