C语言文件操作

本篇目标:

1.文件的作用与什么是文件

  1. 学会文件的打开,关闭 ,读写的操作

  2. ⽂件读取结束的判定

  3. 知道文件缓冲区的存在及作用

一.认识文件

1.文件的作用与概念

作用: 如果没有文件,我们写的程序数据是存储在电脑的内存中,如果程序退出,内存回收,数

据就丢失 了,等再次运行程序,是看不到上次程序的数据的,所以要想将数据进行持久化的保

,我们就可以使用⽂件进行操作。

什么是文件?

磁盘(硬盘)上的文件是文件,然而在程序设计中,我们⼀般谈的⽂件有两种:程序文件数据文

**件,**以下是关于文件的具体分类。

2.文件分类

程序文件包括源文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序

(windows 环境后缀为.exe)。

数据文件:程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或 者输出内容的文

件,本篇是以数据文件为主的。

根据数据的组织形式,数据⽂件被分为**⼆进制文件** 和文本文件。

二进制文件:数据在内存中以二进制的形式存储,如果不加转换的输出到外存的⽂件中。

文本文件:在外存上以ASCII码的形式存储,则需要在存储前转换,以ASCII字符的形式存储的⽂

件。

例如有一整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字

节),而⼆进制形式输出,则在磁盘上只占4个字节,可以通过如图进行理解:

二.常见文件操作

1.文件的打开与关闭

1.1.流的概念

概念:我们程序的数据需要输出到各种外部设备,例如我们使用printf向显示屏上输出内容,也需

从外部设备获取数据,但是不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种

设备进行方便的操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。

⼀般情况下,我们要想向流⾥里写数据,或者从流中读取数据,都是要先打开流,然后操作

1.2.标准流

这是可能就会有人有疑问:为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语⾔程序在启动的时候,默认打开了3个流:

stdin- 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。

stdout- 标准输出流,⼤多数的环境中输出⾄显示器界面,printf函数就是将信息输出到标准输出 流中。

stderr- 标准错误流,大多数环境中输出到显示器界面。

并且stdin 、 stdout 、 stderr 三个流的类型是: FILE * ,通常称为文件指针

FILE* 的⽂件指针来维护流的各种操作的。

1.3.文件指针

概念:每个被使⽤的文件都在内存中开辟了⼀个相应的文件信息区 ,⽤来存放文件的相关信息(如

⽂件的名字,文件状态及文件当前的位置等),这些信息是保存在⼀个结构体变量中的。该结构

体类型是由系统声明的,取名FILE,所以我们通过这个文件指针+流的方式就可以直接访问到这个

文件了。

例如下面我们可以简单的看一下里面的内容:

cpp 复制代码
#ifndef _FILE_DEFINED
struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
        };
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif

下⾯我们可以创建⼀个FILE*的指针变量:

cpp 复制代码
FILE* pf;    //⽂件指针变量

定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体

变 量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接

找到与它关联的⽂件,看图理解吧:

1.4.文件开关

1.4.1.fopen

作用:打开一个文件,将打开的文件和⼀个流进行关联, 并返回⼀个指向该文件的FILE*指针变量

语法:

cpp 复制代码
FILE * fopen ( const char * filename, const char * mode );

filename :表示被打开的文件的名字。

mode :表示对打开的文件的操作方式,具体见下面的表格。

