前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >从零开始学C语言文件操作:理论与代码详解

从零开始学C语言文件操作:理论与代码详解

作者头像
用户11029137
发布2025-03-08 09:18:25
发布2025-03-08 09:18:25
3000
代码可运行
举报
文章被收录于专栏:编程学习编程学习
运行总次数:0
代码可运行

一、为什么要使用文件

在C语言编程中,我们编写的程序数据通常存储在电脑内存里。可一旦程序退出,内存就会回收这些数据,再次运行程序时,之前的数据就找不到了。为了能长久保存数据,我们就需要用到文件。文件可以把数据存储在磁盘上,即使程序关闭,数据依然存在,下次运行程序时还能读取使用。

二、什么是文件

在程序设计里,文件分为程序文件数据文件。程序文件包括源程序文件(后缀为.c)、目标文件(Windows环境后缀为.obj)和可执行程序(Windows环境后缀为.exe) 。而数据文件,简单来说,就是程序运行时读写的数据所在的文件,它的内容不一定是程序代码。比如,一个记录用户信息的文件,或者程序运行过程中生成的日志文件等。

每个文件都要有一个独一无二的标识,方便我们识别和引用,这就是文件名。文件名由文件路径 + 文件名主干 + 文件后缀组成,像 “c:\code\test.txt” 就是一个完整的文件名。

三、二进制文件和文本文件

根据数据的组织形式,数据文件分为文本文件和二进制文件。数据在内存中是以二进制形式存储的,如果直接把数据不加转换地输出到外存文件中,这个文件就是二进制文件。而如果要求数据在外存上以ASCII码的形式存储,那在存储前就需要进行转换,以这种形式存储的文件就是文本文件

四、文件的打开和关闭

(一)流和标准流

在C程序里,数据的输入输出操作涉及到各种外部设备,为了方便程序员操作,就引入了“流”的概念。程序的数据通过流来与外部设备进行交互。比如,从键盘输入数据,向屏幕输出数据,以及对文件的读写操作,都是通过流来实现的。

C语言程序启动时,会默认打开3个流:

  • stdin - 标准输入流,大多数情况下从键盘输入数据,scanf 函数就是从标准输入流中读取数据的。
  • stdout - 标准输出流,多数环境下输出到显示器界面,printf 函数就是将信息输出到标准输出流。
  • stderr - 标准错误流,通常也输出到显示器界面 。

这三个流的类型都是 FILE*,也就是文件指针,C语言通过文件指针来管理流的各种操作。

(二)文件指针

在缓冲文件系统中,文件指针非常重要。每个被使用的文件在内存里都会开辟一个文件信息区,用来存放文件的相关信息,像文件名、文件状态以及文件当前的位置等。这些信息保存在一个结构体变量中,这个结构体由系统声明,叫做 FILE。不同的C编译器中,FILE 类型包含的内容会有些差异,但大致相似。

当我们打开一个文件时,系统会自动创建一个 FILE 结构的变量,并填充好相关信息,我们不用关心具体细节,只需要通过一个 FILE 指针来操作这个文件就可以了。比如:

代码语言:javascript
代码运行次数:0
复制
FILE* pf; 

这里定义了一个文件指针变量 pf,它可以指向某个文件的文件信息区,通过这个指针就能访问对应的文件。

(三)文件的打开与关闭函数

在对文件进行读写操作之前,要先打开文件,使用完后要关闭文件。ANSI C规定用 fopen 函数打开文件,fclose 函数关闭文件。fopen 函数的原型是:

代码语言:javascript
代码运行次数:0
复制
FILE * fopen ( const char * filename, const char * mode );

其中,filename 是要打开的文件名,mode 是文件的打开模式。fclose 函数的原型是:

代码语言:javascript
代码运行次数:0
复制
int fclose ( FILE * stream );

stream 就是 fopen 函数返回的文件指针。

文件的打开模式有很多种,常见的如下:

模式大全表:

模式

说明

文件存在

文件不存在

r

只读(文本)

打开

失败

w

新建写入(清空内容)

清空

新建

a

追加写入

追加

新建

r+

读写(从开头)

打开

失败

w+

新建读写(清空内容)

