器→工具, 编程语言

C语言学习之文件操作

钱魏Way · · 11 次浏览

什么是文件?

文件其实是指一组相关数据的有序集合。这个数据集有一个名称,叫做文件名。文件通常是驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来。

从文件功能上来讲,一般可分为:程序文件与数据文件

  • 程序文件:这类文件包含了可执行的程序代码。它们可以是二进制的可执行文件,也可以是脚本文件,脚本文件包含了可以由解释器直接执行的文本指令。例如,Windows系统中的.exe文件和Unix/Linux系统中的.bash文件都是程序文件。在C语言中,源代码文件(后缀为.c的文件)经过编译后,会生成一个可执行的程序文件。这个程序文件包含了用机器语言编写的指令,并且可以直接被操作系统加载并执行。
  • 数据文件:这类文件包含了用来输入或输出的数据,但不包含可执行的代码。数据文件可以有很多种格式,包括文本文件(如.txt文件或.csv文件)和二进制文件(如.jpg图片文件或.docx文档文件)等。数据文件通常由程序文件读取和写入。在C语言中,你可以使用标准I/O函数(如fopen, fread, fwrite等)来操作数据文件,读取文件中的数据或向文件中写入数据。

文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用,这就是文件名。

以下是关于文件的一些基本概念:

  • 文件名:每个文件都有一个唯一的名称,称为文件名,用于标识文件。在大多数操作系统中,文件名后面通常跟着一个文件扩展名,用来表示文件的类型。
  • 文件格式:文件的格式取决于文件中数据的组织方式。例如,文本文件通常使用.txt或.doc格式,图片文件可能使用.jpg或.png格式,而音频文件可能使用.mp3或.wav格式。
  • 文件大小:文件的大小是指文件中包含的数据量,通常以字节(Byte)为单位度量。
  • 文件路径:文件路径是文件在文件系统中的位置,通常包括驱动器名、目录名和文件名。
  • 文件属性:文件属性包括文件的创建时间、修改时间、访问权限等信息。
  • 文件操作:文件操作包括创建、打开、读取、写入、关闭和删除文件等。

文件是操作系统和应用程序管理和组织数据的重要方式。通过文件,用户可以在电脑上保存工作,分享信息,安装和运行软件等。

程序文件

程序文件是电脑上用来执行特定任务的文件。它们包含了由特定编程语言编写的代码,用于指导计算机完成特定的操作或任务。根据编程语言和平台的不同,程序文件可能有各种不同的文件扩展名,如 .exe, .bat, .sh, .py, .java, .c, .cpp等。

以下是一些常见类型的程序文件:

  • 可执行文件(Executable Files):这些文件包含了用于指导计算机执行特定任务的机器代码。在Windows系统中,可执行文件通常有 .exe, .bat, .com 或 .cmd 等扩展名。在Unix或Linux系统中,可执行文件没有固定的扩展名,但可以通过设置文件的执行权限来使其成为可执行文件。
  • 脚本文件(Script Files):脚本文件包含了用于自动执行一系列任务的命令。这些文件通常由脚本语言编写,如Python(.py)、Perl(.pl)、Shell(.sh)等,它们需要相应的解释器来执行。
  • 源代码文件(Source Code Files):这些文件包含了程序的源代码,通常由高级编程语言编写,如Java(.java)、C(.c)、C++(.cpp)等,它们需要通过编译器将源代码编译成机器代码,然后再执行。
  • 库文件(Library Files):库文件包含了预编译的代码片段,这些代码片段可以被其他程序引用和使用。在Windows中,库文件通常有 .dll (动态链接库) 或 .lib (静态链接库) 的扩展名。在Unix或Linux系统中,库文件通常有 .so(共享库)或 .a(静态库)的扩展名。

程序文件是实现软件功能的重要组成部分。它们通常由程序员编写和维护,但也可以通过特定的开发工具自动生成。

在C语言中源程序文件经过编译器链接与链接器链接可以生成我们的可执行程序的文件。

数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。

数据文件是用于存储特定类型数据的文件。它们可以包含各种类型的数据,包括文本、数字、图像、音频、视频等。数据文件可以被应用程序、操作系统或软件工具读取和写入,进行数据的操作或处理。

以下是一些常见类型的数据文件:

  • 文本文件(Text Files):这种类型的数据文件通常包含不含格式的纯文本数据,例如 .txt 文件。用户可以使用任何文本编辑器打开和编辑这些文件。
  • 数据库文件(Database Files):数据库文件存储结构化数据,这些文件通常由数据库管理系统(DBMS)创建和管理,如 MySQL、Oracle、MongoDB 等。它们通常包含表、索引、视图等数据库对象。
  • 电子表格文件(Spreadsheet Files):这些文件,如 .xls 或 .xlsx(Microsoft Excel),.ods(OpenOffice Calc)等,包含在电子表格中组织的数据,这些数据可以进行排序、过滤、计算等操作。
  • 图像、音频和视频文件:这些文件包含多媒体数据。图像文件类型包括 .jpg, .png, .gif 等;音频文件类型包括 .mp3, .wav, .aac 等;视频文件类型包括 .mp4, .avi, .mov 等。
  • 二进制文件(Binary Files):二进制文件直接包含了二进制格式的数据,这些数据可以是任何类型,也包括一些非文本数据。二进制文件通常由特定的程序或设备生成,并需要特定的程序或设备来读取。

数据文件用于在不同的应用程序之间共享和交换数据,同时也用于存储和备份数据。

其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。

如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们就需要使用文件。

文件的打开和关闭

流与标准流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。

在计算机科学中,流(Stream)是一种抽象接口,用于读写数据。流可以是输入流(从源头读取数据)或输出流(向目的地写入数据)。流中的数据可以是任何东西,包括文件、内存、网络连接等。

标准流是预先打开的输入和输出通信通道,通常与程序的输入、输出和错误报告相关联。在大多数操作系统中,都有三种类型的标准流:

  • 标准输入(Standard Input):通常以stdin表示,它是程序读取输入数据的主要方式。默认情况下,标准输入是键盘。
  • 标准输出(Standard Output):通常以stdout表示,它是程序写入输出数据的主要方式。默认情况下,标准输出是计算机屏幕。
  • 标准错误(Standard Error):通常以stderr表示,它是程序写入错误信息的方式。默认情况下,标准错误也是计算机屏幕。

其中,stdin的文件描述符为0,stdout的文件描述符为1,stderr的文件描述符为2。这些文件流在程序启动时自动打开,当程序结束时自动关闭。因此,不需要(也不应该)手动调用fopen或fclose来打开或关闭这些文件流。这些流可以被重定向,即可以被重新指定到其他设备或文件。

标准流的一个重要特点是可以被重定向。例如,你可以将程序的输出重定向到一个文件中,或者将一个文件的内容重定向到程序的输入。这使得可以在不修改程序代码的情况下,改变程序的输入和输出行为。

例如,当我们在命令行运行一个程序时,我们可以使用>操作符来将stdout重定向到一个文件,使用>操作符来将stderr重定向到一个文件,使用<操作符来将stdin重定向从一个文件读取。

