二进制文件与文本文件
内部存储器与外部存储器
什么是文件?
- 数据一般以文件的形式为用户及应用程序使用
使用文件的好处?
- 使程序与数据分离
- 在不同用户间进行数据共享
- 长期保持数据(相对于内存)
文件的分类
-
按文件的逻辑结构
- 记录文件:具有一定结构的记录组成(定长和不定长) - 流式文件(stream):由一个个字符(字节)数据顺序组成 - 数据流 - 字节流
c语言中的文件模型就是流式文件。
- 通过一个流(和键盘相关)获得全部的输入
- 通过另一个流(和屏幕相关)获得全部的输出
c语言中的标准流
文件指针 | 标准流 | 默认含义 |
---|---|---|
stdin | 标准输出 | 键盘 |
stdout | 标准输出 | 终端显示器屏幕 |
stderr | 标准错误输出 | 终端显示器屏幕 |
scanf(), getchar(), gets() 通过stdin获得输入
printf(), putchar(), puts() 通过stdout进行输出
输入输出重定向(redirection)
- 某些操作系统允许标准输入输出重定向文件
- DOS和UNIX允许程序从文件获得输入或者向文件写数据
这种重定向,程序本身是感受不到的
- 输入重定向
- D:>demo < infile.text
- 从终端(键盘)输入数据改为从文件中读入数据
- 输出重定向
- D:>demo > outfile.text
- 从终端(显示器)输出数据重定向到文件
-
按数据的组织形式
-
文本文件(Text file)
c程序的源代码
用字节表示字符的字符序列,存储每个字符的ASCII码
- 整数123在文本文件中占3个字节,分别存放这三个字符的ASCII码
按行划分:必须用特殊的字符标记行的结尾
某些OS还用特殊的字符标记文件的末尾:如DOS将Ctrl+Z设为文件结束符
- 二进制文件(Binary file)
可执行的c程序
- 按照数据在内存中的存储形式(二进制)存储到文件
- 存储的字节不一定表示字符,无需ASCII码表进行字符变换,读写速度快
- 不是按行划分的:可合法地包含任何字符,故不可能留出文件结束符
- 必须按照数据存入的类型和格式读出才能恢复本来面貌
- 按int型读,0x0064,是整数100
- 按float读,0x00000064,是浮点型1.4012985e-43,近似为0
- 公开的标准文件格式
- bmp tif gif jpg mp3
- 不公开或加密的文件格式
- Microsoft Word 的doc格式
文件的打开和关闭
文件与流的关系
- 程序通过
文件打开
操作将流与设备联系起来,文件打开后,可以在程序和文件之间交换数据
- 建立文件:由程序在磁盘上建立文件
- 写入数据:文件打开后,通过写操作将数据存入该文件
- 读取数据:由程序打开某个已有文件,通过读操作将文件中的数据读入内存供程序使用
- 程序通过
文件关闭
操作断开流与文件的联系
文件指针
- c程序中流的打开和关闭是通过文件指针实现的
- 文件指针类型为FILE *
FILE * fp;
文件打开后一定要检查是否打开成功
if(fp == NULL) {
printf("Failure to open!\n");
exit(0);
}
- 编译器会将
'\'
看成转义字符,如\n
和\t
,所以需要改成\\
- Windows用反斜杠
\
分割路径,UNIX用斜杠/
(不会出现此问题)
文本文件 | ||
---|---|---|
r | 只读 | 必须是已存在的文件 |
w | 只写 | 无论文件是否存在,都新建一个文件 |
a | 追加 | 向文件尾添加数据,该文件必须已经存在 |
r+ | 读写 | 打开一个已存在的文件,用于读写 |
w+ | 读写 | 新建一个文件,可读可写 |
a+ | 读写 | 在文件尾追加数据,可读可写 |
w:若文件不存在,新建文件;
若文件存在,将原文件内容覆盖
w+:若文件不存在,新建文件;
若文件存在,清空文件;
文件的关闭
int fclose(FILE *fp);
//若成功关闭,返回值为0
//关闭有错,返回为非零值(驱动器无盘或者盘空间不够,文件关闭失败会导致数据丢失、文件破坏,甚至程序出现随即错误
- 把遗留在缓冲区的数据写入文件,实施操作系统级的关闭
- 同时,释放与流联系的文件控制块FCB,以便以后重复使用
- 文件用完一定要关闭。否则可能导致数据丢失,甚至影响其他文件的打开(系统限制同时打开状态的文件总数)
按格式读写文件
读文件
读文件方向:从外存到内存
int a;
char b;
fscanf(fp,"%d %s", &a, &b);
//函数返回值为读到的参数个数,可以作为条件来判断输入是否结束
写文件
fprintf(fp, "%d %c", a, b);
返回值:写到文件中的字符个数
写错误,返回一个负数
字符和字符串文件的读写
int fputs(int c, FILE *fp);
//向fp指向的文件输出字符c
//写入成功,返回c。写入错误,返回EOF
//从屏幕读入,保存到文件中
int ch;
ch = getchar();
while(ch != '\n'){
fputc(ch, fp);
ch = getchar();
}
```c
int fgets(FILE *fp);
//从文件读入,输出到屏幕上
int ch;
ch = fgetc(fp);
while(ch != EOF){ //while( !feof(fp) ) 检测到文件尾返回非零,否则返回0值
putchar(ch);
ch = fgetc(fp);
}
下面先输出字符,再判断是否到达文件尾的方式,会多输出一个文件结束符
long pos;
while( !feof(fp)){
pos = ftell(fp);
ch = fgetc(fp);
printf("%c %ld\n", ch, pos);
}
开始时文件位置指针指向第一个字符,ftell返回值为0;
fgetc读走一个字符之后,fp自动指向下一个字符;
读完最后一个字符之后,fp指向文件末尾,即指向文件结束符EOF;
直到fgetc把EOF也读走之后,feof才能探测到文件尾。
feof函数总结:
feof在读完文件所有内容之后,再执行一次读文件操作,将文件结束符EOF读走,才能返回真(非0)值。
改进1:先判断是否到达文件尾,后输出字符。不会输出文件结束符
pos = ftell(fp);
c = fgetc(fp);
while(!feof(fp)){
printf("%c %ld", c, pos);
pos = ftell(fp);
c = fgetc(fp);
}
最后一行若接收到文件结束符EOF,feof就会返回非零值,就会跳出循环
改进2:用EOF判断是否到达文件尾
pos = ftell(fp);
c = fgetc(fp);
while(c != EOF){
printf("%c %ld", c, pos);
pos = ftell(fp);
c = fgetc(fp);
}
//简化
pos = ftell(fp);
while((c = fgetc(fp)) != EOF){
printf("%c %ld", c, pos);
pos = ftell(fp);
}
用EOF判断存在问题:读到文件尾或者读取错误时,fgetc()都返回EOF,无法区分
用ferror()判断是否读取错误,出错返回非0值
if(ferror(fp){
printf("error on file.\n");}
按行读取文件
puts(),将字符串写入标准输出流,并且在其后添加一个换行符;
fputs(),不会自动添加换行符
gets(char *s),从键盘读一个字符,读到换行符停止
【get会将字符串保存到s指向的缓冲区,然后返回字符串的首地址,但是不考虑缓冲区大小,容易造成缓冲区溢出,给缓冲区溢出攻击造成可乘之机】
fgets(char *s, int n, FILE *fp),从文件读取字符串,最多读n-1个字符。当遇到回车换行符,文件末尾,或者读满n-1个字符时,函数返回字符串的首地址
【参数n限制了字符串的长度,解决了缓冲区溢出问题。用fgets(buf, sizeof(buf),stdin)代替 gets(buf)更安全】
char *gets(char *s);
char *fgets(char *s, int n, FILE *fp);
【相同点】都会在字符串末尾添加’\0’;
读取失败或者读到文件尾都返回NULL;
读取失败用ferror确定,读到文件尾用feof确定。
【不同点】对换行符的处理不同
gets:不保留换行符\n,替换为\0
fgets:保留并存储\n,在换行符后面添加\0
用fputs代替fputc
从键盘输入,输出到屏幕
char str[80];
gets(str); //fgets(str, sizeof(str), stdin); 规定输入字符串的长度
fputs(str, fp);
从文件读出,输出到屏幕
char str[80];
fgets(str, sizeof(str), fp);
puts(str);
按数据块读写文件
从fp所指文件读数据,并将数据存到a所指向的地址
读文件
num = fread(a, sizeof(char), n ,fp);
//n个块,每个块占一个字节
返回值为实际写入的数据块个数,应该等于n,除非出现错误
写文件
num = fwrite(a, sizeof(char), n ,fp);
随机读写与文件缓存
文件定位
void rewind(FILE fp),使文件位置指针重修指向文件开始位置
long ftell(EILEfp),返回当前文件位置指针相对于文件起始位置的字节偏移量
int fseek(FILE *fp,long offset ,int fromwhere),改变文件指针位置
offset:指针偏移量(字节数)
fromwhere:起始位置
【SEEK_SET或0】:文件开始
【SEEK_CUR或1】:当前位置
【SEEK_END或2】:文件末尾
文件缓冲
向磁盘输出数据:数据->缓冲区,装满缓冲区后->磁盘文件
从磁盘读入数据:一次性从磁盘文件将一批数据输入到缓冲区,然后再从缓冲区逐个读入数据到内存中某个变量