【C语言基础】小小的代码想看外面的世界——文件操作

2024-03-14 04:10

本文主要是介绍【C语言基础】小小的代码想看外面的世界——文件操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【C语言基础】小小的代码想看外面的世界——文件操作

笔者:吃汉堡吃到饱

森林的Visual Studio洞穴里

有一段短短的Code

他在内存的地底即将走完短暂的一生

它不曾见过太阳

它不曾见过雪山

它不曾见过冰川、极光

也不曾见过爱情的模样

终于在一个分不清季节和早晚的时间

Code带着一段数据从一个接口离开了源文件

它如此渺小

又如此勇敢

本文诗句改写自:天真的和感伤的小说家的个人空间-天真的和感伤的小说家个人主页-哔哩哔哩视频 (bilibili.com)

姑且让我们的视线随Code一同游移在缓冲区和磁盘之间,去看看C语言的文件操作吧。

一、为什么要使用文件

森林中人尽皆知的是

Code很快就会走完一生的路

那段距离不过是一束光从内存的一段照到另一端

于是缓缓地

小小Code放下背上的数据和没处理完的函数

躺在return的叶子上缓缓闭上眼睛

他知道这是最后一次闭上

Code终究脱离不了死亡的宿命,来时赤条条,去也无牵挂,Visual Studio的黑框将其拘束于树林的一隅。又有谁能记得它曾去过何方,他曾做过何事,他曾留下什么。

于是文件操作应运而生,它可以导入上一段Code曾经使用过、处理过的数据,亦可保存这一段Code的数据,实现数据的共享与数据持久性。

  1. 数据持久性: 文件操作使得程序能够将数据存储在文件中,从而实现数据的持久性。一旦程序运行结束,数据仍然保存在文件中,下次运行时可以重新加载。
  2. 数据共享: 文件允许数据在不同的程序之间进行共享。通过将数据写入文件,其他程序或者同一程序的其他实例都可以读取和使用这些数据。

二、文件操作简介

小小Code穿过接口的黑暗

穿过无数的指针与电路与光点

他看到文件 那是一条道路的形状

截断的天空

延伸的石板

道路跌跌撞撞扭曲着模糊着

Code不停歇地奔跑

追逐二进制的梦

本章节内容细碎而繁杂,笔者学习的时候也是翻阅许多博客,只希望能全面而细致地了解文件操作。

1.文件的分类

1.二进制文件

把内存中的数据按照其在内存中存储形式原样输出到磁盘上存放。

基于值编码,把内存中的数据原样输出到磁盘上 ,一般需要自己判断或使用特定软件分析数据格式

例如:

在内存中将整数123以二进制形式存放(假设123为short类型),则:

内存中的123用二进制数“0000 0000 0111 1011”直接存储,所以将二进制文件从磁盘导入到内存不需要转换数据。

2.文本文件

文本文件中数据以字符形式呈现,一个字符就占一个字节,而字节在计算机中又以ASCII码来识别,在存储文本文件时需要将ASCII码转换为二进制形式存储。

文本文件基于字符编码。 一般可以使用文本编辑器直接打开。

例如:

在内存中奖整数123以文本形式存储:

字符‘1’的ASCII码为(49),则二进制码为:‘0011 0001’

字符‘2’的ASCII码为(50),则二进制码为:‘0011 0010’

字符‘3’的ASCII码为(51),则二进制码为:‘0011 0011’

2.文件标识(文件名)

一个文件需要有一个唯一的文件标识,从而使用户可以识别和引用,文件标识包括三个部分:

文件路径 + 文件名主干 +文件后缀

一个文件要有一个唯一的文件标识,以便用户识别和引用。文件名包含3部分:

文件路径+文件名主干+文件后缀

例如: c:\code\test.txt

TIP:

相对路径与绝对路径

相对路径:从当前位置开始的路径。在相对路径中,通常使用"…“表示上一级目录,”."表示当前目录。如:documents/file.txt …/pictures/image.jpg

绝对路径:绝对路径是文件或目录在文件系统中的完整路径,从根目录开始一直到目标文件或目录。绝对路径提供了从文件系统根目录到指定位置的完整路径信息,不受当前工作目录的影响。

3.文件缓冲区

1.文件操作中缓冲区

请添加图片描述