stdin、stdout、stderr三个流的类型是: FILE* ,通常称为文件指针。在C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的。

文件指针

在C语言中,FILE是一个用于文件操作的重要的结构体类型,定义在stdio.h头文件中。它包含了所有用于文件I/O操作的必要信息。当我们使用fopen函数打开一个文件时,会返回一个指向FILE类型的指针,我们可以通过这个指针进行后续的文件操作。

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。

例如,VS2022编译环境提供的 stdio.h 头文件中有以下的文件类型申明:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
       };
typedef struct _iobuf FILE;
FILE* pf;//文件指针变量

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。通常包含如下信息:

  • 文件名
  • 文件状态(如是否到达文件末尾、是否发生错误等)
  • 当前读写位置
  • 文件的缓冲区
  • 文件的访问模式(如只读、只写、读写等)
  • 和底层的文件描述符等信息

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。

一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。我们来看看如何创建一个FILE的指针变量

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

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

但是需要注意的是,FILE结构体的具体内容对于程序员来说是不可见的,我们不能直接访问和操作FILE结构体的成员。我们应该通过标准库提供的函数(如fopen、fclose、fread、fwrite等)来操作文件。

例如,下面的代码展示了如何使用FILE指针来打开一个文件,并向文件中写入数据:

FILE *fp = fopen("file.txt", "w");
if (fp == NULL) {
    perror("fopen");
    return 1;
}
fprintf(fp, "Hello, World!\n");
fclose(fp);

这段代码首先调用fopen函数打开一个名为”file.txt”的文件,然后调用fprintf函数向文件中写入”Hello, World!\n”,最后调用fclose函数关闭文件。如果在打开文件时发生错误,fopen函数会返回NULL,并设置全局错误变量errno,我们可以通过检查fopen的返回值并调用perror函数来打印错误消息。

文件的打开与关闭

文件在读写之前应该先打开,在使用结束之后应该关闭。这与我们前面学习的动态内存开辟很类似。

在编写程序的时候,打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。

ANSIC规定使用 fopen 函数来打开文件, fclose 来关闭文件。

fopen函数

  • 头文件#include<stdio.h>
  • 声明:FILEfopen(const char filename, const char *mode)
    • filename– 字符串,表示要打开的文件名称。
    • mode– 字符串,表示文件的访问模式。
  • 作用:使用给定的模式mode 打开 filename 所指向的文件
  • 返回值:该函数返回一个 FILE 指针。否则返回 NULL,且设置全局变量 errno 来标识错误。

下表为常见的访问模式(mode):

文件使用方式 含义 如果指定文件不存在
“r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
“a”(追加) 向文本文件尾添加数据 建立一个新的文件
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件
“ab”(追加) 向一个二进制文件尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,建议一个新的文件 建立一个新的文件
“a+”(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件 出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“a+”(读写) 打开一个二进制文件,在文件尾进行读写 建立一个新的文件

这些模式字符串决定了你对文件的访问权限,但并不能决定文件的系统权限,如读、写和执行权限。文件的系统权限由操作系统决定,通常在创建文件时由umask决定,也可以使用chmod或其他工具修改。

fclose函数

  • 头文件#include<stdio.h>
  • 声明:int fclose(FILE *stream)
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流。
  • 作用:关闭流 stream。刷新所有的缓冲区
  • 返回值:如果流成功关闭,则该方法返回零。如果失败,则返回 EOF(通常为-1)。

下列是fopen与fclose具体使用:

int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }    
    //关闭文件
    fclose(pf);
    pf = NULL;        //防止野指针
    return 0;
}

文件的打开和关闭是配对的,打开一个文件后,当不再需要时,应当关闭它。否则,如果打开的文件过多并且没有关闭,可能会因为系统资源不足而导致程序崩溃或者其他问题。此外,有些系统可能会缓存写入文件的数据,如果不关闭文件,可能会导致部分数据没有实际写入文件。

二进制文件

在计算机中,文件可以大致地分为文本文件和二进制文件两种类型。

  • 文本文件:文本文件是由字符(如字母、数字、符号等)组成的文件,这些字符通常按照某种字符编码(如ASCII、UTF-8等)存储。文本文件可以直接使用文本编辑器(如记事本、文本编辑器等)打开并查看其内容。因为文本文件的内容直接对应于人类可读的字符,所以它们通常在需要人类阅读或编辑的情况下使用。例如,源代码文件、配置文件、XML文件、CSV文件等都是文本文件。在C语言中打开文本文件时,通常使用”fopen”函数的模式参数为”r”、”w”、”a”等。
  • 二进制文件:二进制文件是由二进制数据组成的文件,这些数据不一定对应于人类可读的字符。二进制文件的内容通常对应于某种特定的数据结构或协议,可以是图片、音频、视频、可执行程序等。二进制文件通常不能直接用文本编辑器打开。在C语言中打开二进制文件时,通常使用”fopen”函数的模式参数为”rb”、”wb”、”ab”等。

二进制文件是指直接包含二进制数据的文件。与文本文件(只包含可打印的字符和特定的控制字符)不同,二进制文件包含了一种或多种不同类型的数据,这些数据可能是字符、整数、浮点数,甚至是自定义的数据类型。

以下是一些二进制文件的特性:

  • 存储格式:二进制文件将数据以二进制形式存储,这通常是非常高效且紧凑的。这意味着它们通常比等效的文本文件占用更少的磁盘空间。
  • 访问速度:由于二进制文件直接存储了数据的二进制表示,因此在读取和写入数据时通常比文本文件更快。
  • 可读性:二进制文件通常不能直接被人类读取,需要用一些特定的工具(如十六进制编辑器)来查看,或者通过编程语言来读取和解析。
  • 兼容性:二进制文件的格式通常取决于创建它的程序,因此可能不同的程序或操作系统之间可能存在兼容性问题。

二进制文件广泛地用于各种不同的场景,包括可执行文件、数据库文件、图像、音频和视频文件等。在处理二进制文件时,通常需要知道或理解它的格式或结构,否则可能无法正确地解析和使用其中的数据。

虽然文本文件和二进制文件在物理存储上都是以二进制形式存储的,但是二者在逻辑上和使用上有很大的区别。文本文件更便于人类阅读和编辑,而二进制文件则更适合于计算机程序直接处理。

C语言中文件目录是不是文本文件?

在C语言或者一般来说,文件目录不是文本文件,也不是二进制文件。文件目录是文件系统用来管理文件的一种数据结构。

简单来说,文件目录是用来存储文件或者其他目录信息的系统对象。它可以包含文件的名称、位置、大小和其他一些元数据。这些信息使得操作系统能够管理和查找文件,例如,通过路径名称访问特定的文件或者目录。

在C语言中,我们不能直接像打开和读写普通文件那样打开和读写一个目录。但是我们可以使用一些特定的函数来访问和操作目录。例如,opendir、readdir、closedir等函数可以用来打开目录、读取目录中的内容和关闭目录。

文件的读写

