输入/输出流
- I/O(输入/输出)流类库提供对象之间的数据交互服务。
- 流类库预定义了一批流对象,连接常用的外部设备。
- 可以定义所需的I/O流对象,使用流库提供的工作方式实现数据传输。
流类和流对象
程序中,对数据的输入/输出是以字节流实现的
应用程序对字节序列作出各种数据解释
I/O系统的任务就是在内存和外部设备之间稳定可靠地传输数据和解释数据。
流类对象可以建立和删除,可以从流中获取数据和向流添加数据
流的概念
输入/输出是一种基本的数据传递操作,它可以理解为字符序列在计算机
内存与外设之间的流动。 数据从一个对象到另一个对象的流动抽象为流(stream),
实现设备之间交换信息的类称为流类。 按面向对象方法组织的多个流类及其类层次集合构成了I/O流类库,简称为流库。
流库中的每一个流类都定义了一种设备之间的信息交换方式,按信息流动方向的不同,可以分为输入/输出流。
与输入设备(如键盘)相联系的流称为输入流。
与输出设备(如屏幕)相联系的流称为输出流。
与输入输出设备相联系的流称为输入输出流。
流的种类
•文本流(Text Stream)是一串ASCII字符
如数字12在文本流中的表示方法为1与2的ASCII码31H与32H。
源程序文件和文本文件在传送时均采用文本流。
通常,文本流在不同的设备之间传送时,可能要作一些变换(windows 操作系统需要,而在linux下不需要变换),如将换行字符’\n’变换成回车换行两个字符“\r\n” (创建文本文件时) ,或作相反的操作(将文本文件的内容读入内存中时)。
•二进制流(Binary Stream)是由一串二进制数组成,
如数字12在二进制流中的表示方式为00001100。
二进制流是将数据以二进制形式存放的,这种流与设备上的输入输出一
一对应,在数据传送时不需作任何变换。
缓冲流与非缓冲流
系统在主存中开辟的用于临时存放输入/输出流信息的内存区称缓冲区。
输入/输出流也相应的分成缓冲流与非缓冲流。
对于非缓冲流,一旦数据送入流中,立即进行处理。
对于缓冲流,只有当缓冲区满时,或当前送入的数据为新的一
行字符时,系统才对流中的数据进行处理(称为刷新)。
引入缓冲的主要目的是为了提高系统的效率,
因为输入/输出设备的速度要比CPU慢得多,频繁地与外设交换信息必将占用大量的CPU时间,从而降低程序的运行速度。使用缓冲后,CPU只要从缓冲区中取数据或者把数据输入缓冲区,而不要等待设备具体输入/输出操作完成。通常情况下使用缓冲流,但对于某些特殊场合,也可使用非缓冲流。
流类库
C++流类库具有两个平行的基类,即streambuf类和ios类,所有其他的流类都是从它们直接或间接地派生出来的。
其中:
ios类为输入输出操作在用户一方的接口,负责高层操作;
streambuf类为输入输出操作在物理设备一方的接口,负责低层操作。
filebuf:文件缓冲区管理
strstreambuf:字符串缓冲区管理
stdiobuf:标准I/O缓冲区管理
头文件
iostream 包含操作所有输入/输出流所需的基本信息
含有cin、cout、cerr、clog对象,提供无格式和格式化的I/O
iomanip 包含格式化I/O操纵算子,用于指定数据输入输出的格式
fstream 处理文件信息,包括建立文件,读/写文件的各种操作接口
每种C++版本还包含其他一些与I/O相关的库,提供特定系统的某些功能
标准流和流操作
标准流是C++预定义的对象,提供内存与外部设备进行数据交互功能
标准流为用户常用的外部设备提供与内存之间的通信通道,对数据进行解释和传输,提供必要数据缓冲
(1) cin istream 类的对象,通常连向键盘,可以重定向
(2) cout ostream 类的对象,通常连向显示器,可以重定向
(3) cerr ostream 类的对象,连向显示器。不能重定向
(4) clog ostream 类的对象,连向打印机。不能重定向
流的操作是流类的公有成员函数
输入流操作
istream类的公有成员函数
ostream类的公有成员函数
流错误状态
ios 处理流错误状态的公有成员函数
格式控制
ios提供直接设置标志字的控制格式函数
iostream和iomanip库还提供了一批控制符简化I/O格式化操作
文件处理(重点)
各种计算机应用系统通常把一些相关信息组织起来保存在外存储器中,称为文件,并用一个名字(称为文件名)加以标识。
C++把文件看成无结构的字节流,
编码方式: 文本文件 二进制文件
存取方式: 顺序文件 随机文件
ifstream、ofstream 和 fstream 类用于内存与文件之间的数据传输
打开和关闭文件
文件操作的基本步骤:
打开文件
读 / 写文件
关闭文件
1.打开文件
包括建立文件流对象;与外部文件关联;指定文件的打开方式
打开文件有两种方法:
• 先建立流对象,再调用fstream::open()函数连接外部文件
流类(ifstream、ofstream或 fstream) 对象名 ;
对象名 . open ( 文件名 , 方式 ) ;
• 调用流类带参数的构造函数,建立流对象的同时连接外部文件
流类 对象名 ( 文件名 , 方式 ) ;
打开和关闭文件
打开文件
open 函数原型
void open ( const char * , int mode) ;//第一个参数表示相关联的文件名第二个参数表示文件的打开方式
filebuf、ifstream、ofstream、fstream的构造函数具有相同的参数和缺省值
文件流的构造函数和 open ( ) 函数用于打开文件,析构函数在流对象被删除之前关闭文件
打开一个输入文件流 说明类型为 ifstream 的对象
打开一个输出文件流 说明类型为 ofstream 的对象
要建立输入和输出的文件流 说明类型为 fstream 的对象
输入输出是相对于内存来说的
用第一种方式打开文件:
打开一个已有文件datafile.dat,准备读:
ifstream infile ; // 建立输入文件流对象
infile.open( “datafile.dat” , ios::in ) ; // 连接文件,指定打开方式
打开(创建)一个文件newfile.dat,准备写:
ofstream outfile ; // 建立输出文件流对象
outfile.open( “d:\newfile.dat” , ios::out ) ; // 连接文件,指定打开方式
打开文件
open 函数原型
void open ( const char * , int mode) ;
第二种方法 调用文件流类带参数构造函数 : 例1
ifstream infile ( "datafile.dat" , ios::in ) ;
ofstream outfile ( "d:\\newfile.dat" , ios::out );
fstream rwfile ( "myfile.dat" , ios::in | ios::out ) ; //用或运算符 “|” 连接两个表 示打开方式的标识常量
例2 打开一个二进制文件进行追加操作 :
ofstream ofile ( "d:\\binary" , ios :: binary | ios :: app ) ;
// 对文件写操作
// ……
ofile . close ( ) ;
1.打开文件
•如果打开操作失败,则流对象的值为0 •也可以通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开了:
bool is_open();
它返回一个布尔(bool)值:
真(true)代表文件已经被顺利打开,
假( false )则打开操作失败。
•无论用哪一种方法打开文件,都需要在程序中测试文件是否成功打开。
•例:
ofstream ost ;
ost . open ("d:\\test1.txt") ; //默认为文本方式打开
if ( ! ost.is_open() )
{ cout << "can not open file. " ; return 1; }
ifstream ist ( "d:\\test1.txt" ) ; //默认为文本方式打开
if ( !ist ) // ist==0
{ cout << "can not open file. " ; return 1; }
2.关闭文件
关闭文件操作包括:
•把缓冲区数据完整地写入文件
•添加文件结束标志
•切断流对象和外部文件的连接
若流对象的生存期没有结束,可以重用
当一个流对象的生存期结束,系统也会自动关闭文件
例如:
ofstream ofile ; // 创建输出文件流
ofile . open ( "myfile1" ) ; // ofile流与文件“myfile1”相关联
…… // 访问文件“myfile1”
ofile . close ( ) ; // 关闭文件“myfile1”
ofile . open ( "myfile2" ); // 重用ofile流
#include<iostream>
using namespace std;
int main ( )
{ char buf [ 80 ] ;
cin.get ( buf , 80 , 'y' ) ;
cout << buf << endl ;
cin.get ( buf , 80 ) ;
cout << buf << endl ;
cin.getline ( buf , 80 , 'n' ) ;
cout << buf << endl ;
cin.get ( buf , 80 ) ;
cout << buf << endl ;
return 0;
}
/*
输出
They are friends.
The
y are friends.
many students coming.
ma
y students coming.
*/
***Ma前输出空行的原因分析:
第2个cin.get ( buf , 80 ) ; 最后读入一个回车,“get ( ) 不从流中提取终止字符,终止字符仍在输入流中”,最后读入的回车仍在输入流
中”,而cin.getline ( buf , 80 , ‘n’ ) ;读入此回车,且需要读入‘n’才结束,此时的buf中的字符串是“\nMa” 。***
文本文件
文本文件用默认方式打开
文本文件用文本文件流进行读/写操作
文本文件是顺序存取文件
描述一个对象的信息称为一个记录**
文本文件本身没有记录逻辑结构
通常一个逻辑记录用换行符分隔;数据项之间可以用空白符、换行符、制表符等分隔
提取文本数据的其它操作
两个无格式化提取操作成员函数:
istream & istream :: get ( char *s , int n, char delim= ‘\n’ ) ;
istream & istream :: getline ( char *s , int n, char delim= ‘\n’ ) ;
作用:根据指定分隔符delim(默认为‘\n’),从输入流中提取n个字符构成字符串,并在串末添加一个空字符作为字符串的结束标记,存入s。
区别:
•get ( ) 不从流中提取终止字符,终止字符仍在输入流中:当遇到分隔符后,停止获取,并将分隔符留在输入流中;
•getline ( ) 从流中提取终止字符,但终止字符被丢弃:当遇到分隔符后,停止获取,但会将分隔符从输入流中取出。所以使用get函数的时候,需要手动跳过分隔符,而getline则不需要。
getline() 详解
头文件:
getline()的原型是istream& getline ( istream &is , string &str , char delim );
其中 istream &is 表示一个输入流,譬如cin;
string&str表示把从输入流读入的字符串存放在这个字符串中(可以自己随便命名,str什么的都可以);
char delim表示遇到这个字符停止读入,在不设置的情况下系统默认该字符为’\n’,也就是回车换行符(遇到回车停止读入)。
ps: 对于while(getline(cin,line)) 语句,这里默认回车符停止读入,按Ctrl+Z或键入EOF回车即可退出循环。
二进制文件
二进制数据文件以基本类型数据的二进制代码形式存放,二进制数据流不对写入或读出的数据做格式转换。
写操作时,将从内存的指定位置开始的若干个字节插入到流中;
读操作时,从流的指定位置开始的若干个字节送给指定对象;
数据的解释由内存对象的类型决定。
二进制文件的读写方式由程序控制,一般的字处理软件不能编辑。
打开二进制文件用binary方式。对二进制文件的操作也需要先打开文件,用完后要关闭文件。二进制文件除了可以作为输入文件或输出文件外,还可以是既能输入又能输出的文件。
二进制文件打开后,系统自动生成2个隐含的流指针:
读指针和写指针,并指向文件的第1个字节,文件的读写从指定位置
开始,每完成一次读/写操作,指针自动移到下一个读/写位置。
二进制文件是随机存取文件。
流指针在字节流中移动,指针指向的位置可以读写数据。
istream 类操作流读指针的成员函数
istream & istream :: seekg ( long pos) ;
作用 读指针从流的起始位置向后移动由pos指定字节
istream & istream :: seekg ( long off, ios::seek_dir ) ;
作用 读指针从流的seek_dir位置移动 off 指定字节
istream & istream :: tellg () ;
作用 返回读指针当前所指位置值
函数名的最后一个字母是g。 带 g的是用于输入的函数(g 是get的第一个字母)
ios::seek_dir 值:
cur 相对于当前读指针所指定的当前位置
beg 相对于流的开始位置
end 相对于流的结尾处
enum ios::seek_dir { beg = 0 ; cur = 1 , end = 2 } ;
istream input ;
……
input . seekg ( - 10 , ios :: cur ) ;
// 读指针以当前位置为基准,向前移动 10 个字节
istream input ;
……
beg end
cur
input . seekg ( 10 , ios :: beg ) ;
// 读指针从流的开始位置,向后移动 10 个字节
istream input ;
……
beg end
cur cur
input . seekg ( -10 , ios :: end ) ;
// 读指针从流的结尾,向前移动 10 个字节
ostream 类操作流写指针的成员函数
ostream & ostream :: seekp ( long pos ) ;
作用 写指针从流的起始位置向后移动由参数指定字节
ostream & ostream :: seekp ( long off , ios::seek_dir ) ;
作用 写指针从流的seek_dir位置移动由 off 指定字节
ostream & ostream :: tellp ( ) ;
作用 返回写指针当前所指位置值
函数名的最后一个字母是p。 带p的是用于输出的函数(p是put的第一个字母,以P作为输出的标识)。
例如 tellg用于输入文件,
tellp用于输出文件。
seekg用于输入文件,
seekp用于输出文件
适于二进制流操作的成员函数
这些函数操作对象是单字节数据
它们也可以用于文本流,但必须保证流中存储的数据是ASCII码,
并且不会跳过空白字符
适于二进制流操作的成员函数
istream 类中三个操作字节数据的成员函数
•istream & istream :: get ( char & c ) ;
•作用 从流中提取一个字节数据,更新对象 c
•int istream :: get ( ) ;
•作用 函数值返回流中一个字节数据
•istream & istream :: read ( char * buf , int n ) ;
•作用 从流中提取 n 个字节数据,更新对象 buf
适于二进制流操作的成员函数
ostream 类中两个操作字节数据的成员函数
•ostream & ostream :: put ( char c ) ;
•作用 向流插入一个字节数据
•ostream & ostream :: write ( char * buf , int n ) ;
•作用 向流插入 buf 对象的由第二个参数指定数目的字节数据
例:
char * s = “string buffer” ;
cout . write ( s , strlen ( s ) ) ; // 插入一串字符
cout . put ( ‘\n’ ) ; // 插入一个新行符
它可以把一块指定字节数的内存块拷贝到文件中去,一个字节一个字节地复制,即,原字节是啥样就按照啥样复制过去。
其用法如下图所示:
第一个参数是某变量的地址,第二个参数是要复制的字节数。
c++中write是用于向文件中写数bai据的函数。
函数原型du:ostream& write (const char* s, streamsize n);
参数:s是数据源指针zhi,n表示字节数
返回dao值:返回 ostream 对象的引用 (*this).
注意:使用需要#include
streamsize可以设置输出流的位数…
cout.precision()就是返回当前输出流的位数,
streamsize prec = cout.precision();
就是保存当前流的位数到prec中.
•在文件读写的时候,要关心这个文件里的数据内容本身是二进制格式还是文本格式:
•如果内容是文本格式的,就调用文本格式函数读写,如
puts,gets,<<,>>等,
•如果内容是二进制格式的,就调用二进制格式函数读写,
如read(),write()等。
要保持文件内容与处理函数相对应。
•不管是用什么模式打开文件,
•假如用<<向一个二进制文件写入一个整数,文件中保存的就是文本格式的数据,可以以二进制模式打开它,然后用>>来读取这个整数。
•相反,如果二进制文件里面是一个以二进制形式保存的整数,如write()写入,那就不能用>>来读取里面的整数,而要用read()读取。
命令行参数
假设一个输出文本文件内容的程序编译生成的可执行文件是listfile.exe,如果希望该程序的用法是:在Windows 的控制台窗口(也叫DOS 命令窗口)中输入:listfile 文件名
回车后就能启动listfile 程序,并将“文件名”所指定的文件的内容输出。如:listfile file1.txt,就能将file1.txt 这个文件的内容输出。
要做到这一点,listfile 程序必须知道那个文件名。
将用户在DOS 窗口输入可执行文件名的方式启动程序时,跟在可执行文件名后面的字符串,称为“命令行参数”。如上例中的“file1.txt”,就是一个命令行参数。命令行参数可以有多个,以空格分隔。如:listfile file1.txt file2.txt
在程序中如何知道用户输入的命令行参数? 要做到这一点,main 函数的写法须和以往不同