2.使用缓冲区的原因
  1. 性能提升: 文件读写是相对较慢的操作,通过使用缓冲区,可以减少对文件的实际读写次数,从而提高性能。数据首先被读取到内存缓冲区中,或者从内存缓冲区中写入文件,而不是直接对文件进行频繁的读写操作。
  2. 减少系统调用: 操作系统通常以块为单位进行文件读写,而不是按字节一个一个地进行。缓冲区可以存储一定大小的数据块,减少了系统调用的次数,提高了数据传输的效率。
  3. 避免频繁IO操作: 缓冲区的引入避免了每次读写都需要进行实际的IO操作。当缓冲区满或为空时,才会触发实际的磁盘读写。

4.文件指针

文件指针 是一个指向 FILE 类型结构的指针,用于进行文件的读写操作。FILE结构体是由C标准库提供的,它包含了许多与文件相关的信息,如文件位置指针、文件状态等。

typedef struct {
int level;
unsigned flags;
char fd;
unsigned char hold;
int bsize;
unsigned char _FAR *buffer;
unsigned char _FAR *curp;
unsigned istemp;
short token;
} FILE;

此处内容仅供展示,本文不分析其结构体内部变量的作用。

在标准输入输出库中,系统定义了三个FILE型的指针变量

1.stdin(标准输入文件指针):指向在内存中与键盘相应的文件信息区,我们使用的scanf、getchar 函数默认从此终端获得数据。

2.stdout(标准输出文件指针):指向在内存中与显示器屏幕相应的文件信息区。我们使用的printf、puts 函数默认输出信息到此终端

3.stderr(标准错误文件指针):用来输出出错的信息,它同样指向内存中与显示器屏幕相应的文件信息区。perror函数时信息打印在此终端。

三、文件操作函数

小小Code走在路上

它吞下仅存的

食而无味的数据

直到输入流

流淌进身体

山野震荡 林木簌簌

沼泽拔地而起

河流在天空奔腾

小小Code仿佛拥有巨大的力量

它跨过海岛冰川荒漠极光

留下他的足迹

1.打开与关闭函数 & 检测文件是否结束函数

1.fopen
FILE *fopen(const char *filename, const char *mode)//filename -- 字符串,表示要打开的文件名称。//mode -- 字符串,表示文件的访问模式//该函数返回一个 FILE 指针。否则返回 NULL,且设置全局变量 errno 来标识错误。

功能:使用给定的模式 mode 打开 filename 所指向的文件。

操作名操作条件
r(只读)为输入打开一个文本文件文件必须存在
w(只写)为输出打开一个文本文件存在则清除,不存在则创建
a(追加)向文本文件尾增加数据文件必须存在
rb(只读)为输入打开一个二进制文件文件必须存在
wb(只写)为输出打开一个二进制文件文件不存在则创建一个新的文件

在标识符之后加上一个加号+,可表示既可读也可以写。

2.fclose
int fclose(FILE *stream)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流。//如果流成功关闭,则该方法返回零。如果失败,则返回 EOF。

功能:关闭流stream,并刷新所有缓冲区。

写一段代码来检测一下学习成果吧!

#include <stdio.h>
#include <stdlib.h>int main()
{FILE * fp;fp = fopen ("file.txt", "w+");fprintf(fp, "%s %s %s", "I", "AM", "HAMBURGER");fclose(fp);return(0);
}

这段代码将创建一个文件file.txt

内容包含 I AM HAMBURGER

3.feof
int feof(FILE *stream)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。//当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。

功能:测试给定流 stream 的文件结束标识符。

示例:写一个函数读取如上文件,为了显示feof的作用,引入fgetc函数读取字符。

#include <stdio.h>int main ()
{FILE *fp;int c;fp = fopen("file.txt","r");while(1){c = fgetc(fp);if( feof(fp) ){ break ;}printf("%c", c);}fclose(fp);return(0);
}
//这段代码可以查看上面文件的内容并打印出来。
//在读取过程中,fgetc每获取一个字符,把文件位置指针向后移动,直到到文件末尾,feof函数检测到文件结束,从而返回0,终止while循环。

2.文件的顺序读写

1.逐个字符读写
1.fgetc
int getc(FILE *stream)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。//该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。

功能:从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

2.fputc
int fputc(int char, FILE *stream)//char -- 这是要被写入的字符。该字符以其对应的 int 值进行传递。//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。//如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

功能:把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。