清空

新建

a+

读写(追加写入)

打开

新建

rb

二进制只读

打开

失败

wb

二进制写入

清空

新建

下面是一个打开和关闭文件的示例代码:

代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
int main ()
{
    FILE * pFile;
    pFile = fopen ("myfile.txt","w");
    if (pFile!=NULL)
    {
        fputs ("test",pFile);
        fclose (pFile);
    }
    return 0;
}

在这段代码中,我们以 “只写” 模式打开了 myfile.txt 文件,向文件中写入了 “test”,最后关闭了文件。


五、文件的顺序读写

文件的顺序读写是指按照文件内容的先后顺序进行读写操作。C语言提供了一系列函数来实现文件的顺序读写(输入流即为所有输入流):

  • fgetc:字符输入函数,用于从输入流中读取一个字符。
  • fputc:字符输出函数,向输出流中写入一个字符。
  • fgets:文本行输入函数,从输入流中读取一行文本。
  • fputs:文本行输出函数,向输出流中写入一行文本。
  • fscanf:格式化输入函数,按照指定格式从输入流中读取数据。
  • fprintf:格式化输出函数,按照指定格式向输出流中写入数据。
  • fread:二进制输入函数,用于从文件中读取二进制数据。
  • fwrite:二进制输出函数,向文件中写入二进制数据。

下面是一个使用 fputcfgetc 函数进行文件读写的示例:

代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
int main()
{
    FILE *fp1, *fp2;
    char ch;
    // 以只写模式打开文件1
    fp1 = fopen("file1.txt", "w");
    if (fp1 == NULL)
    {
        printf("无法打开文件1\n");
        return 1;
    }
    // 向文件1中写入字符
    fputc('A', fp1);
    fputc('B', fp1);
    fputc('C', fp1);
    fclose(fp1);

    // 以只读模式打开文件1
    fp2 = fopen("file1.txt", "r");
    if (fp2 == NULL)
    {
        printf("无法打开文件1\n");
        return 1;
    }
    // 从文件1中读取字符并输出
    ch = fgetc(fp2);
    while (ch != EOF)
    {
        printf("%c ", ch);
        ch = fgetc(fp2);
    }
    fclose(fp2);
    return 0;
}

在这个示例中,我们先以只写模式打开 file1.txt 文件,向里面写入了 ABC 三个字符,然后关闭文件。接着又以只读模式打开这个文件,使用 fgetc 函数逐个读取字符并输出到屏幕上。


六、文件的随机读写

有时候,我们需要在文件中随机地读写数据,而不是按顺序读写。C语言提供了几个函数来实现文件的随机读写:

  • fseek根据文件指针的位置和偏移量来定位文件指针。函数原型为 int fseek ( FILE * stream, long int offset, int origin );stream 是文件指针,offset 是偏移量,origin 是起始位置,取值可以是 SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件末尾)。
  • ftell:返回文件指针相对于起始位置的偏移量,函数原型为 long int ftell ( FILE * stream );
  • rewind:让文件指针的位置回到文件的起始位置,函数原型为 void rewind ( FILE * stream );

下面是一个使用 fseek 函数的示例:

代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
int main ()
{
    FILE * pFile;
    pFile = fopen ( "example.txt" , "wb" );
    fputs ( "This is an apple." , pFile );
    fseek ( pFile , 9 , SEEK_SET );
    fputs ( " sam" , pFile );
    fclose ( pFile );
    return 0;
}

在这段代码中,我们先向 example.txt 文件中写入了 “This is an apple.”,然后使用 fseek 函数将文件指针移动到第9个字符的位置origin + offset),接着再写入 “ sam”。这样,文件的内容就变成了 “This is a sampple.” 。


七、文件读取结束的判定

(一)feof()函数
1. 文本文件的读取结束判定

feof() 是C标准库中专门用于检测文件结束的函数。它的原型如下:

代码语言:javascript
代码运行次数:0
复制
int feof(FILE *stream);
  • 返回值
    • 非零值(true):已到达文件末尾
    • 0(false):未到达文件末尾

