【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

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

python实现pdf转word和excel的示例代码

《python实现pdf转word和excel的示例代码》本文主要介绍了python实现pdf转word和excel的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、引言二、python编程1,PDF转Word2,PDF转Excel三、前端页面效果展示总结一

在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码

《在MyBatis的XML映射文件中<trim>元素所有场景下的完整使用示例代码》在MyBatis的XML映射文件中,trim元素用于动态添加SQL语句的一部分,处理前缀、后缀及多余的逗号或连接符,示... 在MyBATis的XML映射文件中,<trim>元素用于动态地添加SQL语句的一部分,例如SET或W

Go语言实现将中文转化为拼音功能

《Go语言实现将中文转化为拼音功能》这篇文章主要为大家详细介绍了Go语言中如何实现将中文转化为拼音功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 有这么一个需求:新用户入职 创建一系列账号比较麻烦,打算通过接口传入姓名进行初始化。想把姓名转化成拼音。因为有些账号即需要中文也需要英

C# 读写ini文件操作实现

《C#读写ini文件操作实现》本文主要介绍了C#读写ini文件操作实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、INI文件结构二、读取INI文件中的数据在C#应用程序中,常将INI文件作为配置文件,用于存储应用程序的

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创

使用C#代码计算数学表达式实例

《使用C#代码计算数学表达式实例》这段文字主要讲述了如何使用C#语言来计算数学表达式,该程序通过使用Dictionary保存变量,定义了运算符优先级,并实现了EvaluateExpression方法来... 目录C#代码计算数学表达式该方法很长,因此我将分段描述下面的代码片段显示了下一步以下代码显示该方法如