C语言中,文件的读写通常可以分为顺序读写和随机读写两种方式。

  • 顺序读写:顺序读写是指按照文件中的字节序列,从头到尾一次性或连续性地进行读取或写入。换句话说,是按照文件中的数据顺序,不跳跃地进行读写。例如,使用fgetc,fputc,fgets,fputs,fread,fwrite等函数进行的通常都是顺序读写。
  • 随机读写:随机读写是指可以在文件中任意位置开始读写,换句话说,可以跳到文件中的任何位置进行读或写操作。在C语言中,可以使用fseek函数设置文件位置指针到文件的任意位置,然后使用fread或fwrite进行读写。ftell函数可以用来获取当前文件位置指针的位置。rewind函数可以将文件位置指针重置到文件的开始位置。

这两种读写方式各有用途,选择使用哪一种方式取决于你的具体需求。比如,如果你需要处理大量的数据,并且每一次都需要从头到尾读取所有数据,那么顺序读写可能更适合。而如果你需要访问文件的特定部分,而不需要读取整个文件,那么随机读写可能会更有效率。

文件的顺序读写

单字符输入输出

fputc函数

  • 头文件:#include<stdio.h>
  • 声明:int fputc(int char, FILE *stream)
    • char– 这是要被写入的字符。该字符以其对应的 int 值进行传递。
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。
  • 作用:把参数char 指定的字符(一个无符号字符)写入到指定的流 stream 中。
  • 返回值:如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

下列是具体的fputc的使用方法:

#include<stdio.h>
int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    //将abc放进文件
    fputc('a', pf);
    fputc('b', pf);
    fputc('c', pf);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

fgetc函数

  • 头文件:#include<stdio.h>
  • 声明:int fgetc(FILE *stream)
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。
  • 作用:从指定的流 stream 获取下一个字符(一个无符号字符)。
  • 返回值:该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。

下列是具体的fgetc的使用方法:

#include<stdio.h>
int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "r");//只读
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fputc('a', pf);
    fputc('b', pf);
    fputc('c', pf);
    int ch = fgetc(pf);
    printf("读出来的字符为:%c\n", ch);
    ch = fgetc(pf);
    printf("读出来的字符为:%c\n", ch);
    ch = fgetc(pf);
    printf("读出来的字符为:%c\n", ch);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

文本行输入输出

fputs函数

  • 头文件:#include<stdio.h>
  • 声明:int fputs(const charstr, FILE stream)
    • str– 这是一个数组,包含了要写入的以空字符终止的字符序列。
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
  • 作用:把字符串写入到指定的流 stream 中,但不包括空字符。
  • 返回值:该函数返回一个非负值,如果发生错误则返回 EOF。

下面是fputs的具体使用方法:

#include<stdio.h>
int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fputs("hello betty", pf);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

fgets函数

  • 头文件:#include<stdio.h>
  • 声明:charfgets(char str, int n, FILE *stream)
    • str– 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
    • n– 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
  • 作用:从指定的流 stream 读取一行,并把它存储在str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
  • 返回值:如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。如果发生错误,返回一个空指针。

下面是fgets的具体使用方法:

#include<stdio.h>
int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fputs("hello betty", pf);
    char arr[] = "##########";
    fgets(arr, 5, pf);
    puts(arr);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

虽然读取五个字符,但是只会显示四个字符,因为’\0也会默认添加进去

格式化输入输出

fprintf函数

  • 头文件:#include<stdio.h>
  • 声明:int fprintf(FILEstream, const char format, …)
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
    • format– 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。
  • 作用:按照一定格式向输出流输出数据。
  • 返回值:如果成功,则返回写入的字符总数,否则返回一个负数。

下面是fprintf的具体使用方法:

