正如Effective C++所讲:C++就是一个语言联邦。他是一个多面手。对于同一个问题往往有很多种解决办法,具体采用那种办法就交给程序员去选择。因此本文对C++文件IO的整理就得分为两个面:C方式,C++方式。程序员可根据自己所在团队选择合适且统一的IO方式。
一. 基础知识
1.1文件类型:ASCII文件和二进制文件
首先我不保证文件类型只有这两种。但理解这两种文件对学习文件IO操作非常重要。
1.1.1ASCII文件
ASCII文件也就是文本文件,每个字节存放一个ASCII代码,代表一个字符。可以使用任何编辑器打开,如记事本或者UE等,打开就是你能看懂的字符。比如姓名"richard"就会存储为7个字节,每个字节分别为对应字母的ASCII码。整数10000就会被存为"10000",每个字节为每个字母的ASCII码即:00110001 00110000 00110000 00110000 00110000。ASCII文件通常是适合人看的,多用于展现给人,所以叫文本文件。
1.1.2二进制文件
把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。所谓内存中的数据就是我们写的程序中的各种值。比如我定义了int a = 10000;那么a在内存中是什么样子呢?a是一个int类型,32位占4个字节:00000000 00000000 01000111 00010000。这就是内存中a的形式。把这个形式原样存储到文件中就是二进制文件。如下图所示。一般中间结果数据需要暂时保存在外村上,以后又需要输入到内存的,常用二进制文件保存。
1.2缓冲文件系统和非缓冲文件系统
待完善。
1.3文件都包含什么
一个文件不仅仅包含内容。可以从C语言中FILE类型的生命中观察一个文件都有些什么:
typedef struct{
short level; /*缓冲区"满"或"空"的程度*/
unsigned flags; /*文件状态标志*/
char fd; /*文件描述符*/
unsigned char hold; /*如无缓冲区不读取字符*/
short bsize; /*缓冲区的大小*/
unsigned char *buffer; /*数据缓冲的位置*/
unsigned char *curp; /*指针,当前的指向*/
unsigned istemp; /*临时文件,指示器*/
short token; /*用于有效性检查*/
}
原来一个文件需要包含如此多的信息。重点关注curp这个指针变量。它就像文件自带了的一个游标,每次从文件读写东西之后,这个游标就会向后移动。才能方便下次读写。
1.4 文件打开的方式
r,w,a,rb,wb,ab,r+,w+,a+,rb+,wb+,ab+
凡是带b的表示读写二进制文件,不带的表示读写ASCII文件。
待完善。
二. C文件IO
C语言中对IO的处理主要通过各种函数,而这些函数的参数往往就是一个文件指针。
2.1文件的打开(fopen函数)
FILE* fp;
fp = fopen(文件名,打开方式);
代码示例:
if((fp = fopen("./file1","r")) == NULL){
printf("cannot open this file\n");
exit(0);
}
2.2文件的关闭(fclose函数)
文件使用完必须关闭文件
fclose(文件指针);
代码示例:fclose(fp);
2.3文件的读写
2.3.1 fputc函数和fgetc函数(putc函数和getc函数)
fputc(ch, fp); 把一个字符ch写到文件指针fp中.
ch = fgetc(fp); 读取字符赋给ch.
2.3.2 fread函数和fwrite函数
用fgetc和fputc函数可以用来读写文件中的一个字符。但是常常要求一次读入一组数据(例如,一个实数或者一个结构体变量)。这就要用到fread和fwrite读写一个数据块。
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
fread和fwrite函数一般用于二进制文件的输入输出。因为他们是按数据块的长度来处理输入输出的。以哪种形式写和读必须是对应的,切记这一点。吃过这方面亏得同学体会很深,也就更加理解其原理了。
2.3.3 fprintf函数和fscanf函数
考虑这种情况,我将学生数据存储到文件中,以备后面查阅,注意着是要给人看的。fread和fwrite虽然可以读写结构体这样的数据块。但是如前所说,那个适合的二进制文件的读写。不适合给人看的ASCII文件。
struct student{
int id;
char name[15];
char addr[15];
int age;
};
int main(){
struct student lily = {1234, "li", "shaanxi", 20};
FILE* fp;
if((fp = fopen("studentsTable", "wb")) == NULL){
printf("%s\n", "error");
return 0;
}
fwrite(&lily, sizeof(struct student), 1, fp);
fclose(fp);
return 0;
}
执行这段代码后,打开studentsTable之后,发现除了中间lishaanxi之外全都是二进制1,0.根本无法识别,因为他存储的是整数1234和20在内存中的32位二进制。当然无法识别了,而char型在内存中和ASCII文件以及二进制文件都是一个字节。恰好显示出来。人能够识别。所以你打开二进制文件studentsTable之后看到那样的结果。
因此给人看的话就要按照ASCII文件读写学生数据,你当然可以用fread和fwrite以ASCII文件方式打开studentsTable.即使成功写入结果就是:1234lishaanxi20.(往往会出错)。你看这个数据黏在一起多么难区分。所以我们需要能格式化输出数据到文件中,比如:1234|li|shaanxi|20。这个时候就要用到fprintf和fscanf了。
fprintf(文件指针,格式字符串,输出列表);
fsacnf(文件指针,格式字符串,输入列表);
那么代码可以改良为:
if((fp = fopen("studentsTable","w")) == NULL){
printf("%s\n", "error");
return 0;
}
fprintf(fp, "%d|%s|%s|%d",lily.id, lily.name, lily.addr, lily.age);
这下打开studentsTable文件后就会看到1234|li|shaanxi|20了。
fsacnf当然就是读取对应格式的文件了。
2.3.4其他函数
putw,getw,fgets,fputs。这些函数当你掌握上面的只是之后看看声明自然就会用了。也用不了那么多。
2.4 文件的定位
如前面基础部分所述,文件中有一个位置指针,指向当前读写的位置。如果顺序读写一个文件,每次读写一个字符,则读写完一个字符之后,该位置指针自动移动指向下一个字符位置。如果想改变这样的规律,强制使位置指针指向其他指定的位置,可以使用以下函数。
rewind,fseek,ftell等。
三. C++文件IO
C++除了具有C的文件IO功能之外,还提供了面向对象的处理方式。即使用IO流类库。首先学习C++文件IO之前一定要记住这张图,这是非常重要的。其中stream就不解释了。i表示in, o表示out, f表示file.
3.1温故知新
3.1.1 小知识
"cin >>" ,"cout<< "大家都用过吧,其实cin是istream类的对象,cout是ostram类的对象。至于"<<",">>"只是这两个类的重载操作符而已。因为"<<",">>"就像流水一样只是一个流向。比如cin >> a; 从cin流向了a,就是从标准输入即键盘输入流向了变量a,这不就是从键盘输入赋值给a吗。又如cout << a;从a流向了cout,就是从变量a流向了标准输出即显示其屏幕,这不就是把a打印到屏幕吗。那么由此推出istream和ostream及其子类们的对象都可以对应的使用"<<"或">>"。
3.1.2 标准输出(屏幕输出)和标准输入(键盘输入)
流对象<<操作数
流对象.put(char c);
流对象.wirte(const char *str, int n);
流对象>>操作数
流对像.get();
流对象.get(char c);
流对象.getline(char* buf, int n, char deline='\n');
流对象.read(char *buf, int size);
注意以上流对象的操作除了get()之外多数均返回本流对象的引用,可连续使用。比如cout<<a<<b<<c, cout.put(a).put(b),cin.get(a).get(c);
3.2 文件的打开和关闭操作
3.2.1打开文件操作
a)使用fstream类对象打开文件的方法
fstream <对象名>
<对象名>.open("<文件名>", <方式>);
方式:io, out, app, ate, binary …… ios::in|ios:out,ios::out|ios::binary……
例子:
写方式打开一个文本文件
fstream outfile;
outfile.open("file.txt",ios::out)
或者 fstream outfile("file.txt", ios::out)
读方式打开二进制数据文件file.dat
fstream infile("file.dat",ios::binary|in);
b)使用ofstream类或ifstream类对象打开文件的方法
ofstream <对象名>
<对象名>.open("<文件名>")
为什么这块没有打开方式呢?因为已经指明是输出流了。就不需要告诉是写还是读了。
例子:
ofstream out1;
out1.open("file.cpp");
或者 ofstream out1("file.cpp");
ifsteam in1;
in1.open("file1.cpp");
或者 ifstream in1("file1.cpp")
3.2.2 关闭文件的操作
<流对象名>.close();
例子:out1.close();
3.3 文本文件的读写操作
既然cout,cin是ofstream,ifstream的对象,那么他们的那些方法也同样适用于我们的文件流。so easy。
3.4 二进制文件的读写操作
同上。
3.5 格式控制
流对象的格式控制函数太多了,需要用的时候查就可以了。
3.6 随机读写
随机读写就相当于C语言中的文件的定位,更改文件里面的那个游标。流对象都相应的用友这些方法。随用随学吧。