本文主要是介绍【C语言基础】小小的代码想看外面的世界——文件操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
【C语言基础】小小的代码想看外面的世界——文件操作
笔者:吃汉堡吃到饱
森林的Visual Studio洞穴里
有一段短短的Code
他在内存的地底即将走完短暂的一生
它不曾见过太阳
它不曾见过雪山
它不曾见过冰川、极光
也不曾见过爱情的模样
终于在一个分不清季节和早晚的时间
Code带着一段数据从一个接口离开了源文件
它如此渺小
又如此勇敢
本文诗句改写自:天真的和感伤的小说家的个人空间-天真的和感伤的小说家个人主页-哔哩哔哩视频 (bilibili.com)
姑且让我们的视线随Code一同游移在缓冲区和磁盘之间,去看看C语言的文件操作吧。
一、为什么要使用文件
森林中人尽皆知的是
Code很快就会走完一生的路
那段距离不过是一束光从内存的一段照到另一端
于是缓缓地
小小Code放下背上的数据和没处理完的函数
躺在return的叶子上缓缓闭上眼睛
他知道这是最后一次闭上
Code终究脱离不了死亡的宿命,来时赤条条,去也无牵挂,Visual Studio的黑框将其拘束于树林的一隅。又有谁能记得它曾去过何方,他曾做过何事,他曾留下什么。
于是文件操作应运而生,它可以导入上一段Code曾经使用过、处理过的数据,亦可保存这一段Code的数据,实现数据的共享与数据持久性。
- 数据持久性: 文件操作使得程序能够将数据存储在文件中,从而实现数据的持久性。一旦程序运行结束,数据仍然保存在文件中,下次运行时可以重新加载。
- 数据共享: 文件允许数据在不同的程序之间进行共享。通过将数据写入文件,其他程序或者同一程序的其他实例都可以读取和使用这些数据。
二、文件操作简介
小小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.使用缓冲区的原因
- 性能提升: 文件读写是相对较慢的操作,通过使用缓冲区,可以减少对文件的实际读写次数,从而提高性能。数据首先被读取到内存缓冲区中,或者从内存缓冲区中写入文件,而不是直接对文件进行频繁的读写操作。
- 减少系统调用: 操作系统通常以块为单位进行文件读写,而不是按字节一个一个地进行。缓冲区可以存储一定大小的数据块,减少了系统调用的次数,提高了数据传输的效率。
- 避免频繁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语言基础】小小的代码想看外面的世界——文件操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!