|-------------|----------------------|----------|
| 操作方式 | 含义 | 若文件不存在 |
| "r"(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
| "w"(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建⽴⼀个新的文件 |
| "a"(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的文件 |

返回值:

文件成功打开,该函数将返回⼀个指向FILE对象的指针,该指针可用于后续操作中标识对应的流。

打开失败,则返回NULL指针,所以⼀定要对 fopen 的返回值做判断,来验证文件是否打开成 功。

1.4.2.fclose

作用:关闭参数 stream 关联的文件,并取消其关联关系,与该流关联的所有内部缓冲区均会解除关 联并刷新:任何未写⼊的输出缓冲区内容将被写⼊,任何未读取的输⼊缓冲区内容将被丢弃。

注:缓冲区后面后讲

语法:

复制代码
int fclose ( FILE * stream );

参数:

stream :指向要关闭的流的 FILE 对象的指针。

返回值:

成功关闭 stream 指向的流会返回0,否则会返回EOF。

1.5.代码演示

cpp 复制代码
#include <stdio.h>

int main()
{
	FILE* fp = fopen("test.txt", "w"); //以"r"的形式打开⽂件,如果⽂件不存在,则打开失败
	if (fp == NULL)
	{
		perror("fopen\n");
		return 1;
	}
	printf("打开文件成功,可以对文件进行操作\n");

    //对文件的读写操作
    //...

	fclose(fp);		//不再使用文件时,需要关闭文件
	fp = NULL;		//将指针置为NULL,避免成为野指针。

	return 0;
}

2.文件的顺序读写

2.1.fprintf

功能: 将格式化后的数据写入指定文件流,它与 printf 类似,但可以输出到任意输出流(如磁盘文件、标准输出、标准错误等),⽽不仅限于控制台。

语法:

cpp 复制代码
int fprintf ( FILE * stream, const char * format, ... );

参数:

• stream :FILE 对象的指针,表示要写入的文件流。

• format :格式化字符串,通常是写入的文本和格式说明符(如 %d 、 %s 等)。

• ... :可变参数列表,提供与格式字符串中说明符对应的数据。

返回值:

• 成功时,返回写入的字符总数。

• 失败时,先设置对应流的错误指示器,再返回负值。

代码操作:

cpp 复制代码
#include <stdio.h>

int main()
{
	FILE* fp = fopen("test.txt", "w"); //以"r"的形式打开⽂件,如果⽂件不存在,则打开失败
	if (fp == NULL)
	{
		perror("fopen\n");
		return 1;
	}
	printf("打开文件成功,可以对文件进行操作\n");

	// 对文件进行写入操作
	fprintf(fp, "Hello, File!\n");
	fprintf(fp, "This is a test file.\n");
	// 添加新的fprintf写入操作
	fprintf(fp, "Adding another line with fprintf.\n");
	fclose(fp);		//不再使⽤文件时,需要关闭⽂件
	fp = NULL;		//将指针置为NULL,避免成为野指针。

	return 0;
}

注意:此时我们打开test.txt文件会发现我们利用fprintf输入的内容,但是如果我们输入其他的内

容,原来的内容又会被覆盖,这是因为以'w'的方式打开文件的特点就是这样的,或者我们可以

用**'a'的方式在文件末尾添加内容.**

2.2.fscanf

**功能:从指定文件流中读取格式化数据的函数,**它类似于 scanf ,但可以指定输⼊源 (如⽂件、

标准输⼊等),⽽非仅限于控制台输⼊,适用于从文件解析结构化数据(如整数、浮点 数、字符

串等)。

语法:

cpp 复制代码
int fscanf ( FILE * stream, const char * format, ... );

参数:

• stream :指 向FILE 对象的指针,表示要读取的文件流(如 %d 、 %f 、 %s 等)。

•format :格式化字符串,定义如何解析入数据(如 • stdin 、文件指针等)。

•... :可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。

返回值:

• 成功时,函数返回成功填充到参数列表中的项数

• 失败时,发生读取错误,会在对应流上设置错误指时符,则返回 EOF ;到达文件末尾,会在对

应流上设置文件结束指示符,则返回 EOF 。

代码操作:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
	FILE* fp = fopen("test.txt", "w+");
	if (fp == NULL) return 1;

	// 恢复你的 3 行写入
	fprintf(fp, "Hello, File!\n");
	fprintf(fp, "This is a test file.\n");
	fprintf(fp, "Adding another line with fprintf.\n");

	// 指针归位
	rewind(fp);

	char buffer[100];

	while (fscanf(fp, " %99[^\n]", buffer) == 1)
	{
		printf("读取到的内容: %s\n", buffer);
	}

	fclose(fp);
	fp = NULL;
	return 0;
}

2.3.fread

功能:函数用于从 stream 指向的文件流中读取数据块,并将其存储到 ptr 指向的内存缓冲区中。

cpp 复制代码
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

参数:

• ptr :指向内存区域的指针,用于存储从文件中读取的数据。

• size :要读取的每个数据块的⼤小(以字节为单位)。

• count :要读取的数据块的数量。

• stream :指向FILE类型结构体的指针,指定了要从中读取数据的⽂件流。

返回值:返回实际读取的数据块数量。

使⽤注意事项:

• 在使⽤ fread() 之前,需要确保文件已经以二进制可读方式打开

• ptr 指向的内存区域必须足够大,以便存储指定数量和大小的数据块。

• 如果 fread() 成功读取了指定数量的数据块,则返回值等于 count,如果读取数量少于,则可能

已经到达文件结尾或者发⽣了错误。

代码操作:

cpp 复制代码
#include <stdio.h>

int main() 
{
    int data[5] = {0};  // 假设文件中包含 5 个整数数据
    
    // 打开文件
    FILE *fp = fopen("data.bin", "rb");
    
    // 检查文件是否成功打开
    if (fp == NULL) {
        perror("fopen");
        return -1;
    }
    
    // 从文件中读取整数数据块,fread() 函数读取这些数据:
    size_t num_read = fread(data, sizeof(int), 5, fp);
    
    // 检查读取是否成功
    if (num_read != 5) 
    {
        if (feof(fp))
            printf("Reached end of file\n");
        else if (ferror(fp))
            printf("Error reading file\n");
    } 
    else 
    {
        // 输出读取的数据
        for (int i = 0; i < 5; i++) {
            printf("Data[%d]: %d\n", i, data[i]);
        }
    }
    
    // 关闭文件
    fclose(fp);
    return 0;
}

2.4.fgets

功能:从 stream 指定输⼊流中读取字符串,读取到换行符、文件末尾(EOF)或达到指定字符数 (包含结尾的空字符 \0 ),然后将读取到的字符串存储到str指向的空间中。

语法:

cpp 复制代码
char * fgets ( char * str, int num, FILE * stream );

参数:

• str :是指向字符数组的指针,str指向的空间用于存储读取到的字符串。

• num :最大读取字符数(包含结尾的 \0 ,实际最多读取num-1 个字符)

• stream :输⼊流的文件指针(如⽂件流或 stdin)。

返回值:

• 成功时返回 str 指针。

• 失败时,例如在尝试读取字符时遇到⽂件末尾,则设置⽂件结束指⽰器,并返回NULL,需通过

feof()检测;发⽣读取错误,则设置流错误指⽰器,并返回NULL,通过 ferror() 检测。

代码演示:

cpp 复制代码
#include <stdio.h>
int main()
{
    FILE* fp = fopen("test.txt", "r");
    if (fp == NULL)
    {
        perror("fopen\n");
        return 1;
    }
    char arr1[] = "*************";
    fgets(arr1, sizeof(arr1), fp);
    char arr2[] = "*************";
    fgets(arr2, sizeof(arr1), fp);
    fclose(fp);    //不再使⽤⽂件时,需要关闭⽂件
 
    fp = NULL; //将指针置为NULL,避免成为野指针。
 
    return 0;
}

三.文件缓冲区

3.1.概念

ANSI C标准采⽤"缓冲⽂件系统"处理数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程 序中每⼀个正在使⽤的⽂件开辟⼀块"⽂件缓冲区"。从内存向磁盘输出数据会先送到内存中的缓冲 区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊ 到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲 区的⼤⼩根据C编译系统决定的。

其实就是一块内存区域,用来临时存放数据的。

为什么需要文件缓冲区?

根本原因是为了解决 CPU/内存 与 物理磁盘 之间巨大的速度差

  • 内存的速度是纳秒级别的(极快)。

  • 磁盘 I/O 的速度是毫秒级别的(极慢,比内存慢几十万倍)。

如果你的 C 代码里写了一个循环,每次循环都用 fprintf 向文件写入 1 个字节。如果没有缓冲

区,CPU 就得频繁地命令操作系统的磁盘驱动去"写 1 个字节",这就像是你开着一辆 10 吨的大卡

车,每次只运送一颗螺丝钉,极其浪费系统资源和时间。

有了缓冲区之后: 你的代码调用 fprintf 时,数据实际上并没有立刻写进磁盘,而是被

悄悄塞进了这块"缓冲区"内存里。等缓冲区塞满了 ,或者你下达了特定指令,C 标准库才会把这整

整一车的数据,一次性打包"批发"写入物理磁盘。

可以如图加深理解:

满足刷新的条件:

<1>.全缓冲

  • 场景 :对磁盘文件 的读写(如 fopen("test.txt", "w"))。

  • 规则 :只有当缓冲区被完全填满(通常是 4KB 或 8KB,即 _bufsiz 的大小),或者程序正常

  • 结束、调用 fclose 时,数据才会真正写入磁盘。

<2>. 行缓冲

  • 场景 :标准输入输出设备(如终端屏幕 stdout、键盘 stdin)。

  • 规则 :遇到一个换行符 \n,就会立刻触发刷新,把数据推出去。

<3>. 无缓冲

  • 场景 :标准错误输出流(stderr)。

  • 规则:根本没有集装箱。写一个字节,就立刻送达一个字节。因为错误信息通常非常紧急,哪

  • 怕程序下一秒就崩溃,错误日志也必须立刻显示出来。

3.2.fflush

功能:强制刷新参数stream 指定流的缓冲区,确保数据写入底层设备。

语法:

cpp 复制代码
int fflush ( FILE * stream );

参数:

stream :指向文件流的指针(如 stdout 、文件指针等)。

返回值:成功返回 0 ,失败返回 EOF。

代码操作:

cpp 复制代码
#include <stdio.h>
#include <windows.h>

// VS2022 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;
}
相关推荐
月落归舟1 小时前
Java并发容器与框架
java·开发语言
右耳朵猫AI1 小时前
Golang技术周刊 2026年第20周
开发语言·后端·golang
zhangfeng11331 小时前
glibc = GNU C Library (GNU C 标准库)CentOS 7 (glibc 2.17) pip支持
c语言·人工智能·神经网络·机器学习·centos·gnu
不吃土豆的马铃薯1 小时前
高性能服务器程序框架详解(包括Reactor,有限状态机等)
linux·服务器·开发语言·网络·c++
bucenggaibian1 小时前
搭建CMD编译C语言环境
linux·c语言·windows
Shadow(⊙o⊙)1 小时前
库的制作与原理1.0,库打包,协作,目标文件.o、ELF格式。
linux·运维·服务器·开发语言
wyc是xxs1 小时前
用纯 Node.js 写了一个 JS 解释器 — kernel-js-lite
开发语言·javascript·npm·node.js
hai3152475431 小时前
AI工业化编程的黎明:由逻辑压缩到知识融合的范式跃迁
开发语言·人工智能·线性代数·机器学习·数学建模·概率论
Cloud_Shy6181 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第一章 Item 7 - 9)
开发语言·数据库·python