所谓缓冲区就是读写数据必须经过的中间渠道。默认情况下当我们想要向文件中写入数据时,系统会先把我们需要写入的内容放在缓冲区这个中间渠道,等到满足一定条件时(如所有需写入的内容均已存入、缓冲区已满等情况),再一起写到文件里。这样做可以提升程序的运行效率,因为计算机从RAM中读写数据很快,但从ROM中读写数据就会慢很多,把所有要写入的内容积攒起来一次性写入会减少很多调文件所需要的时间。同理,当我们想要读取文件中的内容时,计算机也会先把文件的全部内容放到缓冲区,然后再根据我们的读取需求拿出相应的数据。下面我们看一个写入文件的例子:
int main()
{
ofstream mytest("test1.txt",ios::app);
if(mytest.is_open())
{
for(int i=0;i<800;i++)
{
mytest<<"这是第"<<i<<"个测试\n";
this_thread::sleep_for(chrono::milliseconds(20)); // 用于延时20毫秒
}
mytest.close();
return 0;
}
else
{
cout<<"打开文件失败"<<endl;
return 0;
}
}
执行的效果为:
可以看到,执行代码时文件并不是一行一行地多出内容,而是过一段时间后一下子多出了很多内容。
这样的设置虽然可以提升程序的运行效率,但是在处理某些任务时可能会有隐患。假如缓冲区存了很多内容,刚要写入文件时程序崩溃了,于是程序员炸了…
为了避免以上问题的发生,我们可以手动设置文件逐行写入:
flush方法可以立即将缓冲区内容写入文件,用法如下:
int main()
{
ofstream mytest("test1.txt",ios::app);
if(mytest.is_open())
{
for(int i=0;i<800;i++)
{
mytest<<"这是第"<<i<<"个测试\n";
this_thread::sleep_for(chrono::milliseconds(100));
}
mytest.close();
return 0;
}
}
在讲cout函数时,我们提到过endl就类似于输出一个换行符,但实际上它还有刷新缓冲区的作用,在写入文件任务中也能发挥相同的作用。我们只需要把
mytest<<“这是第”<
改成
mytest<<“这是第”<
就可以得到与使用flush方法相同的效果。
我们可以通过设置输出流的状态达到无缓冲的效果,只需要在打开文件后加上语句
mytest<
就好了。效果还是和上面一样,这里不做展示。如果想要恢复有缓冲效果就可以对应使用
mytest<
每创建一个流对象,内部都会有三个流状态标志位用于表示流的状态,分别为eofbit,badbit和failbit,他们的值为布尔类型的0或1。
当输入流操作到达文件末尾时,eofbit标志位会被设置为1,例如使用while循环读取文件内容时,当文件内容已被全部取出后,eofbit标志位被置一。但需要注意,只是移动文件指针到末尾不会改变eofbit标志位的值。检查eofbit标志位需使用eof()方法:
int main()
{
ifstream mytest("test.txt",ios::in);
if(mytest.is_open())
{
string out;
while(mytest>>out)
{
cout<<mytest.eof()<<endl;
}
cout<<"\n"<<mytest.eof()<<endl;
// 关闭文件
mytest.close();
}
else
{
cout<<"打开文件失败"<<endl;
return 0;
}
}
// 输出为:0
// 0
// 0
// 0
//
// 1
从这个例子中可以看出,在while循环内时,eofbit标志位的值一直为false,当while结束后即已经读取完全部内容时,eofbit标志位会被置1。
badbit属于严重的错误的标志位,当无法诊断的失败破坏流时,badbit会被标记为1,如遇到磁盘空间已满或对输入流进行写入操作。检查badbit位使用方法bad():
int main()
{
fstream mytest("test.txt",ios::in); // 使用fstream类是因为fstream有写方法,后面不会报错
if(mytest.is_open())
{
cout<<mytest.bad()<<endl;
mytest<<"这是另一个测试\n";
cout<<mytest.bad()<<endl;
// 关闭文件
mytest.close();
}
else
{
cout<<"打开文件失败"<<endl;
return 0;
}
}
// 输出为:0
// 1
我们打开test.txt文档也可以发现并没有写入内容。一般badbit被标记为1都是严重错误导致的,没有简单的解决办法。
failbit标记非严重的错误,如软件错误。有时没有遇到错误也会被标记为1,例如读取文件完成后,在eofbit被标记为1的同时,failbit也会被标记为1。failbit被标记为1一般都是可挽回的,查看所用方法为fail(),大家可以自行尝试。
当三个标志位都为0时,调用good()方法会获得True的返回值,表示文件操作顺利。但是如果某些标志位为1时,后面就不能再正常的操作文件了,比如在读取文件全部内容后就不能直接写入新内容。这种情况不是错误,我们可以清空流状态,然后继续操作文件。通常,清空流状态的方法为clear()。大家可以参考上节内容,这里不再演示。
C++中也为我们提供了删除文件的方法:函数remove。成功删除文件后,remove会返回false,表示路径下已经没有了需要删除的文件,删除失败则返回true。通常删除失败的原因为文件正在使用或没有权限。演示如下:
int main()
{
if(!remove("test.txt"))
{
cout<<"删除成功"<<endl;
}
else
{
cout<<"删除失败"<<endl;
}
}
运行完成后,陪伴我们走过了三节课的test.txt文件就在对应目录下消失了,同时终端留下了一行文字:
删除成功。
由于文件操作涉及到的方法过多,我们这里用一个表格进行总结:
方法 | 功能 |
---|---|
ifstream | 定义输入流文件 |
ofstream | 定义输出流文件 |
fstream | 定义输入/输出流文件 |
open() | 打开文件,可以声明打开方式 |
close() | 关闭文件 |
is_open() | 判断文件是否打开 |
getline() | 获取文件的一行内容,有两种常用的重载 |
write() | 用于向文件中写入二进制内容,参数为一个char指针和写入内容的字节数 |
read() | 用于读取二进制文件内容,参数同write() |
tellp() | 获取ofstream类(和fstream类)流文件指针位置 |
tellg() | 获取ifstream类(和fstream类)流文件指针位置 |
seekg() | 移动ifstream类(和fstream类)流文件指针,有两个常用的重载 |
seekp() | 移动ofstream类(和fstream类)流文件指针,有两个常用的重载 |
flush() | 刷新缓冲区,可将缓冲区内容立刻写入文件 |
eof() | 判断是否到达文件末尾 |
bad() | 判断是否出现严重错误 |
fail() | 判断是否出现可挽回的错误或问题 |
remove() | 删除指定文件,删除成功返回false失败返回true |
以上就是我们常用的方法汇总。另外提一嘴,我们使用的如ios::in、ios::cur等参数都是枚举常量哦,忘记了枚举常量的小伙伴记得及时复习。