【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

相关文章

C语言中联合体union的使用

本文编辑整理自: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=179471 一、前言 “联合体”(union)与“结构体”(struct)有一些相似之处。但两者有本质上的不同。在结构体中,各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来,无需把原生代码转换为uniapp,可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录,原生入口组件的路径 4、manifest.json中配置分包,使用原生组件 5、需要把原生代码包里的页面修改成组件的方

Vim使用基础篇

本文内容大部分来自 vimtutor,自带的教程的总结。在终端输入vimtutor 即可进入教程。 先总结一下,然后再分别介绍正常模式,插入模式,和可视模式三种模式下的命令。 目录 看完以后的汇总 1.正常模式(Normal模式) 1.移动光标 2.删除 3.【:】输入符 4.撤销 5.替换 6.重复命令【. ; ,】 7.复制粘贴 8.缩进 2.插入模式 INSERT

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

公共筛选组件(二次封装antd)支持代码提示

如果项目是基于antd组件库为基础搭建,可使用此公共筛选组件 使用到的库 npm i antdnpm i lodash-esnpm i @types/lodash-es -D /components/CommonSearch index.tsx import React from 'react';import { Button, Card, Form } from 'antd'

17.用300行代码手写初体验Spring V1.0版本

1.1.课程目标 1、了解看源码最有效的方式,先猜测后验证,不要一开始就去调试代码。 2、浓缩就是精华,用 300行最简洁的代码 提炼Spring的基本设计思想。 3、掌握Spring框架的基本脉络。 1.2.内容定位 1、 具有1年以上的SpringMVC使用经验。 2、 希望深入了解Spring源码的人群,对 Spring有一个整体的宏观感受。 3、 全程手写实现SpringM

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

ps基础入门

1.基础      1.1新建文件      1.2创建指定形状      1.4移动工具          1.41移动画布中的任意元素          1.42移动画布          1.43修改画布大小          1.44修改图像大小      1.5框选工具      1.6矩形工具      1.7图层          1.71图层颜色修改          1

代码随想录算法训练营:12/60

非科班学习算法day12 | LeetCode150:逆波兰表达式 ,Leetcode239: 滑动窗口最大值  目录 介绍 一、基础概念补充: 1.c++字符串转为数字 1. std::stoi, std::stol, std::stoll, std::stoul, std::stoull(最常用) 2. std::stringstream 3. std::atoi, std