C++文件操作
文件的概念
对于用户来说,常用到的文件有两大类:程序文件和数据文件。而根据文件中数据的组织方式,则可以将文件分为ASCII 文件和二进制文件。
ASCII 文件,又称字符(character)文件或者文本文件,它的每一个字节放一个 ASCII 代码,代表一个字符。ASCII 文件中数据与字符一一对应,一个字节代表一个字符,可以直接在屏幕上显示或打印出来,这种方式使用方便,比较直观,便于阅读,但一般占用存储空间较大,而且输出时要将二进制转化为 ASCII 码比较花费时间。
二进制文件,由二进制数组成。又称内部格式文件或字节文件,是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。二进制文件,输出时不需要进行转化,直接将内存中的形式输出到文件中,占用存储空间较小,但一个字节并不对应一个文件,不能直观显示文件中的内容。
文件流和文件流对象
文件流是以外存文件未输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都有一个内存缓冲区与之对应。
C++中的IO(input-output、输入与输出)流
C++流:信息从外部输入设备(如键盘)向计算机内部(如内存) 输入 和 从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。
流的特性是:有序连续、具有方向性。
流可以分为两种:
1)文本流:在缓冲区中读出和写入时是基于ASCII或者Unicode字符编码的。写入缓冲区的最大长度规定为254个字符。在文本文件结束时规定以一个回车符和换行符结尾,在写入缓冲区时,会将\n转换为回车换行符,在从缓冲区中读取出数据时,就会将回车换行符转换为换行符。
2)二进制流:二进制的流在向缓冲区中写入或读出文件时,不进行字符的转换,直接从设备或文件中读取,在这个过程中不发生任何改变,原先是什么样子,读取之后还是什么样子,它是基于二进制数字的。
C++中的三种操作:标准IO、文件IO、串IO。
标准IO:兼容C中的输入输出,以键盘和屏幕为操作对象,从键盘输入或从屏幕输出 。
文件IO:以磁盘文件为输入输出的对象,数据在磁盘中写入和读出 。
串IO:对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(该空间可以存储任何信息)。这种输入和输出称为字符串输入输出,简称串I/O。
C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:
ifstream:专用于从文件中读取数据;
ofstream:专用于向文件中写入数据;
fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。
值得一提的是,这 3 个文件流类都位于 <fstream> 头文件中,因此在使用它们之前,程序中应先引入此头文件。
C++在类库中定义了四个全局流的对象:
cin : 标准输入流对象,对应键盘;
cout:标准输出流对象,对应显示器;
cerr:标准输出错误流对象(无缓冲),对应显示器;
clog:标准输出错误流对象(缓冲),对应显示器;
在新库中 要使用这四个功能,必须包含文件并引入std标准命名空间。
注意:
1.使用cin输入时,从键盘输入的数据会存放在键盘的缓冲区。当我们按下回车时,就会将其送入输入缓冲区。所以对输入的修改只能在按下回车之前。一旦按下回车就无法修改,因为在输入缓冲区中,只有当数据被全部取完时,需要重新输入,写入缓冲区的数据,会顺序依次被取出,无法删除。
2.输入与输出的数据的类型必须一致,否则会报错。出错时会在status状态字的对应位置置1,程序继续运行。
3.在对于字符型或字符串进行cin时,字符串中不能有空格,而且回车符也无法起作用。当遇到回车时就直接结束,遇到空格,空格后输入的字符无效。
要以磁盘文件为对象进行输入输出,必须定义一个文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件,或者将磁盘文件输入到内存。
定义文件流对象后,我们还需要将文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件,并确定文件的工作方式(输入还是输出,二进制还是 ASCII)。我们可以在定义流对象的时候指定参数来调用构造函数,或者通过成员函数 open 来进行文件流对象和指定文件的关联。
使用文件的过程是固定的,一般步骤如下:
(1) 打开一个文件,使磁盘文件和文件流对象建立联系;
(2) 将数据写入一个文件,就如同cout用于向显示器送数据。以后可从这个文件读取数据,就如同cin用于键盘输入。
(3) 当不再使用文件时,要关闭文件,此时文件将从缓冲区中完全写回磁盘。这样,可以永久保存数据。
相关具体细节说明:
☆对文件进行操作,必须在程序前增加一句: #include<fstream>
☆打开文件:使得文件流对象和磁盘文件之间建立联系
a、通过构造函数
//参数:文件名,请求标志位
std::ofstream ofile("test.txt",std::ofstream::out);
b、使用成员函数open。如果该文件已经打开,open会调用失败。
std::ofstream ofile;
ofile.open("test.txt",std::ofstream::out)
每个类都有对于文件的访问权限操方式的限制,输入输出的请求标志:
in、input:打开文件读,内部流缓冲区支持输入操作
out、output:文件打开供写入,内部流缓冲区支持输出操作
binary:执行二进制操作
app、append:追加,所有的操作都发生在文件的末尾
ate、atend:输出位置在文件的末尾
trunc、trucate:打开文件之前存于文件之中的内容都会被丢弃
☆关闭文件:断开当前文件与流对象之间的关联
ofile.close();
☆进行操作时 可以用插入 >> 运算符 和 提取 << 运算符进行
ofile << "sdasdas"; //表示向文件中输入一个数
char a[1024];
istream ifile("test1.txt",std::ifstream::in);
ifile >> a; //将文件中的数据写入a中
☆写入
ofstream 类中提供了write成员函数,用于向文件中写入数据
ostream& write (const char* s, streamsize n);
☆读取
ifstream 类中提供了read成员,用于从文件中读取数据
istream& read (char* s, streamsize n);
在对二进制读写时,我们采用write 和 read 成员函数进行操作。
在类中还提供了一些其他的成员函数入 put()(单个字符插入到流中) get() (从流中读取单个字符)等。
先介绍文本文件的操作。
例1、将百鸡问题计算结果存入d盘myfile.txt文件
#include<fstream>
#include<iomanip>
using namespace std;
int main(){
int i,j,k;
ofstream ofile; //定义输出文件
ofile.open("d:\\myfile.txt"); //作为输出文件打开
ofile<<" 公鸡 母鸡 小鸡"<<endl; //标题写入文件
for(i=0;i<=20;i++)
for(j=0;j<=33;j++){
k=100-i-j;
if((5*i+3*j+k/3==100)&&(k%3==0)) //注意有(k%3==0)
ofile<<setw(6)<<i<<setw(10)<<j<<setw(10)<<k<<endl; //数据写入文件
}
ofile.close(); //关闭文件
return 0;
}
注意:在c++中字符\要用转义字符\\表示。
运行之,在d盘中生成一个文件myfile.txt,参见下图:
例2、读出上例存放百鸡问题计算结果的文件myfile.txt的内容
#include<fstream>
#include<iostream>
#include<iomanip>
using namespace std;
int main(){
char a[28];
ifstream ifile; //定义输入文件
ifile.open("d:\\myfile.txt"); //作为输入文件打开
int i=0,j,k;
while(ifile.get(a[i])){ //读标题,请对比cin.get(),不可用>>,它不能读白字符
if(a[i]=='\n') break;
i++;
}
a[i]='\0';
cout<<a<<endl;
while(1){
ifile>>i>>j>>k; //由文件读入数据
if(ifile.eof()!=0) break; //当读到文件结束时,ifile.eof()为真
cout<<setw(6)<<i<<setw(10)<<j<<setw(10)<<k<<endl; //屏幕显示
}
ifile.close(); //关闭文件
return 0;
}
运行之,参见下图:
提示
☆打开文件时,如磁盘文件不存在,会自动建立文件,但指定目录必须存在,否则建立文件失败。
☆磁盘文件操作与键盘、显示器操作非常相似。例题中用输出文件流对象(如ofile)代替cout,输入文件流对象(如ifile)代替cin,数据的去向和来源则由显示器和键盘变为磁盘文件。
☆对文件进行操作,必须在程序前增加一句: #include<fstream>
上面主要介绍讨论文本文件的使用方法和操作过程。下面介绍二进制文件使用方法和操作过程。要实现以二进制形式读写文件,<< 和 >> 将不再适用。
1)get()函数 :get函数有三种操作形式
file2.get(x)
x=file2.get()
file2.get(str1,127,‘A’)
2)put()函数
3)为顺序读写数据特殊设计的成员函数:write 和 read
write ( char * buffer, streamsize size );
read ( char * buffer, streamsize size );
这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数(size值很重要,因为二进制文件内容没有行的概念(’\n’),字节之间是紧挨着的)。
4)随机读写文件
通过移动文件读写指针,可在文件指定位置进行读写。
seekg(绝对位置); //绝对移动, //输入流操作
seekg(相对位置,参照位置); //相对操作
tellg(); //返回当前指针位置
seekp(绝对位置); //绝对移动, //输出流操作
seekp(相对位置,参照位置); //相对操作
tellp(); //返回当前指针位置
参照位置:
ios::beg = 0 //相对于文件头
ios::cur = 1 //相对于当前位置
ios::end = 2 //相对于文件尾
总结:打开二进制文件时,如可直接定义输入输出而不是分开进行定义,如:
fstream iofile(“stud.dat”,ios::in|ios::out|ios::binary|ios::trunc);
其中若最初无stud.dat文件,则需添加ios::trunc来创建一个新文件;
打开文件后,用if(!iofile){cerr<<“error!”<<endl; exit(1);}来判断文件是否打开成功;
对二进制文件,普通字符用get()和Put()函数进行操作,对数据块,可以用write()和read()函数操作,使用<<,>>运算符只能进行文本文件的读写操作,用于二进制文件可能会产生错误。经常和read配合使用的函数是gcount(),用来获得实际读取的字节数。使用eof()函数检测文件是否读结束,使用gcount()获得实际读取的字节数。
二进制文件操作示例,代码如下:
/*
有几个学生的数据,要求:
把它们存到磁盘文件中;
将磁盘文件中的第1,3,5个学生数据读入程序,并显示出来;
将2第个学生的数据修改后存回磁盘文件中的原有位置。
从磁盘文件读入修改后的个学生的数据并显示出来。*/
#include<iostream>
#include<fstream>
#include <cstring>
using namespace std;
struct student
{
char name[20];
int age;
char id[20];
char sex[10];
};
int main()
{
student stu1[5] = { {"张三",17,"2018212114","男"},
{"李四",19,"2018212115","男"},
{"王五",20,"2018212113","男"},
{"aaa",19,"2018212116","男"},
{"nvzi",18,"2018212110","女"} };
fstream iofile("stud.dat", ios::in | ios::out | ios::binary | ios::trunc);
if (!iofile)
{
cerr << "error!" << endl;
exit(1);
}
int i;
for (i = 0; i < 5; i++)
iofile.write((char*)&stu1[i], sizeof(stu1[i]));
student stu2[3];
for (i = 0; i < 5; i=i+2)
{
iofile.seekg(i * sizeof(stu2[i/2]), ios::beg);
iofile.read((char*)&stu2[i/2], sizeof(stu2[i/2]));
}
for (i = 0; i < 5; i=i+2)
{
cout << "第" <<i+1<< "位同学:" << endl;
cout << stu2[i/2].name << " "<< stu2[i/2].age << " " <<stu2[i/2].id << " "<<stu2[i/2].sex << endl;
}
stu1[1].age = 18;
strcpy(stu1[1].name, "goto");
strcpy(stu1[1].sex, "女");
iofile.seekp(sizeof(stu1[1]), ios::beg);
iofile.write((char*)&stu1[1], sizeof(stu1[1]));
iofile.seekg(0, ios::beg);
student stu3[5];
for (i = 0; i < 5; i++)
iofile.read((char*)&stu3[i], sizeof(stu3[i]));
for (i = 0; i < 5; i++)
{
cout << "第" << i + 1 << "位同学:" << endl;
cout << stu3[i].name << " " << stu3[i].age << " " << stu3[i].id << " " << stu3[i].sex << endl;
}
iofile.close();
return 0;
}
运行之,参见下图:
在当前目录下建立文件stud.dat,请注意例子中的这句fstream iofile("stud.dat", ios::in | ios::out | ios::binary | ios::trunc);