一、什么是文件

磁盘上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件

  • 程序文件:
    • 包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
  • 数据文件:
    • 文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内
      容的文件。

本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上
文件。


二、文件类型

  • 根据数据的组织形式,数据文件被称为文本文件或者二进制文件
  • 数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
  • 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件

那么数据在内存中是怎么存储的呢?

字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。
举个栗子:使用二进制写入文件

#include <stdio.h>
#include <stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("test.txt", "wb");
	fwrite(&a, 4, 1, pf);//二进制的形式写到文件中
	fclose(pf);
	pf = NULL;
	return 0;
}

16进制转换图:
在这里插入图片描述

三、文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,重命名为:FILE.
例如,VS2008编译环境提供的
stdio.h 头文件中有以下的文件类型申明:
例如,VS2008编译环境提供的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*的指针变量:

 FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

四、文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC 规定使用fopen函数来打开文件,fclose函数来关闭文件。

fopen函数的原型如下:
FILE * fopen ( const char * filename, const char * mode )

  • filename:C 字符串,包含要打开的文件的路径名。(如果是相对路径只会找工程目录下有无对应的文件名)
  • mode:C 字符串包含文件访问模式(如下表格)
文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据出错
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个新的二进制文件建立一个新的文件
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

fclose函数的原型如下:
int fclose ( FILE * stream );

  • FILE * stream:指向一个 FILE 对象的指针,指定要关闭的流。

在这里插入图片描述
使用ASCII码的形式写入到"myfile.txt"文件中
在此之前,我的当前工程文件路径下是没有"myfile.txt"文件的,我们使用先对路径测试。
在这里插入图片描述

int main()
{
    FILE* pFile;
    pFile = fopen("myfile.txt", "w");
    if (pFile != NULL)
    {
        fputs("fopen example", pFile);
        fclose(pFile);
    }
    return 0;
}

程序运行结果:

在这里插入图片描述
在成功运行后,为你们打开工程文件查看,发现使用"w"只写的访问模式下,会自动创建一个"myfile.txt"文件。 并且也已经在"myfile.txt"文件中写入了"fopen example"
在这里插入图片描述

五、文件的顺序读写

下表格为文件操作函数,这里不才推荐掌握fscanf()fprintf()函数,其他的会查文旦即可。

功能函数名适用于
字符输入函数fgetc所有输⼊流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入fread文件
二进制输出fwrite文件

六、fscanf与fprintf

6.1、fprintf

fprint()函数的原型如下:
int fprintf ( FILE * stream, const char * format, ... );

  • stream:存放文件指针,用于fprintf函数访问到对应的文件。
  • format,...:与普通的printf函数相同的使用

fprintf函数的用法:

  • format,...中的数据写入到文件中

fprintfprintf的区别:

  • fprintf的使用输出方式printf模式是相同的,只是输出流不一样
    • printf的写入的输出流是标准输出流,即把所有的format,...参数写入到标准输出流中。
    • fprintf的写入的输出流是文件,即把所有的format,...参数写入到文件中。

在这里插入图片描述
我们上面的例子中已经在"myfile.txt"文件中存有"fopen example"数据,我们在后面追加数据:“你干嘛~~哎哟~”。

int main()
{
    char arr[] = "你干嘛~~哎哟~";
    FILE* pFile;
    pFile = fopen("myfile.txt", "a");
    if (pFile != NULL)
    {
        fprintf(pFile, "\n%s\n",arr);
        printf("已完成写入\n");
        fclose(pFile);
    }
    return 0;
}
  • 因为需求是追加,所以需要把fopen的访问模式设置为a追加模式。
  • fprintf(pFile, "\n%s\n",arr);:以%s的形式把arr的内容写入到pFile文件指针指向的文件中。

运行成功后,我们查看"myfile.txt"文件
在这里插入图片描述
可以看到在"myfile.txt"文件文件中,我们成功追加了鸽经典语录了~


6.2、fscanf

fscanf()函数的原型如下:
int fscanf ( FILE * stream, const char * format, ... );

  • stream:指向 FILE 对象的指针,用于标识从中读取数据的输入流。
  • format:与普通的scanf相同。

fscanf的用法:

  • 从流中读取数据,并根据参数格式将它们存储到由额外参数指针的位置。
  • 附加参数应指向格式字符串中其对应格式说明符指定的类型所指定的已分配对象。
  • 即读取format文件指针内部的数据存储到stream,...指针指向的变量中。