3.举个栗子!
#include <stdio.h>int main() {// 定义文件指针FILE *inputFile, *outputFile;// 使用 fputc 函数将字符串写入文件outputFile = fopen("output.txt", "w");if (outputFile == NULL) {printf("输出文件打开失败。\n");return 1;  // 返回非零表示出错}// 写入字符串到文件const char *text = "I AM HAMBURGER";for (int i = 0; text[i] != '\0'; ++i) {fputc(text[i], outputFile);}fclose(outputFile);printf("输出文件写入成功。\n");// 使用 fgetc 函数从文件读取字符inputFile = fopen("output.txt", "r");if (inputFile == NULL) {printf("输入文件打开失败。\n");return 1;  // 返回非零表示出错}// 从文件读取字符并输出printf("从文件读取的字符:");int character;while ((character = fgetc(inputFile)) != EOF) {putchar(character);}fclose(inputFile);return 0;  
}
2.字符串读写
1.fgets
char *fgets(char *str, int n, FILE *stream)//str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。//n -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。//如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。如果发生错误,返回一个空指针。

功能:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

2.fputs
int fputs(const char *str, FILE *stream)//str -- 这是一个数组,包含了要写入的以空字符终止的字符序列。//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。//该函数返回一个非负值,如果发生错误则返回 EOF。

功能:把字符串写入到指定的流 stream 中,但不包括空字符。

3.举个栗子!
#include <stdio.h>int main() {// 定义文件指针FILE *outputFile, *inputFile;// 使用 fputs 函数将字符串写入文件outputFile = fopen("output.txt", "w");if (outputFile == NULL) {printf("输出文件打开失败。\n");return 1;  // 返回非零表示出错}// 写入字符串到文件fputs("I AM HAMBURGER", outputFile);// 关闭输出文件fclose(outputFile);printf("输出文件写入成功。\n");// 使用 fgets 函数从文件读取字符串char buffer[50];inputFile = fopen("output.txt", "r");if (inputFile == NULL) {printf("输入文件打开失败。\n");return 1;  // 返回非零表示出错}// 从文件读取字符串并输出到控制台fgets(buffer, sizeof(buffer), inputFile);printf("从文件读取的字符串:%s\n", buffer);// 关闭输入文件fclose(inputFile);return 0;  // 返回零表示成功
}
3.格式化读写
1.fscanf
int fscanf(FILE *stream, const char *format, ...)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。//format -- 这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。

功能:从流 stream 读取格式化输入。

2.fprintf
int fprintf(FILE *stream, const char *format, ...)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。//format -- 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。

功能: 发送格式化输出到流 stream 中。

3.举一个香甜可口的栗子:
#include <stdio.h>typedef struct {int num;           // 汉堡订单号char size;         // 汉堡大小char flavor[50];   // 汉堡口味需求
} HamburgerOrder;int main() {// 定义文件指针FILE *outputFile, *inputFile;// 创建一个结构体变量HamburgerOrder order = {123, 'L', "No onions"};// 使用 fprintf 函数将结构体写入文件outputFile = fopen("orders.txt", "w");if (outputFile == NULL) {printf("输出文件打开失败。\n");return 1;}// 写入结构体到文件fprintf(outputFile, "%d %c %s\n", order.num, order.size, order.flavor);// 关闭输出文件fclose(outputFile);printf("输出文件写入成功。\n");// 使用 fscanf 函数从文件读取结构体HamburgerOrder readOrder;inputFile = fopen("orders.txt", "r");if (inputFile == NULL) {printf("输入文件打开失败。\n");return 1;  // 返回非零表示出错}// 从文件读取结构体并输出到控制台fscanf(inputFile, "%d %c %s", &readOrder.num, &readOrder.size, readOrder.flavor);printf("从文件读取的结构体:\n");printf("订单号: %d\n", readOrder.num);printf("大小: %c\n", readOrder.size);printf("口味需求: %s\n", readOrder.flavor);// 关闭输入文件fclose(inputFile);return 0;  // 返回零表示成功
}
4.二进制读写
1.fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)//ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。//size -- 这是要读取的每个元素的大小,以字节为单位。//nmemb -- 这是元素的个数,每个元素的大小为 size 字节。//stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

功能:从给定流 stream 读取数据到 ptr 所指向的数组中。

2.fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)//ptr -- 这是指向要被写入的元素数组的指针。//size -- 这是要被写入的每个元素的大小,以字节为单位。//nmemb -- 这是元素的个数,每个元素的大小为 size 字节。//stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。

功能:把 ptr 所指向的数组中的数据写入到给定流 stream 中。

3.文件的随机读写

1.rewind
void rewind(FILE *stream)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

功能:设置文件位置为给定流 stream 的文件的开头。

2.ftell
long int ftell(FILE *stream)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

功能:设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。