注意feof() 只有在尝试读取超出文件末尾的数据后才会返回 true。(也就是说,遇到错误停止时,如果没到末尾,也返回false,这就会导致实际上已经结束了,但是feof() 认为没有结束)因此,不能直接用 feof 函数的返回值来判断文件是否结束,通常需要结合其他函数使用。

(一)文本文件读取结束判定示例

对于文本文件,我们可以通过判断返回值是否为 EOFfgetc 函数)或者 NULLfgets 函数)来确定是否读取结束。例如:

1. 使用 fgetc() 和 feof()
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r");
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {  // 读取字符
        putchar(ch);  // 输出字符
    }

    if (feof(fp)) {
        printf("\n已到达文件末尾\n");
    } else {
        printf("\n读取过程中发生错误\n");
    }

    fclose(fp);
    return 0;
}
2. 使用 fgets() 和 feof()
代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "r");
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }

    char buffer[100];
    while (fgets(buffer, 100, fp) != NULL) {  // 读取一行
        printf("%s", buffer);
    }

    if (feof(fp)) {
        printf("\n已到达文件末尾\n");
    } else {
        printf("\n读取过程中发生错误\n");
    }

    fclose(fp);
    return 0;
}

(二) 二进制文件的读取结束判定
1. 使用 fread() 的返回值

fread() 函数返回实际读取的数据项数量。如果返回值小于请求的数量,则可能到达文件末尾或发生错误。

代码语言:javascript
代码运行次数:0
复制
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  • 返回值:实际读取的数据项数量,返回值为 < 0,到达末尾或错误

示例:

代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>

int main() {
    FILE *fp = fopen("data.bin", "rb");
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }

    int buffer[10];
    size_t itemsRead;
    // read()返回值 < 0 : 到达末尾 or 错误
    while ((itemsRead = fread(buffer, sizeof(int), 10, fp)) > 0) {
        for (size_t i = 0; i < itemsRead; i++) {
            printf("%d ", buffer[i]);
        }
    }

    if (feof(fp)) {
        printf("\n已到达文件末尾\n");
    } else {
        printf("\n读取过程中发生错误\n");
    }

    fclose(fp);
    return 0;
}

(三)错误处理
  • 除了检查文件结束,还应该检查是否发生了其他错误。可以使用 ferror() 函数:
代码语言:javascript
代码运行次数:0
复制
if (ferror(fp)) {
    printf("读取过程中发生错误\n");
}

八、文件缓冲区

ANSI C标准采用“缓冲文件系统”来处理数据文件。在这种系统下,系统会自动在内存中为每个正在使用的文件开辟一块“文件缓冲区”

从内存向磁盘输出数据时,数据会先被送到内存中的缓冲区,等缓冲区装满后,才会一起被送到磁盘上。从磁盘向计算机读入数据时,先从磁盘文件中读取数据到内存缓冲区,装满缓冲区后,再逐个将数据送到程序数据区。缓冲区的大小由C编译系统决定。

下面的代码展示了文件缓冲区的作用:

代码语言:javascript
代码运行次数:0
复制
#include <stdio.h>
#include <windows.h>
int main()
{
    FILE*pf = fopen("test.txt", "w");
    fputs("abcdef", pf);
    printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
    Sleep(10000);
    printf("刷新缓冲区\n");
    fflush(pf);
    printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
    Sleep(10000);
    fclose(pf);
    return 0;
}

在这段代码中,我们向文件写入 “abcdef” 后,文件中并不会立即出现这些内容,因为数据先存放在缓冲区中。当我们调用 fflush 函数刷新缓冲区或者调用 fclose 函数(自动刷新缓冲区)关闭文件时,缓冲区的数据才会真正写入到文件中。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、为什么要使用文件
  • 二、什么是文件
  • 三、二进制文件和文本文件
  • 四、文件的打开和关闭
    • (一)流和标准流
    • (二)文件指针
    • (三)文件的打开与关闭函数
    • 五、文件的顺序读写
    • 六、文件的随机读写
    • 七、文件读取结束的判定
      • (一)feof()函数
      • 1. 文本文件的读取结束判定
      • (一)文本文件读取结束判定示例
      • (二) 二进制文件的读取结束判定
      • (三)错误处理
    • 八、文件缓冲区
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档