文件操作
说到文件肯定都不陌生,常见的 txt 文件、Word文档等。文件时数据源的一种,最主要的作用是保存数据。
1. 什么是文件
电脑文件,也可以称之为计算机文件,是存储在某种长期储存设备或临时存储设备中的一段数据流,并且归属于计算机文件系统管理之下。所谓“长期储存设备”一般指磁盘、光盘、磁带等。而“短期存储设备”一般指计算机内存。需要注意的是,存储于长期存储设备的文件不一定是长期存储的,有些也可能是程序或系统运行中产生的临时数据,并于程序或系统退出后删除。
简单来说,磁盘上的文件就属于文件。
在程序设计中,一般讨论两种文件:程序文件 和 数据文件
1.1 程序文件
程序文件包括:1. 源程序文件(xxx .c)。 2.目标文件(windows下为xxx .obj)。 3.可执行程序(windows下为xxx .exe)。
1.2 数据文件
文件中的内容不一定是数据,而是程序运行时读写的数据。有时候会把信息输出到磁盘上,需要的时候再从磁盘上把数据读取到内存中使用,那么这里处理的就是磁盘上的文件。
1.3 文件名
文件名是文件存在的标识,操作系统根据文件名来对其进行控制和管理。不同的操作系统对文件命名的规则略有不同,即文件名的格式和长度因系统而异。为了方便人们区分计算机中的不同文件,而给每个文件设定一个指定的名称。由文件主名和扩展名组成。
一个文件要有一个唯一的文件名,以便用户的识别使用。
2. 二进制文件 和 文本文件
文件又可以分为文本文件和二进制文件两种类型。
文本文件:存储文本信息的文件
二进制文件:存储二进制信息的文件
字符⼀律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用⼆进制形式存储。
2.1 二进制文件
图形文件及文字处理程序等计算机程序都属于二进制文件。数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存文件中,就是⼆进制文件。
2.2 文本文件
文本文件是一种计算机文件,它是一种典型的顺序文件,其文件的逻辑结构又属于流式文件。文本文件是指以ASCII码方式(也称文本方式)存储的文件,更确切地说,英文、数字等字符存储的是ASCII码,而汉字存储的是机内码。文本文件中除了存储文件有效字符信息(包括能用ASCII码字符表示的回车、换行等信息)外,不能存储其他任何信息。
3.文件的基本操作
在操作之前,先弄了解一些相关概念。
3.1 流和标准流
3.1.1 流
在我们程序写完之后,运行时系统会进行读写数据的操作,从外部设备输入数据,或输出数据到外部设备。对于各种各样的外部设备,操作起来也会变得复杂。所以为了方便操作,就引入了一个抽象的概念:流。
可以把流看做成一条河流,这里的河流里就不是水了,而是字符。
流更像一个 “ 中间人 ” ,只要程序要跟外部设备打交道,就一定要打开 “ 流 ” 进行操作。
3.1.2 标准流
程序中 scanf 和 printf 很常见,而他们却没有进行打开流的操作。
原来,在程序启动时,会默认打开三个流:
- stdin :标准输入流,一般指键盘输入到缓冲区里的东西,是应用程序的默认数据源。
- stdout :标准输出流,通常默认定向到文本控制台(通常在屏幕上)。
- stderr :标准错误流,通常也默认定向到文本控制台(通常在屏幕上)。
以上三个流类型都是:FILE*(文件指针)
3.2 文件指针
在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。
每个被使用的文件都开辟了一个文件信息区,用于存放文件名称、文件类型、文件位置等相关信息。不同的数据类型,就需要一个结构体变量以供存放,此结构体类型是由系统声明的,取名为 FILE 。
3.3 文件的打开和关闭
想要对文件进行操作,打开关闭是必不可少的两步骤。打开文件:实际上是建立文件的各种有关信息,并使文件指针指向该文件,以便进行其他操作。关闭文件:断开指针与文件之间的联系,也就是禁止再对该文件进行操作。(C语言中,文件操作都是由库函数来完成的。)
文件的打开: 使用 fopen 函数来打开一个文件。
fopen 结构:FILE * fopen ( const char * filename, const char * mode );
等同于:文件指针名=fopen(文件名,使用文件方式); //文件使用方式见下文
文件的关闭: 使用 fclose 函数来关闭一个文件,避免文件的数据丢失等错误。
fclose 结构:int fclose ( FILE * stream );
等同于:fclose(文件指针);
3.3.1 文件的使用方式
文件使用方式 | 含义 | 指定文件不存在 |
---|---|---|
r(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
w(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
a(追加) | 向文本文件尾添加数据 | 建立一个新的文件 |
rb(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
wb(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
ab(追加) | 向一个二进制文件尾添加数据 | 建立一个新的文件 |
r+(读写) | 为了读和写,打开一个文本文件 | 出错 |
w+(读写) | 为了读和写,创建一个新的文件 | 建立一个新的文件 |
a+(读写) | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
rb+(读写) | 为了读和写,打开一个二进制文件 | 出错 |
wb+(读写) | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
ab+(读写) | 打开一个二进制文件 ,在文件为进行读写 | 建立一个新文件 |
若 fopen 函数打开失败,返回 NULL ;打开成功则返回文件信息区的地址 FILE*
读写过程:
程序中的数据都在内存中,从内存到文件是一个 输出(写) 的操作;从文件到程序中是一个 输入(读) 操作。
#include<stdio.h>
int main()
{
FILE* f = fopen("text.txt", "r");//打开操作
//FILE* f = fopen("C:\\admin\\text.txt", "r"); //可直接打开指定文件(绝对路径)
//FILE* f = fopen("./../../text.txt", "r"); //表示当前路径的上一级上一级路径(相对路径)
// .当前路径
// ..上一级路径
if (f == NULL)
{
perror("fopen");
return 1;
}
fclose(f); //关闭操作
f = NULL; //置空
return 0;
}
文件指针类型也属于指针,关闭文件操作之后,指针变量要及时置空。
4. 文件的顺序读写
函数 | 功能 | 含义 | 适用于 |
---|---|---|---|
fgetc | 字符输入函数 | 一次读取一个字符 | 所有输入流 |
fputc | 字符输出函数 | 一次写一个字符 | 所有输出流 |
fgets | 文本行输入函数 | 一次读取一行字符 | 所有输入流 |
fputs | 文本行输出函数 | 一次输出一行字符 | 所有输出流 |
fscanf | 格式化输入函数 | - | 所有输入流 |
fprintf | 格式化输出函数 | - | 所有输出流 |
fread | 二进制输入 | - | 文件 |
fwrite | 二进制输出 | - | 文件 |
fgetc 结构:int fgetc ( FILE * stream );(指针变量)
fputc 结构:int fputc ( int character, FILE * stream );(字符,指针变量)
fgets 结构:char * fgets ( char * str, int num, FILE * stream );(字符数组的指针,读取的最大字符数,指针变量)
fputs 结构:int fputs ( const char * str, FILE * stream );(字符数组的指针,指针变量)
fscanf 结构:int fscanf ( FILE * stream, const char * format, … );
fprintf 结构:int fprintf ( FILE * stream, const char * format, … );
fread 结构:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );(数组指针,数组类型的字节,数据个数,指针变量)
fwrite 结构:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );(数组指针,数组类型的字节,数据个数,指针变量)
4.1 scanf、printf 相关函数
- sancf / fscanf / sscanf
- printf / fprintf / sprintf
以上两组函数看似熟悉,其实也并不难。
scanf 结构:int scanf ( const char * format, … );
printf 结构:int printf ( const char * format, … );
fscanf / fprintf:
#include<stdio.h>
struct S
{
int a;
char b;
};
int main()
{
struct S s = { 10,'x'}; //赋值
FILE* sf = fopen("text.txt", "w");
if (sf == NULL)
{
perror(fopen);
return 1;
}
//写文件
fprintf(sf, "%d,%c", s.a, s.b);
//读取文件
//fsacnf(sf, "%d,%c", &(s.a), &(s.b));
//printf("%d,%c", s.a, s.b);
fclose(sf);
sf = NULL;
return 0;
}
fscanf / fprintf 和 scanf / printf 相比就多了一个文件指针变量的参数,所以不难理解,就是围绕文件进行读写的操作。
sscanf / sprintf:
sscanf:读取格式化的字符串中的数据。
sprintf:把格式化的数据写入某个字符串中(将格式化的数据写进字符串)。
sscanf 结构:int sscanf ( const char * s, const char * format, …);
sprintf 结构:int sprintf ( char * str, const char * format, … );
#include<stdio.h>
struct S
{
int a;
char b;
char c[20];
};
int main()
{
struct S s = { 10,'x',"hello"};
char c[20] = { 0 };
sprintf(c,"%d %c %s", s.a, s.b, s.c);
struct S t = { 0 };
sscanf(c, "%d %c %s", &(t.a), &(t.b), &(t.c));
printf( "%d %c %s", t.a, t.b, t.c);
return 0;
}
运行结果:
10 x hello
图解:
可理解为一个 “打包” 的过程,先是把东西装起来,再解开包装。
5.文件的随机读写
5.1 fseek
根据文件指针的位置和偏移量来定位文件指针。
fseek 结构:int fseek ( FILE * stream, long int offset, int origin );(指针变量,偏移量,起始位置)
起始位置(origin)有三种形式:
参数 | 含义 |
---|---|
SEEK_SET | 文件开始 |
SEEK_CUR | 文件末尾 |
SEEK_END | 文件指针当前位置 |
#include<stdio.h>
int main()
{
int arr[] = { 0 };
FILE* f = fopen("test.txt", "r"); //文件中已有abcdefg
if (f == NULL)
{
perror("fopen");
return 1;
}
int df = fgetc(f);
printf("%c\n", df); //输出a
fseek(f, 3, SEEK_SET);
df = fgetc(f);
printf("%c\n", df); //输出 d
fseek(f, 2, SEEK_CUR);
df = fgetc(f);
printf("%c\n", df); //输出 g
fseek(f, -2, SEEK_END);
df = fgetc(f);
printf("%c\n", df); //输出 f
fclose(f);
f = NULL;
return 0;
}
5.2 ftell
返回文件指针相对于起始位置的偏移量。
ftell 结构:long int ftell ( FILE * stream );(指针变量)
只需括号内填入指针变量再打印出来就能够得到偏移量。
5.3 rewind
让文件指针回到起始位置。(还原)
rewind 结构:void rewind ( FILE * stream );
6. 文件读取结束的判定
feof 函数:检测流上的 文件结束符,如果文件结束,则返回非0值,否则返回0。
作用:当文件读取结束的时候,判断读取结束的原因是否是:遇到文件尾结束。
文件读取结束的两种情况:1.文件到末尾。2. 遇到错误。
正确判断文件读取结束:
- 文本文件:fgetc 是否为EOF(返回 EOF 则读取结束) 、fgets 返回值是否为NULL(返回 NULL读取结束)
- 二进制文件:fread 返回值小于实际读取的个数(count)未结束
7. 文件缓冲区
文件缓冲区是用以暂时存放读写期间的文件数据而在内存区预留的一定空间。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
图解: