目录
一、什么是文件?
使用文件可以将数据直接存储到磁盘上,做到了数据的持久化(如微信聊天记录,购物记录等)
二、文件的打开和关闭
1.文件指针是什么?
在文件缓冲系统中,通常使用“文件类型指针”,简称为“文件指针”
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息,这些信息是保存在一个结构体变量中的,该结构体类型是由系统声明的,取名FILE
2.FILE是什么?
在C语言中,FILE是一个结构体类型,用来表示文件流。它是在标准库中定义的,当使用文件指针时需要使用FILE类型的指针来指向文件流(抽象的概念,从文件中读取或写入数据的过程)
例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
在不同的C编译器中,FILE的内容可能不完全相同,但是大同小异
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充信息,我们使用者不必关心细节
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便
FILE* fp;//fp是文件指针变量
定义fp是一个指向FILE类型数据的指针变量,可以使fp(是一个结构体变量)指向某个文件的文件信息区,通过文件信息区可以访问文件。也就是说,我们可以通过fp(文件指针变量)来找到它所指向的文件
3.打开和关闭文件的指针变量
文件在读写之前应该先打开文件,使用之后应该关闭文件
我们可以通过 FILE* 建立一个指针变量 指向 我们需要打开的文件
在ANSIC 规定使用 fopen函数来打开文件,fclose来关闭文件,这两个函数都是C语言标准库中的I/O函数
3.1 fopen
FILE *fopen(const char *filename, const char *mode);//打开文件
fopen
函数用于打开一个指定的文件,并返回一个指向该文件的文件流指针
其中,filename是要打开的文件的名称,mode是指定文件打开模式的字符串
3.1.1文件的路径
文件的名称分为 绝对路径 和 相对路径
绝对路径
绝对路径是指从文件系统的根目录开始,一直到目标文件的完整路径
例如,在 Windows 系统中,绝对路径是这样:C:\Users\Username\Documents\file.txt
// 使用绝对路径打开文件
fp = fopen("C:\\Users\\Username\\Documents\\file.txt", "r");
相对路径
相对路径是指相对于当前源文件所在目录的路径,从当前目录开始。
例如,源文件位于C:\Users\Username\Documents
目录中,打开该目录下的test.txt文件,可以使用相对路径test.txt
来打开
//使用相对路径打开文件
FILE *fp = fopen("test.txt", "r");
打开上一级目录中的文件时,应用相对路径来打开
如 ../test.txt 来打开 Username,这里的 .. 表示上一级目录
//打开上一级目录中的文件
FILE *fp = fopen("../test.txt", "r");
3.1.2 mode的参数
文件使用方式 | 含义 | 如果指定文件不存在 |
“r” | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
“w” | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
“a” | 向文本文件尾添加数据 | 建立一个新的文件 |
“rb” | 为了输入数据,打开一个二进制文件 | 出错 |
“wb” | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
“ab” | 向一个二进制文件尾添加数据 | 建立一个新的文件 |
“r+” | 为了读和写,打开一个文本文件 | 出错 |
“w+” | 为了读和写,建议一个新的文件 | 建立一个新的文件 |
“a+” | 打开一个文件,在文件尾进行读写 | 建立一个新的文件 |
“rb+” | 为了读和写打开一个二进制文件 | 出错 |
“wb+” | 为了读和写,新建一个新的二进制文件 | 建立一个新的文件 |
“ab+” | 打开一个二进制文件,在文件尾进行读和写 | 建立一个新的文件 |
3.2 fclose
fclose函数用于关闭由fopen函数打开的文件
如果关闭成功了,该函数会返回0。如果关闭失败了,该函数会返回EOF(-1),即非零值
int fclose(FILE *stream);
stream是要关闭的已经打开的文件的指针
3.3实例
#include <stdio.h>
int main ()
{
FILE * pFile;
//打开文件
pFile = fopen ("myfile.txt","w");
if (pFile == NULL)
{
perror("fopen");
return 1;
}
//关闭文件
fclose (pFile);
//把指针释放为NULL
pFile=NULL;
return 0;
}
在使用fclose
函数关闭文件后,不需要将指针fp
设置为NULL
但是,将指针设置为NULL
是一个好习惯,它可以防止意外地再次使用已经关闭的文件指针
三、文件的顺序读写
1.函数介绍
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
1.1 fgetc和fputc
fgetc
int fgetc ( FILE * stream );
从指定的流 stream(指针) 获取下一个字符(一个无符号字符),并把位置标识符往前移动
该函数以无符号 char 强制转换为 int 的形式返回读取的字符
如果调用时流位于文件末尾,则该函数返回 EOF 并为流设置 (feof) 的文件结束指示器
如果发生读取错误,该函数将返回 EOF 并为流设置错误指示器 (ferror)
代码如下
#include <stdio.h>
int main ()
{
FILE * pFile;
//打开文件
pFile = fopen ("myfile.txt","r");
if (pFile == NULL)
{
perror("fopen");
return 1;
}
//读文件,一次读一个字符
in ch1=fgetc(pFile);//从文件内读
in ch2=fgetc(stdin);//从键盘上读
//关闭文件
fclose (pFile);
//把指针释放为NULL
pFile=NULL;
return 0;
}
fgetc接收的参数不同,则读取的地方不同
fputc
int fputc ( int char, FILE * stream );
把char指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动
要编写的字符的 int 提升,写入时,该值在内部转换为无符号字符
如果没有发生错误,则返回所写的字符;如果发生错误,则返回 EOF 并设置错误指示器(ferror)
代码如下
#include <stdio.h>
int main ()
{
FILE * pFile;
//打开文件
pFile = fopen ("myfile.txt","w");
if (pFile == NULL)
{
perror("fopen");
return 1;
}
//在pFile指向的文件内储存
fputc('a', pFile);
//打印到屏幕
fputc('b',stdout);//标准输出流
fputc('c',stderr);//标准错误流,同样可以输出到屏幕
//关闭文件
fclose (pFile);
//把指针释放为NULL
pFile=NULL;
return 0;
}
1.2 fgets和fputs
fgets
char * fgets ( char * str, int num, FILE * stream );
str——指向一个字符数组的指针,该数组存储了要读取的字符串
num——要读取的最大字符数(包括最后的空字符),通常是使用以 str 传递的数组长度
stream——指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流(stdin 可以用作从标准输入读取的参数)
返回值——如果成功,函数返回str; 如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针; 如果发生读取错误,则设置错误指示器(ferror),并返回空指针(但str指向的内容可能已更改)
代码如下
从文件中获取
#include <stdio.h>
int main()
{
FILE * pFile;
char mystring [10];
pFile = fopen ("myfile.txt" , "r");
if (pFile == NULL)
{
perror ("fopen");
return 1;
}
else
{
if ( fgets (mystring , 10 , pFile) != NULL )
{
puts (mystring);
}
fclose (pFile);
pFile=NULL;
}
return 0;
}
从键盘上获取
我们从键盘上输入后,获取10个字符,但最后只打印了9个,是因为fgets函数会在最后自动添加一个空字符 '\0'(这样可以避免出现错误),所以调用fgets时,最多获取 n-1 个字符
fputs
int fputs ( const char * str, FILE * stream );
把字符串写入到指定的流 stream 中,但不包括空字符
注意:fput与 put 的不同之处不仅在于可以指定目标流,而且 fput 不会写入其他字符,而 put 会自动在末尾附加换行符
str——这是一个数组,包含了要写入的以空字符终止的字符序列
stream——这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流
返回值——成功时,将返回非负值。出错时,该函数返回 EOF 并设置错误指示器(ferror)
代码如下
#include <stdio.h>
int main ()
{
FILE *fp;
fp = fopen("file.txt", "w+");
if(fp==NULL)
{
perror("fopen"):
return 1;
}
fputs("这是 C 语言。", fp);
fputs("这是一种系统程序设计语言。", fp);
fclose(fp);
fp=NULL;
return(0);
}
文件内容如下 (没有自动换行)
这是 C 语言。这是一种系统程序设计语言。
1.3 fscanf和fprintf
//fscanf
int fscanf ( FILE * stream, const char * format, ... );
//scanf
int scanf ( const char * format, ... );
//sscanf
int sscanf ( const char * s, const char * format, ...);
//fprintf
int fprintf ( FILE * stream, const char * format, ... );
//printf
int printf ( const char * format, ... );
//sprint
int sprintf ( char * str, const char * format, ... );
比较 scanf 函数后,区别在于读取数据的来源不同
比较 printf 函数后,区别在于输出目标不同
fscanf | 从指定的文件流中读取数据 |
scanf | 从标准输入流stdin中读取数据 |
sscanf | 从一个一空字符('\0')结尾的字符数组中读取数据 |
fprintf | 将格式化字符串输出到指定的文件流中 |
printf | 将格式化字符串输出到标准输出设备(通常是屏幕) |
sprintf | 将格式化字符串输出到一个字符数组中(不是流) |
1.4 fread和fwrite
//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 );
注意:fread和fwrite是用于读取和写入二进制数据,使用时fopen参数应用 "rb"/"wb"
ptr
是指向存储 读取/写入 数据的缓冲区的指针(地址),size
是每个数据元素的大小(以字节为单位),count
是要读取的数据元素的个数,stream
是指向文件流的指针
函数返回实际 读取/写入 到的数据元素的个数
所创建的文件不可直接访问,用法相似,不再过多赘述
四、文件的随机读写
1.fseek
//根据文件指针的位置和偏移量来定位文件指针
int fseek ( FILE * stream, long int offset, int origin );
stream——根据文件指针的位置和偏移量来定位文件指针
offset——这是相对 origin 的偏移量,以字节为单位,可以为负数
origin——用作偏移参考的位置。它由 <cstdio> 中定义的以下常量之一指定,专门用作此函数的参数:
常量 | 参考位置 |
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 * |
* 允许库实现不有意义地支持 SEEK_END(因此,使用它的代码没有真正的标准可移植性)
返回值——如果成功,该函数将返回零。否则,它将返回非零值。如果发生读取或写入错误,则设置错误指示器(ferror)
代码如下
#include <stdio.h>
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
if (pFile == NULL)
{
perror("fopen");
return 1;
}
//写入文本
fputs("Hello World.", pFile);
//修改指针位置
//SEEK_SET即从文件开头的位置
fseek(pFile, 5, SEEK_SET);
//修改文本信息
fputs("****", pFile);
fclose(pFile);
pFile = NULL;
return 0;
}
当我们把 fseek(pFile, 5, SEEK_SET); 这行代码注释掉,文件内文本为 Hello World.****
我们放开后,文件内文本为 Hello****ld.
2.ftell
long int ftell ( FILE * stream );
返回文件指针相对于起始位置的偏移量(当我们不知道指针的位置时,可以调用ftell来查看)
#include <stdio.h>
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "rb");
if (pFile == NULL)
{
perror("fopen");
return 1;
}
//我们在文件内放入 abcdefgh
fputs("abcdefgh", pFile);
int ch=fgetc(pFile);//用int接收,fgetc返回值类型为int
printf("%c\n",ch);//打印返回的字符
ch = fgetc(pFile);
printf("%c\n",ch);
int pos = ftell(pFile);
printf("%d\n", pos);
fclose(pFile);
pFile = NULL;
return 0;
}
此时偏移了两个字符,pos的结果应该为2
a
b
2
3.rewind
void rewind ( FILE * stream );
让文件指针的位置回到文件的起始位置(也可以用fseek来实现,fseek(pFile,0,FEEK_SET))
我们还用刚才的代码来示例
#include <stdio.h>
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "rb");
if (pFile == NULL)
{
perror("fopen");
return 1;
}
//我们在文件内放入 abcdefgh
fputs("abcdefgh", pFile);
int ch=fgetc(pFile);//用int接收,fgetc返回值类型为int
printf("%c\n",ch);//打印返回的字符
ch = fgetc(pFile);
printf("%c\n",ch);
rewind(pFile);//指针返回起始位置
int pos = ftell(pFile);
printf("%d\n", pos);
fclose(pFile);
pFile = NULL;
return 0;
}
当fgetc偏移两个字符后,rewind又让指针回到了起始位置,此时pos的值为0
a
b
0
五、文本文件和二进制文件
在介绍文件时,我们提到了数据文件,而数据文件,又可以分文文本文件和二进制文件
六、文件读取结束的判定
feof 和 ferror 都是用来检查文件流状态的函数,但它们检查的状态不同
ferror
ferror是用来检查文件流是否发生错误,发生错误返回非零值,否则返回零
int ferror ( FILE * stream );
如我们上面介绍的函数中,返回值错误时会设置feeor
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile=fopen("myfile.txt","r");
if (pFile==NULL)
{ perror ("Error opening file");
return 1;
}
else
{
fputc ('x',pFile);
if (ferror (pFile))
{
printf ("Error Writing to myfile.txt\n");
break;
}
fclose (pFile);
}
return 0;
}
在这个例子中,我们用ferror来判断函数读取字符后是否发生错误,如果返回值为非零(即发生错误),则进入if循环内并结束循环
feof
feof是用来检查文件流是否到达了文件末尾,返回非零值;否则返回零
int feof ( FILE * stream );
如我们上面介绍的 fgetc 函数,如果读取时到达了文件末尾,则返回feof
#include <stdio.h>
int main(void)
{
FILE *fp;
int ch;
fp = fopen("file.txt", "r");
if (fp == NULL)
{
perror("fopen");
return 1;
}
while (!feof(fp))
{
ch = fgetc(fp);
if (ch != EOF)
{
putchar(ch);
}
}
fclose(fp);
return 0;
}
在这个例子中,我们用feof来检查是否到达了文件末尾,如果返回值为非零(即发生错误),则不进入while循环内