文件类型
在程序设计中,一般文件分为两类:程序文件和数据文件。
- 程序文件:包括源程序文件(.c),目标文件(.obj),可执行程序(.exe)
- 数据文件:内容不一定是程序,如txt文本文档等等
在此我们讨论的是对于数据文件的操作。
文件名
文件名是文件的唯一标识,包含三个部分:文件路径+文件名主干+文件后缀
二进制文件和文本文件
这两种都是文件数据的组织形式。
- 二进制文件:数据在内存中以二进制的形式存储,不加转换的输出到外存
- 文本文件:以ASCII字符形式存储
字符在文件中一律以ASCII形式存储,而数值型数据既可以用ASCII形式存储,也可以用二进制方式存储。
流
流是一个抽象出来的概念,将数据比作源源不断的河流,我们可以从流的任意位置读取数据。
标准流
C语言程序启动的时候,默认打开了三个流:
标准输入流(stdin)
:在大多数环境下从键盘输入标准输出流(stdout)
:大多数环境下输出至显示器界面标准错误流(stderr)
:大多数环境下输出至显示器界面
文件指针
以上三个流的类型是FILE*
,叫做文件指针
文件类型是一个结构体,在VS2013中有以下定义:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
打开文件时,系统会根据文件自动创建一个这样的FILE变量。我们可以创建一个FILE*类型的指针变量指向这个FILE变量,而FILE类型的这个结构体中又有一个指针指向这个文件,从而达到间接维护这个文件信息的目的。
文件读写
文件的打开与关闭
这里fopen的第一个参数是文件名,第二个是打开方式
关闭文件只需要传入文件指针即可
//打开文件
FILE* pf = fopen("sample.txt", "w");
//关闭文件
fclose(pf);
pf = NULL;//关闭文件之后pf就成了野指针!!
模式 | 含义 | 如果要打开的文件不存在 |
---|---|---|
r(只读) | 为了读取数据,打开一个已经存在的文本文件 | 出错 |
w(只写) | 为了写入数据,建立一个文本文件(若存在同名文件则先清空再写入) | 建立一个新文件 |
a(追加) | 向文本文件末尾添加数据 | 建立一个新文件 |
rb(只读) | 为了读取数据,打开一个已经存在的二进制文件 | 出错 |
wb(只写) | 为了写入数据,建立一个二进制文件 | 建立一个新文件 |
ab(追加) | 向一个二进制文件末尾添加数据 | 建立一个新文件 |
r+(读写) | 为了读写数据,打开一个文本文件 | 出错 |
w+(读写) | 为了读写数据,建立一个文本文件 | 建立一个新文件 |
a+(读写) | 打开一个文件,在文件末尾读写 | 建立一个新文件 |
rb+(读写) | 为了读写数据打开一个已经存在的二进制数据 | 出错 |
wb+(读写) | 为了读写数据,建立一个新的二进制文件 | 建立一个新文件 |
ab+(读写) | 打开一个二进制文件,在文件末尾读写 | 建立一个新文件 |
文件读写函数
函数名 | 功能 | 适用于 | 参数 | 返回值 |
---|---|---|---|---|
fgetc | 字符写入 | 所有输入流 | FILE* stream | (int)写入的字符/EOF |
fputc | 字符读取 | 所有输出流 | int character, FILE* stream | (int)写入的字符/EOF |
fgets | 文本行写入 | 所有输入流 | char* str, int num, FILE* stream | (char*)字符串指针/EOF |
fputs | 文本行读取 | 所有输出流 | const char* str, FILE* stream | (int)非负值/EOF |
fscanf | 格式化写入 | 所有输入流 | FILE* stream, const char* format, … | (int)成功写入字符数/EOF |
fprintf | 格式化读取 | 所有输出流 | FILE* stream, const char* format, … | (int)成功读取字符数/EOF |
fread | 二进制读取 | 文件 | void* ptr, size_t size, size_t count, FILE* stream | (size_t)成功读取元素个数/EOF |
fwrite | 二进制写入 | 文件 | const void* ptr, size_t size, size_t count, FILE* stream | (size_t)成功写入元素个数/EOF |
sscanf | 格式化写入 | 字符串 | const char* s, const char* format, … | (int)成功转化字符串的数量/EOF |
sprintf | 格式化读取 | 字符串 | char* str, const char* format, … | (int)输出的字符数/EOF |
文件随机读写
- fseek:根据文件指针的位置和偏移量定位
int fseek(FILE* stream, long int offset, int origin)
其中offset是偏移量,往后走n个字符就是n,往前走要加负号
origin的格式有三个,SEEK_SET
, SEEK_CUR
, SEEK_END
文件每读一次,指针就会往后偏移一个字符,所以指针当前可能不在起始位置
- ftell:用来查找文件指针在当前文件的位置(相对起始的偏移量)
long int ftell(FILE* stream)
- rewind:用来使文件指针返回起始位置
void rewind(FILE* stream)
文件读取结束的判定(feof、ferror)
这两个函数并不能用来判断文件有没有被读取完毕;而是等文件读取结束后,判断结束的原因是读完了正常退出还是出错异常退出
FILE* fp = NULL;
//......读取文件......
if(feof(fp))
printf("正常读取结束");
if(ferror(fp))
printf("异常读取中止");
fclose(fp);
fp = NULL;
文件缓冲区
程序在写入到硬盘或者从硬盘中读取数据时,都会先经过一个缓冲区,然后缓冲区的数据再逐个进入目标区域。
用一段代码来测试文件缓冲区的存在:
#include<stdio.h>
#include<windows.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf);//数据abcdef放在缓冲区
printf("已将数据放入缓冲区");
Sleep(10000);//程序休眠十秒,查看文件里并没有数据
fflush(pf);//刷新缓冲区,数据输出到文件中
printf("缓冲区中的数据写入文件");
Sleep(10000)//消除fclose刷新缓冲区的影响
fclose(pf);
pf = NULL;
return 0;
}