fscanfscanf的区别:

  • fscanf的使用写入方式scanf模式是相同的,只是输入流不一样
    • scanf的读取输入流是标准输入流,从第一个字符开始读取,知道遇到 换行\n空格结束读取
    • fscanf的读取输入流是文件内容,也是从第一个字符开始读取,知道遇到 换行\n空格结束读取

在这里插入图片描述

我们刚刚在"myfile.txt"文件中,加入了鸽鸽的语录,现在我们要读取出来赋值给变量。

int main()
{
    char arr[20] = {0};
    FILE* pFile;
    pFile = fopen("myfile.txt", "r");
    if (pFile != NULL)
    {
        fscanf(pFile, "%s",arr);
        printf("读取的内容为:%s\n",arr);
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);
        fclose(pFile);
    }
    return 0;
}
  • 我们现在只需要读取文件中的内容,所以我们需要把fopen函数中的读取方式改为"r"只读模式
  • fscanf(pFile, "%s",arr);:每次只读取到 换行\n空格 ,所以只能多次读取后,才可以读取到鸽的语录。

我们查看运行结果:
在这里插入图片描述

  • 未进行fscanf时,内存图如下:在这里插入图片描述

  • 第一次读取后,内存图如下:在这里插入图片描述

  • 第二次读取后,内存图如下:在这里插入图片描述

如果多次的读取,已经超过了文件本身存储的数据后,文件指针指向最后一组数据并且不再改变

在这里插入图片描述

int main()
{
    char arr[20] = {0};
    FILE* pFile;
    pFile = fopen("myfile.txt", "r");
    if (pFile != NULL)
    {
        fscanf(pFile, "%s",arr);
        printf("读取的内容为:%s\n",arr);//1次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//2次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//3次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//4次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//5次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//6次
        fclose(pFile);
    }
    return 0;
}

运行结果为:
在这里插入图片描述

我们进行一次fprintffscanf,都是在操作FILE指针,读取时FILE指针指向文件内容的哪里,我们返回的结果就是什么

七、sscanf与sprintf

既然我们认识到了fprintffscanf,那我们也学习一下sscanfsprintf,我们知道fprintffscanf前面的f代表着FILE文件的意思,那我们sscanfsprintf前面的s就代表着string字符串的意思。

7.1sprintf函数

构造一个字符串其中包含如果使用 printf 格式化,则会打印出的相同文本但内容不是打印出来,而是作为 C 字符串存储在由 str 指向的缓冲区中。会在内容之后自动附加一个终止字符

fscanf()函数的原型如下:
int sprintf ( char * str, const char * format, ... );

  • str:指向存储 sprintf 函数制造的字符串内容的指针。注意空间需要足够大,以容纳生成的字符串。
  • format, ... :与普通printf相同的格式

sprintf()函数的返回值:

  • 成功时,返回写入的字符总数。此计数不包括字符串末尾自动附加终止字符
  • 失败时,返回一个负数

在这里插入图片描述

int main()
{
	char buffer[50];
	int n, a = 5, b = 3;
	n = sprintf(buffer, "%d + %d = %d", a, b, a + b);
	printf("[%s] 写入了 %d 个字符\n", buffer, n);
	return 0;
}

运行结果:
在这里插入图片描述

解析上面代码:

  • sprintf函数生成最终的字符串前,三个%d被自动打印为对应的整形变量。
  • sprintf函数生成的字符串,被buffer数组接收,最后以%s的形式打印出字符串
  • sprintf函数的返回值的计算是最终字符串中有多少个字符,不是以为创建时来计算。

sprintfprintf的区别:

  • sprintf的使用输出方式printf模式是相同的,只是输出流不一样
    • printf的写入的输出流是标准输出流,即把所有的format,...参数写入到标准输出流中。
    • sprintf的写入的输出流是字符串,即把所有的format,...参数写入到字符串中。

7.2、sscanf函数

sscanf()函数的原型如下:
int sscanf ( const char *s, const char * format, ...);

  • 从 s 读取数据,并根据参数格式将其存储到额外参数提供的位置,就像使用 scanf 一样,但从 s 读取而不是标准输入(stdin)。

sscanf()函数的返回值:

  • 成功时,函数返回成功填充参数列表的项目数量。这个计数可以与预期的项目数量匹配,也可能少于(甚至为零),原因可能是匹配失败、读取错误,或者到达文件末尾。
  • scanf相同当读取失败的时候或者遇到文件结束的时候,都会返回EOF
  • 当接收到EOF后需要判断是因为什么导致结束的。
    在这里插入图片描述
