目录
一,引入
二,C++中的输入输出
1,输入输出流分类
2,I/O流类的安全性和可扩展性
(1)I/O流类的安全性
(2)I/O流类的扩展性
三,流类库简介
1,I/O流类库
2,缓冲区类
四,标准输出流和标准输入流
1,提取运算符、插入运算符
(1)”>>”运算符
(2)”<<”运算符
2,预定义流对象
(1)cerr和clog
3,标准输出流类常用成员函数
(1)put()函数:用于输出单个字符
(2)write()函数:用于输出一个字符串的部分或全部字符
4,标准输入流类常用成员函数
(1)get()函数
(2)getline()函数
(3)get()和getline()函数的比较
(4)read()函数
(5)其他成员函数
五,格式化控制
1,格式标志控制成员函数
(1)精度、域宽、填充字符的设置
2,格式控制操作符
(1)无参操作符
(2)有参操作符
六,文件流
1,构建文件流对象
(1)调用默认构造函数
(2)调用(带参)构造函数时指定文件名
2,文件的打开和关闭
(1)打开文件
(2)关闭文件
(3)文件的打开和关闭案例
(4)文件的读写、定位
(3)文件的随机读写(定位)
(4)文本文件和二进制文件的区别
一,引入
输入输出操作是程序中必不可少的操作,通过输入输出可以完成程序和外界的交互。
C++语言支持两种I/O操作:
(1)从C语言继承来的I/O函数输入输出语句:scanf()、printf()函数
(2)面向对象的I/O流类库:标准输入输出流cout和cin,在头文件iostream.h中定义。 I/O流不是C++语言的一部分,而是标准C++库的一部分,是C++类的一个集合。
二,C++中的输入输出
输入输出是数据的传递过程,数据如流水一般从一处流向另一处,C++形象的将此过程称为流。
输入操作:是控制序列中的字节内容从一个设备流入内存
输出操作:是控制序列中的字节内容从内存流向某个设备
1,输入输出流分类
根据I/O操作对象的不同,将I/O操作分为三类:
(1)标准I/O——以标准输入、输出设备为操作对象
(2)文件I/O——以磁盘文件为操作对象
(3)字符串I/O——以内存中指定的空间(通常为字符数组)为操作对象
2,I/O流类的安全性和可扩展性
(1)I/O流类的安全性
C中存在的问题:
在C语言中,用scanf()与printf()函数进行输入输出,不能保证输入输出的数 据是可靠安全的。
例如:
int i = 1;
scanf("%d",i); //漏掉符号&:发生不可预测的错误,造成安全隐患
printf("%d", "C++"); //输出“C++”字符串的地址而不是字符串内容:不如人 意
C++的对策:
C++的输入输出对数据类型进行严格的检查,凡是类型不正确的数据不可能通 过编译,因此C++的I/O操作是类型安全的。
(2)I/O流类的扩展性
C中存在的问题:
用scanf()与printf()输入输出数据时,无法直接输出用户自定义的数据类型
C++的对策:
C++对I/O操作进行了扩展,不仅可以用来输入输出标准类型的数据,也可以 用于用户自定义类型的数据。提高软件的重用性,加快软件的开发过程。
三,流类库简介
C++系统定义了不同的stream类,构造不同的数据流以实现不同数据的读写,形成了一 个庞大的I/O类库,其基本思想就是用C++中的类来进行输入输出。
输入操作:数据流出stream,即把stream流中的数据读取出来
输出操作:数据流入stream,即将数据与入到流中送到某个地方
1,I/O流类库
C++中输入输出操作由I/O类库实现,该类库是一个庞大的继承机构,其根基类为 ios,直接派生出4个类:输入流类istream、输出流类ostream、文件流基类 fstreambase、字符串流基类strstreambase。
这四个类又派生出其他类,形成了标准I/O流类库、文件流类库和字符串流类库。 学习I/O操作,就是学习这些类提供的成员函数,了解函数用法,实现不同流的读 写。
输入输出流类iostream:同时继承了输入流类和输出流类
输入文件流类ifstream:同时继承了输入流类和文件流基类
输出文件流类ofstream:同时继承了输出流类和文件流基类
输入输出文件流类fstream:同时继承了输入输出流类和文件流基类
输入字符串流类istrstream:同时继承了输入流类和字符串流基类
输出字符串流类ostrstream:同时继承了输出流类和字符串流基类
输入输出字符串流类strstream:同时继承了输入输出流类和字符串流基类
读取文件我们一般用istream和ostream
C++流类库中定义的各种流可以供用户直接使用,它们分别包含在iostream、fstream、 strstream三个头文件中,其包含情况如下所示:
(1)进行标准I/O操作时使用iostream头文件,它包含ios、iostream、istreamt 和ostream等类。
(2)进行文件I/O操作时使用fstream头文件,它包含fstream、ifstream、ofstream 和fstreambase等类。
(3)进行串I/O操作时使用strstream头文件,它包含strstream、istrstream、 ostrstream等类。
2,缓冲区类
在对数据进行输入输出时,为了减少输入输出的次数,节省计算机资源,常常需要使用到缓冲区,在C++中,系统提供了一个缓冲区流类库,它是以抽象类streambuf 为父类的类层次,主要完成信息通过缓冲区的交换。
所谓缓冲区,就是当数据传递时,在内存中为每一个数据流开辟的一个用来存放数据流中的数据的地方缓冲输入或输出的数据,又称为缓存。
C++系统在定义一些流对象时都会自带有缓冲区,它提供的缓冲区有三种类型:
(1)全缓冲:直到缓冲区被填满时才调用I/O函数。对于读操作来说,直到读入 内容的字节数等于缓冲区大小或者文件已经到达结尾,才进行实际的I/O操作, 将外存文件内容读入缓冲区;对于写操作来说,直到缓冲区被填满,才进行实 际的I/O操作,将缓冲区内容写到外存文件中。
(2)行缓冲:当在输入和输出中遇到换行符或者缓冲区被填满时,才执行真正的 I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进 行实际的I/O操作。标准的输入输出一般都是行缓冲。
(3)无缓冲:没有缓冲区。比如标准错误信息输出对象cerr
用户也可用上述缓冲区类来自定义缓冲区。但在使用缓冲区时要注意对缓冲的 刷新。当数据存储在缓冲区时,如果要执行I/O操作,需要刷新缓冲区,将缓 冲区中的数据读/写到某一指定地方。要刷新缓冲区有以下几种方式:
(1)缓冲区满时
(2)执行flush()函数
(3)执行endl语句
(4)关闭文件
如果调用flush()函数来刷新缓冲区,它可以执行I/O操作并清空缓冲区;endl 控制符的作用是将光标移动到输出设备中下一行开头处,并且清空缓冲区。
四,标准输出流和标准输入流
1,提取运算符、插入运算符
C++中输入输出操作是通过类库完成的,istream/ostream类中预定义了输入输出对 象及多种输入输出功能,最简单的就是使用提取运算符“>>”和插入运算符“<<” 完成数据读写。C语言中“>>”、“<<”是位操作运算符,完成数据的左移、右移操作,C++中istream/ostream对它们进行了重载,用以完成数据读写。
C++在进行输入时需要从流中提取数据,在输出时需要向流中插入数据,提取和插 入是通过在流类库中重载“>>”和“<<”运算符来实现的。
(1)”>>”运算符
提取运算符“>>”,又叫输入运算符,它的本义是右移操作,常用于位运算中。 C++的I/O流为了实现从流中提取数据,就在istream类中重载了“>>”运算符。
(2)”<<”运算符
插入运算符“<<”,又叫输出运算符,它的本义是左移操作,常用于位运算中。 C++的I/O流为了实现将数据插入到流中,就在ostream类中重载了“<<”运 算符。
2,预定义流对象
C++提供了4个预定义的标准流对象:cin、cout、cerr、clog,包含在头文件iostream 中。
其中cin是istream类的对象,用于处理标准输入(即键盘输入);cout是ostream 类的对象,用于处理标准输出(即屏幕输出);cerr和clog也都是ostream类的对 象,用于处理标准出错信息,并将信息显示到屏幕上。
在这四个标准流对象中,除了cerr不支持缓冲外,其余3个都带有缓冲区。
cin和cout我们已经非常熟悉了,我们这里说一下cerr和clog
(1)cerr和clog
cerr和clog都是ostream类预定义的流对象,都是用于标准错误输出,默认设 备都是显示器,只是cerr没有缓冲,信息直接输出给屏幕,不会等到缓冲区 填满或遇到换行符才输出错误信息。
cerr和clog多用于输出调试信息,我们这里cout输出流对象已经足够使用了, cerr和clog了解即可
3,标准输出流类常用成员函数
除了常用的插入运算符“<<”外,标准输出流库还提供了put()、write()函数用于数 据输出。
(1)put()函数:用于输出单个字符
函数声明:
ostream& put(char ch);
或:ostream& put(count char ch);
功能:用于输出单个字符。可以将一个字符型变量或一个字符常量输出到屏幕 上。
说明:put()函数的返回值为ostream类对象的引用,可以被cout对象连续调 用。
put函数的用法
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
char ch = 'a';
cout.put(ch) << endl;
cout.put('a').put('b').put('c');
return 0;
}
输出结果
a
abc
(2)write()函数:用于输出一个字符串的部分或全部字符
函数声明:
ostream& write(const char* str, int n);
功能:用于输出一个字符串的部分或全部字符。
说明:第一个参数是要输出的字符串的地址,第二个参数是要输出的字符个数。 若要输出字符串的全部字符,则第二个参数的值可以通过strlen()函数求出。 函数的返回值为ostream类对象的引用,可以被cout对象连续调用。
write()函数的用法
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
char *p = "nihao";
cout.write(p,strlen(p)).put('\n');
return 0;
}
输出结果
nihao
4,标准输入流类常用成员函数
istream流类还提供了get()、getline()、read()等成员函数用于完成多种形式的数据读入:
get()实现字符的输入;getline()实现字符串的输入;read()实现无格式输入等。
(1)get()函数
get()函数有3种重载形式:
函数声明:
int get();
istream& get(char& ch);
istream& get(char* dst, streamsize size, char delimiter=’\0’);
功能:
读取单个、多个字符数据。
第一种形式:表示从输入流中读取一个字符,返回该字符的int值,遇到文件 结束符时,返回EOF。
第二种形式:表示从输入流中读取一个字符,存储在ch中。
第三种形式:第三个参数是字符串的结束符。用于读取字符串,遇到指定的结 束字符或读满指定数量的字符时,函数返回。表示从输入流中连续读取最多 size-1个字符(因为最后一个字符要留给字符‘\0’),也可以设置结束符为别 的字符,遇到这个字符就结束读取,并且结束符不包含在所读取的字符串内。 如果一直读取到size-1个字符也没有遇到结束符,则在结束读取时会自动在末 尾加‘\0’。
get()函数的用法
#include <cstdlib>
#include <iostream>
using namespace std;
int main()
{
//第一种方式调用
cout << "第一种方式:" << cin.get() << endl;
//第二种方式
char ch;
cin.get(ch);
cout << "第二种方式:" << ch << endl;
//第三种方式
char str[8];
cin.get(str,6,'f');
cout << "第三种方式:" << str << endl;
return 0;
}
输出结果
abcftrgh
第一种方式:97
第二种方式:b
第三种方式:c
(2)getline()函数
get()函数一次读取一个字符,显然效率不高,而getline()函数可以一次读取一 个字符串。
函数声明:
istream& getline(char* dst, streamsize size, char delimiter=’\n’);
功能:
第三个参数是字符串的结束符,其参数的意义与get()函数的第三种重载方式 相同,使用方法也类似,当getline()遇到结束符时,就不再接受输入了。但 getline()函数默认的结束符是’\n’,它可以读取空格、制表符等内容。
getline()函数的用法
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char s[10];
cin.getline(s, 10);
cout << s << endl;
return 0;
}
输出结果
abcdefg
abcdefg
(3)get()和getline()函数的比较
get()、getline()函数在碰到结束符后都停止读入,get()函数将结束符仍留在输入缓冲中,getline()函数会将结束符从输入缓冲中读走并丢弃。
(4)read()函数
istream类还提供了成员函数read(),可以从输入流中读取部分或全部数据到指 定的内存空间。
函数声明:
istream& read(char* dst, streamsize size);
功能:读取指定数量的字符。read()函数参数的含义与get()、getline()函数的参 数含义相同,都是读取size-1个字符存储在首地址为dst的内存空间。
read函数的用法:
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char buf[12];
cin.read(buf, 10);
cout << buf << endl;
return 0;
}
输出结果
abcdefghijk
abcdefghij烫烫烫碰赛Z
(5)其他成员函数
ignore()函数
函数声明:[ɪg'nɔː]忽略
istream& ignore(streamsize num=1, int dellm=EOF);
功能:
从输入流中读取字符,直到读入num个字符(默认为1),或直到字符dellm (默认为EOF)被读入(虚读)。即跳过输入流中的num个字符,或在遇 到指定的终止符时提前结束。
putback()函数
函数声明:
istream& putback(char ch);
功能:
把上次通过get()或getline()读取的字符再放回输入流中。当前指针回退到 上次读入的位置。
peek()函数
函数声明:
int peek();
功能:[piːk]偷看一眼
检查输入流的下一个字符,不会删除字符,字符指针也不会移动,返回值 是指针指向的当前字符。
gcount()函数
函数声明:
streamsize gcount();
功能:
计算最后一个非格式化方法(get()/getline()/read()/ignore())读取到的字符 数。
例如:putback()、peek()、gcount()函数的用法:根据输入的第一个字符判 断输入的是数字还是字符串
五,格式化控制
1,格式标志控制成员函数
(1)精度、域宽、填充字符的设置
精度是数中的数字个数。小数位数是数中小数点右边的数字个数。例如:123.45 的精度是5,小数位数是2。
①浮点数精度设置
重载函数:
int ios::precision();
int ios::precision(int n);
功能:返回精度
第一种形式:返回当前的输出精度
第二种形式:设置输出精度为n位,并返回设置前的精度。
说明:设置精度后,该精度对之后所有的输出操作都有效,直到下一次的 精度设置为止。精度最小值为1,如果精度设置为0,则系统会按默认精 度显示数据。
②域宽设置
域宽是输入输出时所占用的字符数。
重载函数:
int ios::width() const;
int ios::width(int n) ;
功能:
第一种形式:返回当前的域宽值,未设置时,域宽的取值为0。
第二种形式:设置域宽为n位,并返回设置前的域宽值。设置的域宽在下 一次格式化输出时有效,而且只对其后的第一个输出有影响,输出完成后, 域宽又立即置为0。
说明:如果数据实际宽度比设置的域宽小,空位用填充字符来填充,如果 实际宽度比设置的域宽大,数据并不会被截断,而是会输出所有位(突破 域宽)。
函数setw()也可以设置域宽。
函数声明:
setw(int n);
功能:可以将设置宽度命令插入到输入/输出流中,比单独调用格式化函 数方便。当数据的实际宽度比设置的宽度大时,将显示整个数据值。
注意:setw(int n)不是ios类的成员函数,它属于操作符,包含在iomanip 头文件中。
③填充字符设置
当输出数据实际宽度小于域宽时,用填充字符来填充。默认填充字符为空 格,用户也可以设置填充字符。
重载函数:
char ios::fill() const;
char ios::fill(char ch) ;
功能:
第一种形式:返回当前填充字符的值。
第二种形式:设置填充字符为ch。
函数setfill()也可以设置填充字符。
函数声明:
setfill(char c) const;
功能:该函数可以直接插入流中,与setw()函数一样,包含在iomanip头 文件中,并不是ios类的成员函数。
④案例:精度、域宽、填充字符的设置
代码如下:
#include <iostream>
using namespace std;
int main()
{
double i = 12.123545;
cout.precision(5); //设置精度为5,会四舍五入
cout << "---精度设置---" << endl;
cout << i<<endl;
cout << "当前精度为:" << cout.precision() << endl;
cout << "---域宽设置---" << endl;
cout << "当前域宽为:" <<cout.width()<< endl;
cout.width(6); //设置域宽,默认是右对齐
cout << "设置之后的域宽为:" << cout.width() << endl;
cout << i << endl;
cout << "---填充字符设置---" << endl;
cout.width(10); //设置域宽
cout <<cout.fill('*') << i << endl; //设置填充字符
return 0;
}
输出结果
---精度设置---
12.124
当前精度为:5
---域宽设置---
当前域宽为:0
设置之后的域宽为:6
12.124
---填充字符设置---
********* 12.124
2,格式控制操作符
除了可用输入输出类库中专门的成员函数进行格式控制外,I/O流类库还提供了一 些专门的操作符(格式控制符),可直接嵌入输入输出语句中,实现I/O格式控制。
操作符是一种功能和ios成员函数相同,但使用更为便捷的函数,又被称为操纵子, 它们都包含在iomanip头文件中,要使用它们需要包含该头文件。
操纵子都返回引用类型,因此操作符可以连用。使用操作符可以简化程序编写,使 程序结构变得清晰。
C++语言提供两种操作符:无参操作符、有参操作符。
(1)无参操作符
无参操作符实现了常用的输入输出格式控制
操作符(格式控制符) | 含义 |
dec | 数值数据采用十进制输出 |
hex | 数值数据采用16进制输出 |
oct | 数值数据采用8进制输出 |
ws | 从输入流中提取空字符 |
endl | 插入换行符,并刷新输出缓冲流 |
ends | 插入空字符 |
flush | 刷新流 |
(2)有参操作符
有参操作符实现了复杂的输入输出格式控制。
操作符(格式控制符) | 含义 |
resertionflag(long n) | 清除n指定的格式化标志 |
setbase(int n) | 设置整形基数(0~10为十进制)为n |
setfill(char c) | 设置填充字符为c |
setiosflags(long n) | 设置n指定的格式化标志 |
setprecision(int n) | 设置精度为n:浮点数的小数位(包括小数点) |
setw(int n) | 设置域宽为n |
有参操作符与无参操作符可以组合使用,达到ios类提供的成员函数的功能。
六,文件流
ifstream:输入文件流类,用于实现文件的输入
ofstream:输出文件流类,用于实现文件的输出
fstream:输入输出文件流类,可以同时实现文件的输入和输出
用这三个类来构建输入/输出/输入输出流,然后进行文件的读写操作。这三个类都包含 在fstream头文件中,所以在使用这三个类时需要包含fstream头文件。
(1)文件操作前,应当创建文件I/O类对象,相当于C语言中定义FILE *变量,用于记 录文件句柄。
两种创建类对象的方法:
一种是调用默认构造函数创建类对象;
另一种是调用带参构造函数创建对象,参数为文件名称,此时也完成了文件的打开。文 件具有默认打开方式,即若是ifstream对象则以读方式打开。
(2)文件操作的基本流程:打开、读写、关闭。
若想打开文件,还可调用open()函数,函数中可指定打开方式。
文件操作完毕后,应调用close()函数关闭文件,实现资源回收、缓冲清理。
1,构建文件流对象
文件流不像标准I/O流预定义了输入/输出流对象,所以在使用文件流时需要调用 相应类的构造函数来构建文件流对象。构造文件输入输出流的常用方法有两种:
(1)调用默认构造函数
ifstream ifs; //定义一个文件输入流对象
ofstream ofs; //定义一个文件输出流对象
fstream fs; //定义一个文件输入输出流对象
(2)调用(带参)构造函数时指定文件名
ifstream ifs("filename"); //指定文件名,此时也完成了文件的打开
ofstream ofs("filename");
fstream fs("filename");
在使用文件流对象进行输入输出时,同样可以使用插入运算符“<<”和提取运 算符“>>”
ifstream ifs; //构建一个输入流对象
……(打开文件)
char ch;
ifs >> ch; //将文件流的数据存储到ch变量中
提取运算符“>>”是用于格式化文本输入的,在提取数据时,以空白符为分隔, 如果要输入一段包含空白符的文本,用提取运算符就很不方便,在这种情况下, 可以选择使用非格式化成员输出函数getline(),这样就可以读取一段包含有空
格的文本块,然后再对其进行分析。
2,文件的打开和关闭
(1)打开文件
两种打开文件的方式:调用文件流的带参构造函数打开文件、调用文件流的成 员函数open()。
①用文件流的带参构造函数打开文件:
调用带参构造函数创建文件流对象时,传入指定的文件,此时也完成了文 件的打开。文件具有默认打开方式,即若是ifstream对象则默认以读方式 (输入方式)打开,ofstream对象默认以输出方式打开文件,fstream对 象默认以输入/输出方式打开文件。
例如:
fstream file1("c:config.sys"); //以输入输出方式打开文件
ifstream file2("c:pdos.def"); //以输入方式打开文件
ofstream file3("c:x.123"); //以输出方式打开文件
②用文件流的成员函数open()打开文件:
ifstream类、ofstream类、fstream类都提供了成员函数open()用于打开文 件。
格式:
void open(const char* filename, int mode, int prot=filebuf::openprot);
说明:
第一个参数filename是要打开文件的文件名;第二个参数表示文件的打开方式;第三个参数是文件打开时的保护方式(文件的属性),该参 数 与操作系统 有关,通常默认值为filebuf::openprot。
如果文件打开失败,open()函数执行后流对象的值为0(假);如果是调用带参构造函数打开文件失败,则流对象的值为0,可以据此测试文件打开 是否成功。
这些文件打开方式通常可组合使用,用“位或”运算符对输入输出方式进 行组合。
例如:
ofstream ofs;
ofs.open(“Hello.txt”, ios::in | ios::out | ios::binary)
对于ifstream类对象其文件的默认打开方式是ios::in,ostream类对象打 开文件的默认方式是ios::out,fstream类对象没有默认打开方式。
(2)关闭文件
ifstream类、ofstream类、fstream类都提供了成员函数close()用于关闭文件。
ofstream ofs;
ofs.open(“file1”); //打开file1文件
ofs.close(); //关闭file1文件
ofs.open(“file2”); //关闭file1文件后,再打开file2文件
ofs.close();
(3)文件的打开和关闭案例
在D盘创建一个txt文本,命名为test,然后在里面随便输入一段话
代码如下:
# include <iostream>
# include <fstream>
using namespace std;
int main()
{
ifstream ifs; //构造输入流
ifs.open("D:\\test.txt",ios::in);
if (!ifs)
{
cout << "打开失败" << endl;
}
else
{
cout << "打开成功" << endl;
}
ifs.close();
return 0;
}
输出结果
打开成功
(4)文件的读写、定位
文件是存放信息的载体,文件读写是文件操作的主要目的。C++文件I/O类继 承了ios、istream、ostream基类的读写函数用于实现读写操作,如get()、getline()、 read()、write()函数均可用于操作文件。需要注意的是若需要操作二进制文件, 除了在打开文件时需要指明操作二进制文件外,其读写操作一般由read()、 write()函数完成。
对于常规文件来说,除了完成读写,还可以对其进行定位,即修改文件位置指 针。定位函数为seekg()、seekp(),tellp()、tellg()函数用于获取文件当前读写位 置。
①文本文件的读取
可以使用提取运算符“>>”和插入运算符“<<”实现文件的读写,get()、 getline()、read()、write()函数均可用于操作文件。
文本的读取:
案例:还是以上面的代码为例
# include <iostream>
# include <fstream>
using namespace std;
int main()
{
ifstream ifs; //构造输入流
ifs.open("D://test.txt",ios::in);
char buf[255];
while (!ifs.eof())
{
ifs.getline(buf, 255, '\n'); //调用文件输入流类ifstream的基类的getline()成员函数
cout << buf << endl;
}
ifs.close();
return 0;
}
输出结果
原是今生今世已惘然,山河岁月空惆怅,而我,终将是要等着你的。 一双冷眼看世人,满腔热血酬知己。相逢秋月满,更值夜萤飞。
注意:在vs中读取txt的话可能会出现乱码的情况,因为是编码方式存在 差异,txt文本保存默认是UTF-8,而控制台的编码方式为ANSI,所以 我们把txt文件的编码方式改成ANSI就可以了。我们打开我们创建的test 文本,另存为test1.txt,下面可以选择编码方式,我们把编码方式改成ANSI
然后删掉原来的text.txt,把test1.txt改成test.txt即可,在运行的话就不 会出现乱码的现象了
文本文件的写入:
# include <iostream>
# include <fstream>
using namespace std;
int main()
{
ofstream ofs;
ofs.open("D://test.txt", ios::trunc); //trunc是打开文件前将文件清空
string s = "i live you";
if (ofs)
{
for (int i = 0; i < s.size(); i++)
{
ofs << s[i];
}
cout << "打开成功" << endl;
}
else
{
cout << "打开失败" << endl;
}
ofs.close();
return 0;
}
输出结果
打开成功
我们打开D盘的test.txt就可以发现里面的内容发生变化了
错误处理函数
eof()是文件流中的错误处理函数,遇到文件结尾会返回true(一个非零值)。 C++文件流还提供了一些其他的进行错误处理的成员函数,它们都返回 bool类型的值。
(2)二进制文件的读写
二进制文件的读写,文件流提供了两个成员函数read()、write()。
read()函数是类istream的一个成员函数,被ifstream所继承;write()函数 是ostream类的一个成员函数,被ofstream类所继承;类fstream的对象 同时拥有这两个函数。
函数声明:
istream& read(char* buf, int size);
ostream& write(const char * buf, int size);
说明:buf是一个内存地址,用来存储或读出数据;size表示数据传输的 字符个数。
案例:向一个二进制文件中写入数据,再读取出来。
在D盘创建一个student.bat文件
# include <iostream>
# include <fstream>
using namespace std;
struct Student
{
char name[20];
int age;
};
int main()
{
Student student;
cin >> student.name >> student.age;
ofstream ofs("D://student.bat", ios::out | ios::binary); //构建输入流对象
ofs.write((char*)&student, sizeof(student)); //写入文件
ofs.close();
Student stu; //接收读取的文件
ifstream ifs("D://student.bat", ios::in | ios::binary); //构建输出流对象
if (!ifs)
{
cout << "打开失败" << endl;
}
else
{
cout << "打开成功" << endl;
while (ifs.read((char*)&stu, sizeof(stu)))
{
cout << "姓名:" << stu.name << "年龄:" << stu.age << endl;
}
}
ifs.close();
return 0;
}
输出结果
kkk 23
打开成功
姓名:kkk年龄:23
用记事本打开student.bat文件,可以看见内容已经变成了上面的内容
第14行将 student 对象写入文件。student的地址就是要写入文件的内存 缓冲区的地址。但是&student不是char * 类型,因此要进行强制类型转 换。
注意:在进行二进制文件读写时,文件打开模式必须是binary(二进制文 件)。
(3)文件的随机读写(定位)
在C语言中要实现文件的随机读写是依靠文件位置指针,在C++中实现文 件的随机读写也是依靠移动文件位置指针来完成的。
ifstream有一个被称为get pointer的指针,指向下一个将被读取的元素。 ofstream有一个指针put pointer,指向写入下一个元素的位置。C++在文 件输入流和文件输出流中都提供了设置文件位置指针的函数(定位)。
对于输入流,istream类提供了seekg()、tellg()两个定位函数。tellg()函数 用于获取文件指针的当前位置。
函数声明:
istream& seekg(streampos); //文件指针直接定位
istream& seekg(streamoff, ios::seek_dir); //指针相对定位(相对于第二个 参数向前或后移动)
long tellg(); //返回指针的当前位置
①seekg(streampos)函数
seekg(streampos)函数用于直接定位文件位置指针,streampos是长整 型数据,它是以文件开始处为参考点,将文件位置指针移动到参数所 指位置。
②seekg(streamoff, ios::seek_dir)函数
两个参数的seekg()函数是相对定位文件位置指针(相对于第二个参 数向前或向后移动第一个参数指定的距离),streamoff也是长整型 数据,表示指针的偏移量,可正可负可零,为正时表示指针向后 移动,为负时表示指针向前移动,为零时表示指针相对于当前位置不 动。seek_dir是ios类中定义的一个公有枚举类型,其定义如下:
enum seek_dir
{
ios::beg=0; //文件开头
ios::cur=1; //文件指针当前位置
ios::end=2; //文件结尾
}
seek_dir取不同的值表示从不同的位置开始移动指针。
例如:
ifs.seekg(20L, ios::cur); //文件指针从当前位置向后移动20个字节
ifs.seekg(-20L, ios::end); //文件指针从文件尾向前移动20个字节
ifs.seekg(0,ios::beg) //文件指针移到文件开头
③tellg()函数
返回文件指针的当前位置(整形数值),往往与seekg()函数配合使用。 此函数仅当在读取一个文件时有效。
对于输出流,ostream类提供了seekp()、tellp()两个定位函数。tellp() 函 数用于获取文件指针的当前位置。
函数声明:
ostream& seekp(streampos); //文件指针直接定位
ostream& seekp(streamoff, ios::seek_dir); //指针相对定位
long tellp(); //返回指针的当前位置
①seekp()函数
seekp()函数与seekg()函数有同样的功能,只不过它用于写文件时。
例如:假如在进行文件读写,若要定位到当前位置的3个字符之前,则需调用:FileHandle.seekg(-3);如果在写入一个文件,假如要重写 后5个字符,就必须往回跳转5个字符,应该使用: FileHandle.seekp(-5);
②tellp()函数
tellp()与tellg()有同样的功能,但它用于写文件时。当读取一个文件,并要知道文件位置指针的当前位置时,应该使用tellg();当写入一个 文件,并要知道文件位置指针的当前位置时,应该使用tellp()。
案例:文件的随机读写
我们利用前面的test.txt文本,内容是:i love you
# include <iostream>
# include <fstream>
using namespace std;
int main()
{
ifstream ifs; //构造输入流对象
ifs.open("D://test.txt", ios::in | ios::out | ios::binary);
cout << "输入流对象指针位置:" << ifs.tellg() << endl; //打开文件后查看指针位置
ifs.seekg(5); //打开文本后指针向后移动5个字符
cout << "输入流对象移动指针后位置:" << ifs.tellg() << endl; //移动指针后再看指针位置:5
char ch;
ifs.get(ch); //获取当前指针处的字符
cout << "当前指针出的字符是:" << ch<<endl;
ofstream ofs; //构造输出流对象
ofs.open("D://test.txt", ios::in | ios::out | ios::binary);
cout << "输出流对象指针位置:" << ofs.tellp() << endl; //输出流打开文件后,查看指针位置
ofs.seekp(3); //将指针向后移动3个字节
cout << "输出流对象移动指针后位置:" << ofs.tellp() << endl; //移动指针后查看指针位置:3
ofs.put('A'); //向当前指针处写入字符A,替换掉原来的字符
ifs.seekg(ios::beg); //将输入流的文件位置指针移动到开头
char buf[20];
while (!ifs.eof())
{
ifs.getline(buf, 20, '\n');
cout << "当前文本内容为:" << buf;
}
ifs.close();
ofs.close();
return 0;
}
输出结果
输入流对象指针位置:0
输入流对象移动指针后位置:5
当前指针出的字符是:e
输出流对象指针位置:0
输出流对象移动指针后位置:3
当前文本内容为:i love you
运行结果显示字母A为写入,但是我们打开test.txt文本可以发现已经改成A了
这是什么原因呢?
解答:同一个文件分别用读写打开,两个流各自维护了一个缓冲区。 ifstream流对文件内容没有影响,但它读取的是缓冲区中的数据, ofstream流修改数据也只是修改缓冲区中的数据,在最后关闭时才写 回文件。
解决方法:
方法1:刷新缓冲区:在当前指针处写入字符A后,用下面语句中的 任意一个,刷新缓冲区
//ofs.flush(); //刷新输出流,使写入的字符A进入缓冲区
//ofs<<flush; //刷新输出流,此语句和上面语句功能相同
方法2:关闭文件:在当前指针处写入字符A后,关闭输出流
ofs.close(); //此语句提前到写入字符A后,读取文件前
方法3:将ifstream流与ofstream流连接起来:当要读取ifstream流 时,ofstream流会自动刷新
ifs.tie(&ofs); //此语句加到写入字符A后,读取文件前
说明:
ostream tie(ostream *)
tie()是ostream类的成员函数。将当前流与指定的输出流连接起来。 每当需要读取当前流时,连接的流会自动刷新。C++流库已用 cin.tie(cout)将标准输入流与标准输出流连接起来。要取消与输出流的 连接可采用is.tie(0)
(4)文本文件和二进制文件的区别
用二进制文件存取的话比文本文件好一点
用文本方式存储信息不但浪费空间,而且不便于检索。例如,一个学籍管 理程序需要记录所有学生的学号、姓名、年龄信息,并且能够按照姓名查 找学生的信息。程序中可以用一个类来表示学生:
class Student
{
char szName[20]; //假设学生姓名不超过19个字符,以 '\0' 结尾
char szId[l0]; //假设学号为9位,以 '\0' 结尾
int age; //年龄
};
如果用文本文件存储学生的信息,文件可能是如下样子:
Micheal Jackson 110923412 17
Tom Hanks 110923413 18
这种存储方式不但浪费空间,而且查找效率低下。因为每个学生的信息所 占用的字节数不同,所以即使文件中的学生信息是按姓名排好序的,要用 程序根据名字进行查找仍然没有什么好办法,只能在文件中从头到尾搜索。
如果把全部的学生信息都读入内存并排序后再查找,当然速度会很快,但 如果学生数巨大,则把所有学生信息都读人内存可能是不现实的。
可以用二进制的方式来存储学生信息,即把 Student 对象直接写入文件。 在该文件中,每个学生的信息都占用 sizeof(Student) 个字节。对象写入 文件后一般称作“记录”。本例中,每个学生都对应于一条记录。该学生 记录文件可以按姓名排序,则使用折半查找的效率会很高。