3.fseek
int fseek(FILE *stream, long int offset, int whence)//stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。//offset -- 这是相对 whence 的偏移量,以字节为单位。//whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:

功能:设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。

四、实际应用

学习文件操作的初衷是为将自己学生信息管理系统的数据持久化,实现数据的保存与导入,此处借用@超凡的炒饭 的代码,展示该功能的具体实现。

void SaveStudent()//程序退出时,将信息保存到文件中
{FILE* pw = fopen("stu.txt", "wb");//打开文件if (pw == NULL)return;//如果pw是空指针,说明打开文件失败,直接返回struct node* pc = head->next;//创建临时指针指向首节点。这样就可以从头开始遍历链表。while (pc != NULL)//遍历{fwrite(&pc->date, sizeof(struct stu), 1, pw);//二进制写文件(将信息写入文件保存,下次打开程序在读取就不用每次都手动录入了)pc = pc->next;//移动到下一个节点}fclose(pw);//关闭文件pw = NULL;
}
void ReadStudent()//程序启动时,将信息从文件中读取(尾插法创建节点将文件中的信息读取到内存中)
{FILE* pr = fopen("stu.txt", "rb");//打开文件二进制读printf("正在读取........\n");if (pr == NULL){printf("读取失败!!!\n");return;//如果pr是空指针,说明打开文件失败,直接返回}struct node* new = (struct node*)malloc(sizeof (struct node)) ;//创建新节点new->next = NULL;struct node* end = head;//创建尾指针while (fread(&new->date, sizeof(struct stu), 1, pr) == 1)//一直读取直到读取失败{end->next = new;end = new;//更新尾指针new = (struct node*)malloc(sizeof(struct node));//为下一个新节点申请空间new->next = NULL;}free(new); //最后多定义一个节点,要将它释放掉fclose(pr); //关闭文件printf("读取成功\n");
}

结语:

小小Code终于走完了一生的路

而另一个孤独的Code也走在路上

闭上眼睛相信另一个它就在那里

另一个孤独的Code开始重新寻找

在小小Code出发前

他们都不能证实

世界上另一个同伴的存在

但现在

现世的Code睁大眼睛

森林被生生劈开

一半是火焰

一半是脚印

参考文献:

【C语言】高效处理文件内容:C语言的文件操作技巧与窍门_c语言 文本处理_寒晓星的博客-CSDN博客

c语言文件操作(9000字详解!!!)_c循环写入文件-CSDN博客

特别鸣谢:

超凡的炒饭_-CSDN博客 提供的部分源码

这篇关于【C语言基础】小小的代码想看外面的世界——文件操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/807163

相关文章

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

C语言中位操作的实际应用举例

《C语言中位操作的实际应用举例》:本文主要介绍C语言中位操作的实际应用,总结了位操作的使用场景,并指出了需要注意的问题,如可读性、平台依赖性和溢出风险,文中通过代码介绍的非常详细,需要的朋友可以参... 目录1. 嵌入式系统与硬件寄存器操作2. 网络协议解析3. 图像处理与颜色编码4. 高效处理布尔标志集合

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

利用Python调试串口的示例代码

《利用Python调试串口的示例代码》在嵌入式开发、物联网设备调试过程中,串口通信是最基础的调试手段本文将带你用Python+ttkbootstrap打造一款高颜值、多功能的串口调试助手,需要的可以了... 目录概述:为什么需要专业的串口调试工具项目架构设计1.1 技术栈选型1.2 关键类说明1.3 线程模

Python ZIP文件操作技巧详解

《PythonZIP文件操作技巧详解》在数据处理和系统开发中,ZIP文件操作是开发者必须掌握的核心技能,Python标准库提供的zipfile模块以简洁的API和跨平台特性,成为处理ZIP文件的首选... 目录一、ZIP文件操作基础三板斧1.1 创建压缩包1.2 解压操作1.3 文件遍历与信息获取二、进阶技

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

Java中字符串转时间与时间转字符串的操作详解

《Java中字符串转时间与时间转字符串的操作详解》Java的java.time包提供了强大的日期和时间处理功能,通过DateTimeFormatter可以轻松地在日期时间对象和字符串之间进行转换,下面... 目录一、字符串转时间(一)使用预定义格式(二)自定义格式二、时间转字符串(一)使用预定义格式(二)自

C 语言中enum枚举的定义和使用小结

《C语言中enum枚举的定义和使用小结》在C语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看... 目录1、引言2、基本定义3、定义枚举变量4、自定义枚举常量的值5、枚举与switch语句结合使用6、枚