int main()
{
	char sentence[] = "Rudolph is 12 years old";
	char str[20];
	char str1[20];
	char str2[20];
	int i;

	sscanf(sentence, "%s %*s  %d %s %s", str, &i, str1, str2);
	printf("%s -> %d |str1 = %s |str2 = %s\n", str, i, str1, str2);

	return 0;
}

运行结果为:
在这里插入图片描述

  • sscanf()函数中,%*s可用于跳过字符串中所对应的字符,直到遇到\n空格结束跳过。
  • sscanf()函数把传入的字符串 分解为 多个小字符串并且写入到对应的变量中。

sscanfscanf的区别:

  • sscanf的使用写入方式scanf模式是相同的,只是输入流不一样
    • scanf的读取输入流是标准输入流,从第一个字符开始读取,知道遇到 换行\n空格结束读取
    • sscanf的读取输入流是字符串,也是从第一个字符开始读取,知道遇到 换行\n空格结束读取

八、文件指针的操作

8.1、fseek函数

设置新的指针的偏移量

fseek函数的原型如下:
int fseek ( FILE * stream, long int offset, int origin );

  • stream文件指针
  • offset偏移位数。如果是二进制文件:从原点偏移的字节数
  • origin用于偏移的起始位置。只能用下图的三种形式:在这里插入图片描述
    在这里插入图片描述

我们还是使用坤坤语录的例子。

int main()
{
    char arr[20] = {0};
    FILE* pFile;
    pFile = fopen("myfile.txt", "r");
    if (pFile != NULL)
    {
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//1次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//2次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//3次

        fseek(pFile, 6, SEEK_SET);//把pFile指针调整回开头后第六个偏移处。

        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//4次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//5次
    }
    return 0;
}

结果为:
在这里插入图片描述

  • 我们还是使用坤坤语录的那里例子,在我们之前fscanf第3次后读取的内容都是:“你干嘛~哎哟”,但是我们使用了fseek函数后,把pFile设置新的指针的偏移量,设置在了文件开头的第六位偏移量中。
  • 进行了3次fscanf后,内存图如下:在这里插入图片描述
  • 使用了fseek函数后,内存图如下:在这里插入图片描述
  • 第4次fscanf,内存图如下:在这里插入图片描述

8.2、rewind函数

(rewind 倒带)顾名思义就是把FILE指针重新回到开头位置

rewind()函数的原型是:
void rewind ( FILE * stream );

在这里插入图片描述
我们还是举坤坤的例子。

int main()
{
    char arr[20] = { 0 };
    FILE* pFile;
    pFile = fopen("myfile.txt", "r");
    if (pFile != NULL)
    {
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//1次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//2次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//3次

        rewind(pFile);//把pFile指针调整回开头处。

        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//4次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//5次
        fscanf(pFile, "%s", arr);
        printf("读取的内容为:%s\n", arr);//5次
    }
    return 0;
}

运行结果为:
在这里插入图片描述

九、文件结束判定函数(feof)

输入流中(getc类型与scanf类型)当读取失败的时候或者遇到结束的时候,都会返回EOF
gets类型当读取失败的时候或者遇到结束的时候,返回NULL

我们在判断文件是否结束时,应当在输入函数返回结束标记后使用feof函数来进行判断是否因为文件结束而返回EOF(或NULL)的。

在这里插入图片描述

int main(void)
{
    int c; // 注意:int,非char,要求处理EOF
    char arr[20] = { 0 };
    FILE* fp = fopen("myfile.txt", "r");
    if (!fp) {
        perror("File opening failed");
        return 0;
    }
    //fgetc 与 fscanf 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ((c = fscanf(fp,"%s",arr)) != EOF) // 标准C I/O读取文件循环
    {
        printf("%s\n", arr);
    }
    //判断是什么原因结束的
    if (ferror(fp))
        puts("I/O error when reading");
    else if (feof(fp))
        puts("End of file reached successfully");
    fclose(fp);
    return 0;
}
  • 在上述函数中,fscanf函数返回了EOF结束了文件的读取。
  • 但是fscanf函数是因为什么原因而返回EOF呢,我们需要分类讨论
    • 首先使用perror函数来检查文件指针fp是否有错,这样可以排查是因为错误而导致fscanf函数返回EOF
    • 之后使用feof函数来检查是否因为读取到了文件结尾而返回EOF。
  • 通过上面连个步骤就可以排查问题所在了。

以上就是本章所有内容。若有勘误请私信不才。万分感激 如果对大家有用的话,就请多多为我点赞收藏吧~~~

ps:表情包来自网络,侵删
本篇文章有提到输入输出流,若是本篇笔记点赞过三百,不才会专门写一篇C语言的详细输入输出流笔记~~~~

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部