typedef struct student {
    char name[20];
    int height;
    float score;
}stu;
int main()
{
    stu s = { "beidi", 170, 95.0 };
    //写文件
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fprintf(pf, "%s %d %f", s.name, s.height, s.score);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

fscanf函数

  • 头文件:#include<stdio.h>
  • 声明:int fscanf(FILEstream, const char format, …)
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
    • format– 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符和 format 说明符。
  • 作用:按照一定格式从输入流输入数据。
  • 返回值:如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。

下面是fscanf的具体使用方法:

typedef struct student {
    char name[20];
    int height;
    float score;
}stu;
int main()
{
    stu s = { "beidi", 170, 95.0 };
    //写文件
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fscanf(pf, "%s %d %f", s.name, &(s.height), &(s.score));
    printf("%s %d %f", s.name, s.height, s.score);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

二进制输入输出

fread和fwrite是C语言中用于读取和写入二进制文件的两个函数。它们都可以一次处理多个数据项,非常适合于处理数组或结构体等数据结构。

fwrite函数

  • 头文件:#include<stdio.h>
  • 声明:size_t fwrite(const voidptr, size_t size, size_t nmemb, FILE stream)
    • ptr– 这是指向要被写入的元素数组的指针。
    • size– 这是要被写入的每个元素的大小,以字节为单位。
    • nmemb– 这是元素的个数,每个元素的大小为 size 字节。
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
  • 作用:把ptr 所指向的数组中的数据写入到给定流 stream 中。
  • 返回值:如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。

下面是fwrite的具体使用方法:

typedef struct student {
    char name[20];
    int height;
    float score;
}stu;
int main()
{
    stu s = { "beidi", 170, 95.0 };
    //写文件
    FILE* pf = fopen("test.txt", "wb");//二进制写入
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fwrite(&s, sizeof(s), 1, pf);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

二进制数据正常人是无法看懂的,但是电脑能准确识别

fread函数

  • 头文件:#include<stdio.h>
  • 声明:size_t fread(voidptr, size_t size, size_t nmemb, FILE stream)
    • ptr– 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
    • size– 这是要读取的每个元素的大小,以字节为单位。
    • nmemb– 这是元素的个数,每个元素的大小为 size 字节。
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
  • 作用:从给定流stream 读取数据到 ptr 所指向的数组中
  • 返回值:成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

下面是fread的具体使用方法

typedef struct student {
    char name[20];
    int height;
    float score;
}stu;
int main()
{
    stu s = {0};
    //写文件
    FILE* pf = fopen("test.txt", "rb");//二进制写出
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fread(&s, sizeof(s), 1, pf);
    printf("%s %d %f", s.name, s. height, s.score);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

流输入输出

从前面我们知道在我们输入输出时,默认打开stdin – 标准输入流 , stdout – 标准输出流, stderr – 标准输错误 。那我们可不可以利用流来输入输出呢?答案自然是可以的,下面是具体实例:

int main()
{
    printf("输入前:");
    int ch = fgetc(stdin);//输入
    printf("输入后:");
    fputc(ch, stdout);//输出
    return 0;
}

还有另外一种方法:

int main()
{
    printf("输入前:");
    int ch = 0;
    fscanf(stdin, "%c", &ch);
    printf("输入后:");
    fprintf(stdout, "%c", ch);
    return 0;
}

补充:C语言常见输入输出函数

这些函数都是C语言中用于输入输出的函数,以下是他们的作用和区别:

  • scanf: 这个函数从标准输入(通常是键盘)读取数据。例如scanf(“%d”, &i);会从标准输入读取一个整数并存储到变量i中。
  • printf: 这个函数向标准输出(通常是屏幕)写入数据。例如printf(“%d”, i);会把变量i的值显示在屏幕上。
  • fscanf: 这个函数从一个文件读取数据。例如fscanf(fp, “%d”, &i);会从文件指针fp指向的文件读取一个整数并存储到变量i中。
  • fprintf: 这个函数向一个文件写入数据。例如fprintf(fp, “%d”, i);会把变量i的值写入到文件指针fp指向的文件中。
  • sscanf: 这个函数从一个字符串读取数据。例如sscanf(str, “%d”, &i);会从字符串str中读取一个整数并存储到变量i中。
  • sprintf: 这个函数把格式化的数据写入到一个字符串中。例如sprintf(str, “%d”, i);会把变量i的值格式化为一个字符串并存储到str中。
  • snprintf: 这个函数把格式化的数据写入到一个字符串中,并指定最大长度。例如snprintf(str, size, “%d”, i);会把变量i的值格式化为一个字符串并存储到str中,长度不超过size。

这些函数都使用格式化字符串来确定要读取或写入的数据的格式,格式化字符串包含一系列的格式化说明符,例如%d用于整数,%f用于浮点数,%s用于字符串等。

文件的随机读写

fseek函数

  • 头文件:#include<stdio.h>
  • 声明:int fseek(FILE *stream, long int offset, int whence)
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
    • offset– 这是相对 whence 的偏移量,以字节为单位。
    • whence– 这是表示开始添加偏移 offset 的位置。
  • 作用:设置流stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
  • 返回值:如果成功,则该函数返回零,否则返回非零值。

whence偏移offset的三种位置:

常量 描述
SEEK_SET 文件的开头
SEEK_CUR 文件指针的当前位置
SEEK_END 文件的末尾

假设文件中放的是字符串““abcdef”,下面是fseek的具体使用实例:

int main()
{
    //打开文件
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror(" fopen fail");
        return 1;
    }
    fseek(pf, 4, SEEK_SET);
    //从其实位置偏移四个字节
    int ch1 = fgetc(pf);
    printf("%c ", ch1);
    fseek(pf, -3, SEEK_END);
    //从结束位置偏移七个个字节
         int ch2 = fgetc(pf);
    printf("%c ", ch2);
    fseek(pf, 1, SEEK_CUR);
    //从当前位置偏移一个字节
    int ch3 = fgetc(pf);
    printf("%c ", ch3);
    //关闭文件
    fclose(pf);
    pf = NULL;//防止野指针
    return 0;
}

  • 从起始位置偏移四个字节,输出e。
  • 从末尾偏移三个字节,输出d。
  • 此时偏移指向e的位置,再偏移一个字节指向f。

ftell函数

  • 头文件:#include<stdio.h>
  • 声明:long int ftell(FILE *stream)
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • 作用:返回文件指针相对于起始位置的偏移量
  • 返回值:该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。

我们可以利用fseek和ftell来计算文件的长度(不包含’\0′),下列是代码示例

int main()
{
    FILE* pFile;
    long size;
    pFile = fopen("test.txt", "rb");
    if (pFile == NULL) 
        perror("Error opening file");
    else
    {
        fseek(pFile, 0, SEEK_END); //non-portable
        size = ftell(pFile);
        fclose(pFile);
        printf("文件长度为: %ld bytes.\n", size);
    }
    return 0;
}

rewind函数

  • 头文件:#include<stdio.h>
  • 声明:void rewind(FILE *stream)
    • stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了流
  • 作用:让文件指针的位置回到文件的起始位置
  • 返回值:该函数不返回任何值。

rewind常常在文件读与写同时使用时,以方便文件读取。下面是rewind的具体使用实例:

#include <stdio.h>
int main()
{
    int n;
    FILE* pFile;
    char buffer[27];
    pFile = fopen("myfile.txt", "w+");
    for (n = 'A'; n <= 'Z'; n++)
        fputc(n, pFile);//放入26个字母
    rewind(pFile);//回到起始位置,方便读取
    fread(buffer, 1, 26, pFile);//读取·
    fclose(pFile);
    buffer[26] = '
#include <stdio.h>
int main()
{
int n;
FILE* pFile;
char buffer[27];
pFile = fopen("myfile.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, pFile);//放入26个字母
rewind(pFile);//回到起始位置,方便读取
fread(buffer, 1, 26, pFile);//读取·
fclose(pFile);
buffer[26] = '\0';//字符串的结束标识
printf(buffer);
return 0;
}
';//字符串的结束标识 printf(buffer); return 0; }

fgetpos(),fsetpos()

fseek()和ftell()有一个潜在的问题,那就是它们都把文件大小限制在 long int 类型能表示的范围内。这看起来相当大,但是在32位计算机上,long int 的长度为4个字节,能够表示的范围最大为 4GB。随着存储设备的容量迅猛增长,文件也越来越大,往往会超出这个范围。鉴于此,C 语言新增了两个处理大文件的新定位函数:fgetpos()和fsetpos()。

它们的原型都定义在头文件stdio.h。

int fgetpos(FILE* stream, fpos_t* pos);

int fsetpos(FILE* stream, const fpos_t* pos);

  • fgetpos()函数会将文件内部指示器的当前位置,存储在指针变量pos。该函数接受两个参数,第一个是文件指针,第二个存储指示器位置的变量。
  • fsetpos()函数会将文件内部指示器的位置,移动到指针变量pos指定的地址。注意,变量pos必须是通过调用fgetpos()方法获得的。fsetpos()的两个参数与fgetpos()必须是一样的。

记录文件内部指示器位置的指针变量pos,类型为fpos_t*(file position type 的缩写,意为文件定位类型)。它不一定是整数,也可能是一个 Struct 结构。

下面是用法示例。

fpos_t file_pos;
fgetpos(fp, &file_pos);

// 一系列文件操作之后
fsetpos(fp, &file_pos);

上面示例中,先用fgetpos()获取内部指针的位置,后面再用fsetpos()恢复指针的位置。

执行成功时,fgetpos()和fsetpos()都会返回0,否则返回非零值。

文件读取结束的判定

被错误使用的 feof

feof是C语言中的一个函数,用于检测文件流的状态是否到达了文件末尾。它的原型如下:

int feof(FILE *stream);

这个函数接收一个文件指针stream作为参数,如果文件指针指向的文件流已经到达文件末尾,则返回非零值,否则返回零。

值得注意的是,feof只有在尝试从文件读取数据并且读到了文件末尾,才会返回非零值。这意味着,如果你刚刚打开一个文件,或者刚刚将文件指针定位到文件的末尾,但还没有尝试读取数据,此时调用feof,它还是会返回零。

下面是一个使用feof的例子:

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    char ch;

    while (!feof(fp)) {
        ch = fgetc(fp);
        putchar(ch);
    }

    fclose(fp);
    return 0;
}

这个程序打开一个文件,然后用一个循环读取并输出文件中的每一个字符,直到到达文件末尾。这里使用feof来检测是否到达文件末尾。

常见的结束标志

函数 结束标志
fgetc 如果读取正常,返回读取到的字符的ASCLL码值 如果读取失败,返回EOF
fgets 如果读取正常,返回读取到的数据的地址 如果读取失败,返回NULL
fscanf 如果读取正常,返回的是格式串中指定的数据个数 如果读取失败,返回的是小于格式串中指定的数据个数
fread 如果读取正常,返回的是等于要读取的数据个数 如果读取失败,返回的是小于要读取的数据个数

在C语言中,文件结束标志通常是一个特殊的字符,用于表示文件的结束位置。这个字符叫做EOF(End of File),在C语言的标准库中定义为一个宏,通常的值为-1。

当你从一个已经到达文件末尾的文件中读取数据时,例如使用fgetc或者fscanf,它们会返回EOF。你可以使用这个值来检测文件是否已经结束。

例如,下面的代码会读取并输出一个文件中的所有字符,直到到达文件末尾:

#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "r");
    int ch;

    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    fclose(fp);
    return 0;
}

在这个例子中,当fgetc读取到文件末尾时,它会返回EOF,然后循环就会结束。

需要注意的是,EOF只是一个标志,表示已经到达文件末尾,它并不是实际存储在文件中的一个字符。实际的文件结束标志取决于操作系统,例如在Unix和Linux系统中,文件末尾没有特殊的标志,而在Windows中,文件末尾通常用Ctrl+Z来表示。

EOF与EOL

EOF和EOL是两种常见的文件控制字符,它们在文件和字符串处理中有着重要的作用。

EOF(End Of File)

EOF是一个特殊的字符,表示文件的结束。在C语言中,EOF通常被定义为-1。当我们使用如fgetc、fscanf等函数读取文件时,如果读到文件末尾,这些函数会返回EOF。我们可以使用这个特性来判断文件是否读取完毕。例如:

FILE *fp = fopen("file.txt", "r");
int ch;
while ((ch = fgetc(fp)) != EOF) {
    putchar(ch);
}
fclose(fp);

上面的代码会读取一个文件,并把文件的内容打印到屏幕上,直到读到文件末尾。

EOL(End Of Line)

EOL是一个特殊的字符或字符序列,表示行的结束。在不同的系统中,EOL可能有不同的表示。在Unix和Linux系统中,EOL通常是’\n’;在Windows系统中,EOL通常是”\r\n”;在早期的Mac OS系统中,EOL通常是’\r’。当我们使用如fgets、gets等函数读取一行文本时,这个函数会在读到EOL时停止读取,并返回已读取的字符串。例如:

char line[256];
while (fgets(line, sizeof(line), stdin) != NULL) {
    printf("%s", line);
}

上面的代码会从标准输入读取多行文本,并把这些文本打印到屏幕上,直到读取失败(例如,读到文件末尾或发生错误)。

文件的其他操作

文件删除

remove()函数用于删除指定文件。它的原型定义在头文件stdio.h。

int remove(const char* filename);

它接受文件名作为参数。如果删除成功,remove()返回0,否则返回非零值。

remove("foo.txt");

上面示例删除了foo.txt文件。

注意,删除文件必须是在文件关闭的状态下。如果是用fopen()打开的文件,必须先用fclose()关闭后再删除。

文件的移动

rename()函数用于文件改名,也用于移动文件。它的原型定义在头文件stdio.h。

int rename(const char* old_filename, const char* new_filename);

它接受两个参数,第一个参数是现在的文件名,第二个参数是新的文件名。如果改名成功,rename()返回0,否则返回非零值。

rename("foo.txt", "bar.txt");

上面示例将foo.txt改名为bar.txt。

注意,改名后的文件不能与现有文件同名。另外,如果要改名的文件已经打开了,必须先关闭,然后再改名,对打开的文件进行改名会失败。

下面是移动文件的例子。

rename("/tmp/evidence.txt", "/home/beej/nothing.txt");

文件的复制

在C语言中,可以使用文件操作函数来实现从一个文件复制到另一个文件的功能。

示例:

#include <stdio.h>
#define BUFFER_SIZE 4096

int main() {
    FILE* sourceFile;
    FILE* destinationFile;
    char buffer[BUFFER_SIZE];
    size_t bytesRead;

    // 打开源文件
    sourceFile = fopen("source.txt", "rb");
    if (sourceFile == NULL) {
        printf("无法打开源文件\n");
        return 1;
    }

    // 创建目标文件
    destinationFile = fopen("destination.txt", "wb");
    if (destinationFile == NULL) {
        printf("无法创建目标文件\n");
        fclose(sourceFile);
        return 1;
    }

    // 逐块读取源文件并写入目标文件
    while ((bytesRead = fread(buffer, 1, BUFFER_SIZE, sourceFile)) > 0) {
        fwrite(buffer, 1, bytesRead, destinationFile);
    }

    // 关闭文件
    fclose(sourceFile);
    fclose(destinationFile);

    printf("文件复制完成\n");

    return 0;
}

在上述示例中,首先打开源文件(source.txt)进行读取,然后创建目标文件(destination.txt)进行写入。通过循环读取源文件的内容,并将读取到的内容写入目标文件。

文件错误处理和异常

文件操作函数的返回值和错误码

C语言文件操作函数(如fopen、fclose、fread、fwrite等)的返回值和错误码可以通过检查errno变量来获取。

返回值:

  • fopen函数返回一个文件指针,它指向已打开的文件。如果文件打开失败,返回值为NULL。
  • fclose函数返回一个整数值,表示文件关闭的结果。如果文件关闭成功,返回值为0;如果文件关闭失败,返回值为EOF。
  • fread和fwrite函数返回一个size_t类型的值,表示实际读取或写入的数据项个数。如果读取或写入的数据项个数与指定的个数不一致,可能表示发生了错误。

错误码:

  • C标准库定义了一组宏来表示不同的错误码。这些宏定义在h头文件中。
  • 当文件操作函数发生错误时,应用程序可以通过errno变量来获取相应的错误码。errno是一个全局变量,类型为int。
  • 如果文件操作函数返回NULL或EOF,并且同时设置了errno变量,那么errno的值将指示出具体的错误类型。

常见的errno错误码包括:

  • EACCES:权限不足
  • ENOENT:文件不存在
  • EEXIST:文件已存在
  • ENOMEM:内存不足
  • EBADF:无效的文件描述符
  • EIO:IO错误
  • EINVAL:无效的参数
  • EPIPE:管道破裂

要获取具体的错误信息,可以使用strerror函数,它接受一个错误码作为参数,并返回一个字符串表示错误信息。例如:

#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {
    FILE* fp = fopen("file.txt", "r");
    if (fp == NULL) {
        printf("文件打开失败:%s\n", strerror(errno));
        return 1;
    }

    // 文件操作

    if (fclose(fp) != 0) {
        printf("文件关闭失败:%s\n", strerror(errno));
        return 1;
    }

    return 0;
}

在上述示例中,当文件打开或关闭失败时,使用strerror(errno)来获取具体的错误信息,并打印出来。

文件操作的异常处理(使用perror函数和错误码)

C语言文件操作的异常处理可以使用perror函数和错误码来实现。

perror函数用于将错误信息打印到标准错误流(stderr)中。它接受一个字符串作为参数,用于描述错误的来源,然后会根据当前的errno值来打印相应的错误信息。

示例:演示了如何使用perror函数和错误码进行C语言文件操作的异常处理:

#include <stdio.h>
#include <errno.h>

int main() {
    FILE* fp = fopen("file.txt", "r");
    if (fp == NULL) {
        perror("文件打开失败");
        return 1;
    }

    // 文件操作

    if (fclose(fp) != 0) {
        perror("文件关闭失败");
        return 1;
    }

    return 0;
}

在上述示例中,当文件打开或关闭失败时,使用perror函数将错误信息打印到标准错误流中,并携带自定义的错误来源描述。perror函数会根据当前的errno值来获取相应的错误信息,并将其与自定义的错误来源描述一起打印出来。

ferror(),clearerr()

所有的文件操作函数如果执行失败,都会在文件指针里面记录错误状态。后面的操作只要读取错误指示器,就知道前面的操作出错了。

ferror()函数用来返回错误指示器的状态。可以通过这个函数,判断前面的文件操作是否成功。它的原型定义在头文件stdio.h。

int ferror(FILE *stream);

它接受一个文件指针作为参数。如果前面的操作出现错误,ferror()就会返回一个非零整数(表示 true),否则返回0。

clearerr()函数用来重置出错指示器。它的原型定义在头文件stdio.h。

void clearerr(FILE* fp);

它接受一个文件指针作为参数,没有返回值。

下面是一个例子。

FILE* fp = fopen("file.txt", "w");
char c = fgetc(fp);

if (ferror(fp)) {
  printf("读取文件:file.txt 时发生错误\n");
}

clearerr(fp);

上面示例中,fgetc()尝试读取一个以”写模式“打开的文件,读取失败就会返回 EOF。这时调用ferror()就可以知道上一步操作出错了。处理完以后,再用clearerr()清除出错状态。

文件操作函数如果正常执行,ferror()和feof()都会返回零。如果执行不正常,就要判断到底是哪里出了问题。

if (fscanf(fp, "%d", &n) != 1) {
  if (ferror(fp)) {
    printf("io error\n");
  }
  if (feof(fp)) {
    printf("end of file\n");
  }

  clearerr(fp);

  fclose(fp);
}

上面示例中,当fscanf()函数报错时,通过检查ferror()和feof(),确定到底发生什么问题。这两个指示器改变状态后,会保持不变,所以要用clearerr()清除它们,clearerr()可以同时清除两个指示器。

文件缓冲区

在编程和计算机科学中,缓冲区通常指的是在内存中预留的一块空间,用于暂时存储从一个地方(如磁盘)传输到另一个地方(如CPU)的数据。文件缓冲区就是用来暂存和文件相关的输入输出操作的数据。

文件缓冲区的存在可以提高程序的性能。当你执行一个输入或输出操作时,例如读取或写入文件,操作系统实际上可能并不会立即执行这个操作。相反,它会把数据存储在文件缓冲区中,然后在合适的时机,例如缓冲区已满或者你显式地要求把数据写入磁盘时,才会把数据从缓冲区中写入到磁盘,或者把磁盘上的数据读取到缓冲区。这是因为访问磁盘比访问内存要慢得多,通过缓冲区可以减少需要访问磁盘的次数,从而提高性能。

例如在C语言中,当你使用fopen打开一个文件时,C语言的标准库会自动创建一个文件缓冲区。然后你每次调用fread、fwrite、fgetc、fputc等函数时,它们实际上是在操作这个缓冲区。只有当缓冲区满,或者你调用fflush,或者关闭文件时,数据才会被真正写入到磁盘。

需要注意的是,文件缓冲区是透明的,也就是说,你通常不需要关心它的存在。你只需要像平常一样读写文件,缓冲区的管理会自动由操作系统或运行时库来处理。

ANSI C标准采用缓冲文件系统处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。

  • 从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
  • 如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)
  • 缓冲区的大小根据C编译系统决定的。

我们可以利用下列代码证明缓冲区的存在:

include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
int main()
{
    FILE* pf = fopen("test.txt", "w");
    fputs("abcdef", pf); //先将代码放在输出缓冲区
    printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");
    Sleep(10000);
    printf("刷新缓冲区\n");
    fflush(pf); //刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)
    //注:fflush 在⾼版本的VS上不能使⽤了
    printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
    Sleep(10000);
    fclose(pf);
    //注:fclose在关闭⽂件的时候,也会刷新缓冲区
    pf = NULL;
    return 0;
}

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。

标准I/0提供的三种类型的缓存

C语言的标准I/O库提供了三种类型的缓冲(buffering)方法:

  • 全缓冲(Fully Buffered):在这种模式下,数据会被存放在缓冲区中,当缓冲区满时,缓冲区中的数据才会被一次性写入磁盘或发送出去。这种缓冲方式通常用于普通的磁盘文件操作,因为磁盘操作的开销比较大,一次性写入大量数据可以提高效率。
  • 行缓冲(Line Buffered):在这种模式下,每次遇到换行符时,缓冲区中的数据会被写入磁盘或发送出去。这种缓冲方式通常用于终端输入/输出,可以让用户每输入一行就处理一行,提高交互性。
  • 无缓冲(Unbuffered):在这种模式下,每次输入或输出操作都会直接调用底层的系统函数,不经过缓冲区。这种缓冲方式通常用于错误输出,当程序出错时,我们希望错误信息能立即显示出来,而不是被存放在缓冲区中。

你可以使用setbuf或setvbuf函数来设置文件流的缓冲模式。需要注意的是,不是所有的系统都支持所有的缓冲模式,具体情况需要查看具体的系统和编译器文档。

文件操作的实例

读取和解析配置文件

在C语言中,可以使用文件操作函数来读取和解析配置文件。以下是一个示例:

假设我们有一个名为config.txt的配置文件,其内容如下:

# 这是一个配置文件示例
username=John
password=123456
port=8080

现在,我们可以使用以下代码来读取和解析该配置文件:

#include <stdio.h>
#include <string.h>

#define MAX_LINE_LENGTH 100
#define MAX_KEY_LENGTH 50
#define MAX_VALUE_LENGTH 50

int main() {
    FILE* configFile;
    char line[MAX_LINE_LENGTH];
    char key[MAX_KEY_LENGTH];
    char value[MAX_VALUE_LENGTH];
    char* delimiter;
    int lineNumber = 0;

    // 打开配置文件
    configFile = fopen("config.txt", "r");
    if (configFile == NULL) {
        printf("无法打开配置文件\n");
        return 1;
    }

    // 逐行读取配置文件内容
    while (fgets(line, MAX_LINE_LENGTH, configFile) != NULL) {
        lineNumber++;

        // 忽略注释行和空行
        if (line[0] == '#' || line[0] == '\n') {
            continue;
        }

        // 查找键值分隔符
        delimiter = strchr(line, '=');
        if (delimiter == NULL) {
            printf("配置文件格式错误,第%d行\n", lineNumber);
            fclose(configFile);
            return 1;
        }

        // 解析键和值
        strncpy(key, line, delimiter - line);
        key[delimiter - line] = '
#include <stdio.h>
#include <string.h>
#define MAX_LINE_LENGTH 100
#define MAX_KEY_LENGTH 50
#define MAX_VALUE_LENGTH 50
int main() {
FILE* configFile;
char line[MAX_LINE_LENGTH];
char key[MAX_KEY_LENGTH];
char value[MAX_VALUE_LENGTH];
char* delimiter;
int lineNumber = 0;
// 打开配置文件
configFile = fopen("config.txt", "r");
if (configFile == NULL) {
printf("无法打开配置文件\n");
return 1;
}
// 逐行读取配置文件内容
while (fgets(line, MAX_LINE_LENGTH, configFile) != NULL) {
lineNumber++;
// 忽略注释行和空行
if (line[0] == '#' || line[0] == '\n') {
continue;
}
// 查找键值分隔符
delimiter = strchr(line, '=');
if (delimiter == NULL) {
printf("配置文件格式错误,第%d行\n", lineNumber);
fclose(configFile);
return 1;
}
// 解析键和值
strncpy(key, line, delimiter - line);
key[delimiter - line] = '\0';
strncpy(value, delimiter + 1, strlen(line) - (delimiter - line) - 1);
value[strlen(line) - (delimiter - line) - 1] = '\0';
// 打印键值对
printf("键:%s,值:%s\n", key, value);
}
// 关闭文件
fclose(configFile);
return 0;
}
'; strncpy(value, delimiter + 1, strlen(line) - (delimiter - line) - 1); value[strlen(line) - (delimiter - line) - 1] = '
#include <stdio.h>
#include <string.h>
#define MAX_LINE_LENGTH 100
#define MAX_KEY_LENGTH 50
#define MAX_VALUE_LENGTH 50
int main() {
FILE* configFile;
char line[MAX_LINE_LENGTH];
char key[MAX_KEY_LENGTH];
char value[MAX_VALUE_LENGTH];
char* delimiter;
int lineNumber = 0;
// 打开配置文件
configFile = fopen("config.txt", "r");
if (configFile == NULL) {
printf("无法打开配置文件\n");
return 1;
}
// 逐行读取配置文件内容
while (fgets(line, MAX_LINE_LENGTH, configFile) != NULL) {
lineNumber++;
// 忽略注释行和空行
if (line[0] == '#' || line[0] == '\n') {
continue;
}
// 查找键值分隔符
delimiter = strchr(line, '=');
if (delimiter == NULL) {
printf("配置文件格式错误,第%d行\n", lineNumber);
fclose(configFile);
return 1;
}
// 解析键和值
strncpy(key, line, delimiter - line);
key[delimiter - line] = '\0';
strncpy(value, delimiter + 1, strlen(line) - (delimiter - line) - 1);
value[strlen(line) - (delimiter - line) - 1] = '\0';
// 打印键值对
printf("键:%s,值:%s\n", key, value);
}
// 关闭文件
fclose(configFile);
return 0;
}
'; // 打印键值对 printf("键:%s,值:%s\n", key, value); } // 关闭文件 fclose(configFile); return 0; }

打开配置文件(config.txt)进行读取。然后,使用fgets函数逐行读取配置文件的内容,并使用strchr函数查找键值分隔符(=)。接下来,使用strncpy函数解析出键和值,并打印出来。如果配置文件中存在注释行或空行,则会被忽略。如果配置文件的格式不正确(没有键值分隔符),则会打印出错信息并返回。最后,关闭文件。

读取和写入二进制文件的图像数据

首先定义了图像的宽度和高度(640×480)。readImage函数用于从二进制文件中读取图像数据,它打开文件进行读取,然后按照图像大小分配内存,最后使用fread函数将图像数据读取到内存中。你可以在注释的TODO部分对图像数据进行处理或使用。writeImage函数用于将图像数据写入二进制文件,它打开文件进行写入。

#include <stdio.h>
#include <stdlib.h>

#define IMAGE_WIDTH  640
#define IMAGE_HEIGHT 480

// 从二进制文件中读取图像数据
void readImage(const char* filename) {
    FILE* file;
    unsigned char* imageData;
    int imageSize = IMAGE_WIDTH * IMAGE_HEIGHT;

    // 打开文件
    file = fopen(filename, "rb");
    if (file == NULL) {
        printf("无法打开文件:%s\n", filename);
        return;
    }

    // 分配内存用于存储图像数据
    imageData = (unsigned char*)malloc(imageSize);
    if (imageData == NULL) {
        printf("内存分配失败\n");
        fclose(file);
        return;
    }

    // 读取图像数据
    if (fread(imageData, 1, imageSize, file) != imageSize) {
        printf("读取图像数据失败\n");
        free(imageData);
        fclose(file);
        return;
    }

    // 关闭文件
    fclose(file);

    // TODO: 在这里可以对图像数据进行处理或使用

    // 释放内存
    free(imageData);

    printf("图像数据读取成功\n");
}

// 将图像数据写入二进制文件
void writeImage(const unsigned char* imageData) {
    FILE* file;

    // 打开文件进行写入
    file = fopen("image.bin", "wb");
    if (file == NULL) {
        printf("无法打开文件进行写入\n");
        return;
    }

    // 写入图像数据
    if (fwrite(imageData, 1, IMAGE_WIDTH * IMAGE_HEIGHT, file) != IMAGE_WIDTH * IMAGE_HEIGHT) {
        printf("写入图像数据失败\n");
        fclose(file);
        return;
    }

    // 关闭文件
    fclose(file);

    printf("图像数据写入成功\n");
}

int main() {
    // 读取图像数据
    readImage();

    // TODO: 在这里可以对图像数据进行处理或使用

    // 写入图像数据
    writeImage();

    return 0;
}

将程序输出保存到文件中

在C语言中,可以使用文件操作函数将程序的输出保存到文件中。

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE* file;
    char* output = "这是要保存到文件中的内容";

    // 打开文件进行写入
    file = fopen("output.txt", "w");
    if (file == NULL) {
        printf("无法打开文件进行写入\n");
        return 1;
    }

    // 将程序输出写入文件
    if (fputs(output, file) == EOF) {
        printf("写入文件失败\n");
        fclose(file);
        return 1;
    }

    // 关闭文件
    fclose(file);

    printf("程序输出已保存到文件中\n");

    return 0;
}

首先定义了要保存到文件中的内容(output)。然后,使用fopen函数打开文件进行写入。如果文件打开成功,则使用fputs函数将程序的输出写入到文件中。最后,使用fclose函数关闭文件。如果文件打开或写入失败,则会打印相应的错误信息。

文件操作的注意事项和最佳实践

文件的打开和关闭原则

在C语言中,文件的打开和关闭是一项重要的操作。需要注意一些文件的打开和关闭原则:

  • 使用fopen函数打开文件进行读取或写入。在打开文件时,需要遵循以下原则:
  • 打开文件前,应该先检查文件是否成功打开。可以通过检查fopen函数的返回值是否为NULL来判断文件是否成功打开。
  • 在打开文件后,应该及时关闭文件,以释放相关的资源。使用fclose函数来关闭文件。
  • 在程序中可能出现各种异常情况,例如文件打开失败、读写错误等。应该在异常情况下及时关闭文件并处理错误。关闭文件可以使用fclose函数。处理错误可以根据具体情况进行,例如打印出错信息、释放相关资源等。
  • 文件操作函数(如fread、fwrite、fgets、fputs等)在执行操作后,会返回一个表示操作是否成功的值。应该检查这个返回值来判断操作是否成功。例如,如果fopen函数返回NULL,表示文件打开失败;如果fputs函数返回EOF,表示写入文件失败。

文件的打开和关闭原则是:打开文件前检查是否成功打开,及时关闭文件,处理异常情况并关闭文件,检查文件操作函数的返回值来判断操作是否成功。这样可以保证文件的正确操作和资源的正确释放。

文件操作的错误处理和异常处理

检查文件的打开是否成功

在使用fopen函数打开文件时,应该检查返回值是否为NULL,以确定文件是否成功打开。如果文件打开失败,可以使用perror函数打印错误信息,或者使用fprintf函数将错误信息写入到标准错误流stderr中。

FILE* file = fopen("file.txt", "r");
if (file == NULL) {
    perror("无法打开文件");
    return -1;
}

处理文件读写错误

在使用文件读写函数(如fread、fwrite、fgets、fputs等)进行操作时,应该检查函数的返回值。如果返回值表示操作失败,可以采取适当的处理措施,例如打印错误信息、关闭文件、释放相关资源等。

if (fread(buffer, sizeof(char), 100, file) != 100) {
    fprintf(stderr, "读取文件失败\n");
    fclose(file);
    return -1;
}

文件关闭

在打开文件后,应该及时关闭文件,以释放相关的资源。使用fclose函数来关闭文件。关闭文件时,应该检查函数的返回值,以确保文件成功关闭。

if (fclose(file) != 0) {
    fprintf(stderr, "关闭文件失败\n");
    return -1;
}

异常处理

在文件操作过程中,可能会出现各种异常情况,例如内存分配失败、操作过程中出现错误等。对于这些异常情况,应该进行适当的处理。可以使用if语句或try-catch结构来处理异常情况,具体处理方法根据实际需求而定。

char* buffer = malloc(100);
if (buffer == NULL) {
    fprintf(stderr, "内存分配失败\n");
    return -1;
}

小结:通过检查文件打开和读写函数的返回值,及时关闭文件,处理文件操作中可能出现的异常情况,可以提高程序的健壮性。

文件的读取和写入策略

在C语言中,有不同的策略可用于文件的读取和写入,具体取决于需求和文件操作的特点。

  • 字符读写:使用getc和putc等函数按字符逐个读取和写入文件。这种方式适用于需要逐个字符处理文件内容的情况。
  • 行读写:使用fgets和fputs等函数按行读取和写入文件。这种方式适用于需要按行处理文件内容的情况。
  • 格式化读写:使用fscanf和fprintf等函数按格式读取和写入文件。这种方式适用于需要按指定格式解析和生成文件内容的情况。
  • 二进制读写:使用fread和fwrite等函数以二进制方式读取和写入文件。这种方式适用于需要直接读取和写入二进制数据的情况,如图片、视频等文件。
  • 随机访问:使用fseek和ftell等函数进行文件的随机访问。这种方式适用于需要在文件中跳跃访问特定位置的情况。

另外,还可以根据文件的大小和内容特点,选择逐个字符读取和写入、一次性读取和写入整个文件、分块读取和写入等不同的读写策略。对于大文件或者需要处理大量数据的情况,可以考虑使用分块读写的方式,以减少内存消耗。

文件的安全性和权限

在C语言中,文件的安全性和权限是操作系统层面的概念,涉及到文件系统的权限管理和用户访问控制。C语言本身并没有提供直接控制文件权限的函数,而是通过操作系统提供的文件系统接口进行文件权限的设置和管理。

  • 文件安全性:文件安全性是指文件在操作系统中的保护级别,包括对文件的读、写、执行等权限的控制。文件系统通常会为每个文件设置访问权限,限制用户对文件的操作。在Linux和UNIX系统中,可以使用chmod命令设置文件的权限,而在Windows系统中,可以通过文件属性对话框设置文件的访问权限。
  • 文件权限:文件权限由一系列标志位表示,用于指定特定用户或用户组对文件的读、写和执行权限。在Linux和UNIX系统中,权限由三组标志位表示,分别是所有者权限、用户组权限和其他用户权限。可以使用chmod命令来设置这些权限。在Windows系统中,文件权限由一组权限标志位表示,可以通过文件属性对话框来设置。
  • 文件所有者和用户组:文件系统中的每个文件都有一个所有者和一个用户组。所有者是创建文件的用户,用户组是文件所属的用户组。文件的所有者和用户组可以影响文件的权限控制。在Linux和UNIX系统中,可以使用chown和chgrp命令来修改文件的所有者和用户组。在Windows系统中,可以通过文件属性对话框来修改文件的所有者和用户组。

在C语言中,可以通过操作系统提供的文件系统接口来获取和修改文件的权限和所有者信息。例如,可以使用stat函数来获取文件的权限和所有者信息。在进行文件操作时,需要注意当前用户的权限,以及对文件的访问权限进行适当的判断和控制,以确保文件的安全性。

C语言本身并不直接提供文件的权限设置和管理功能,而是依赖于操作系统提供的文件系统接口来实现。程序员需要了解操作系统的文件权限管理机制,并合理设置和控制文件的权限,以保证文件的安全性和正确性。

文件操作总结

在C语言中,文件操作是一项非常重要的任务。使用标准库提供的文件操作函数,我们可以打开、读写、关闭文件,并进行一系列的文件操作。以下是文件操作的一般步骤:

  • 使用fopen函数打开文件,获取文件指针。
  • 检查文件指针是否为空,以确保文件成功打开。
  • 使用不同的读写函数读取或写入文件内容。
  • 对文件进行适当的错误处理,检查读写函数的返回值以确保操作成功。
  • 使用fclose函数关闭文件,释放资源。
  • 对关闭文件的返回值进行检查,确保文件成功关闭。

除了基本的文件操作,还可以使用其他函数来获取文件的属性,如文件大小、创建时间等。

扩展阅读:

  • 文件指针的使用和操作:了解如何使用文件指针在文件中进行定位、跳转等操作,如fseek、ftell等函数。
  • 文件操作的标准错误处理:学习如何使用perror、fprintf等函数打印错误信息,以及如何处理文件操作中可能发生的各种错误和异常情况。
  • 文件打开模式:了解不同的文件打开模式,如只读、只写、追加等模式,以及如何选择适当的模式来打开文件。
  • 文件的二进制操作:学习如何以二进制方式读写文件,特别是处理二进制文件的技巧和注意事项。
  • 文件的批量操作:了解如何处理多个文件,如批量读取文件夹中的文件、批量写入文件等。
  • 文件锁和并发访问:了解如何使用文件锁来防止多个进程或线程同时访问同一个文件,以确保文件的安全性和一致性。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注