c语言终点站--文件操作

2023-10-11 09:44
文章标签 语言 操作 终点站

本文主要是介绍c语言终点站--文件操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:

为什么要学习文件操作呢?想要知道这个问题,我们就需要先了解什么是数据的可持久化。

那么什么是数据的可持久化呢?数据的可持久化就是把内存中的数据对象永久的保存在电脑的磁盘文件中,将程序数据在持久状态和瞬时状态相互转换的机制。

而文件操作就可以让我们达到这一目的。

1.什么是文件

磁盘上的文件都是文件。

在程序设计中,我们将文件分为两种:程序文件、数据文件(从文件功能角度来分类)。

1.1程序文件

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

像我们写代码时创建的test.c或者test.cpp文件就是程序文件。

1.2 数据文件

数据文件存放程序在运行时需要读取的数据。

列如通讯录中的各个对象的数据,在使用通讯录程序时我们需要先从数据文件中读取数据。

接下来我们主要讨论的是数据文件。

在我们日常写代码刷题,处理数据的输入输出都是终端作为对象,即从终端的键盘输入数据,运行结果显示到显示器上。        

就像这个:

其实更多时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理 的就是磁盘上文件。

1.3 文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分:文件路径+文件名主干+文件后缀

比如:

 c:\code\test.txt

 这个路径的意思就是c盘下的code文件夹里面的test.txt文件

为了方便查找,文件标识常被称为文件名。 而且在同一目录下的文件名必须不一样。

2.文件的打开与关闭

2.1文件指针

文件指针其实就是文件类型的指针。

在我们使用一个文件时,会在内存开辟一块相应的文件信息区,用来存放文件的相关信息(如文件的名 字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为FILE(注意大写).

例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:

struct _iobuf {char *_ptr;int   _cnt;char *_base;int   _flag;int   _file;int   _charbuf;int   _bufsiz;char *_tmpfname;};
typedef struct _iobuf FILE;

注意,不同的编译器FILE类型包含的内容不完全相同,但是大同小异。

每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息, 使用者不必关心细节。

一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。

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

FILE* pf;//文件指针变量

2.2文件的打开与关闭

要想将数据写入文件,或者是将文件中的数据读出来,都需要先打开目标文件。就像我们如果想喝牛奶,那肯定是先把瓶盖拧开。同样的,在完成一些列操作后,我们需要把文件关闭,跟喝完牛奶要盖上瓶盖一样的道理。

在打开文件的时候,会返回一个FILE类型的指针来指向该文件,我们就通过这个指针来对文件进行数据的读取和写入。

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream ) ;

当我们使用fopen来打开文件的时候,需要传入的参数有两个,一个是文件路径filename,还有一个是打开文件的方式mode.

文件路径怎么书写?

在C语言中,文件路径的书写是根据操作系统的不同而有所差异。下面是一些常用操作系统的文件路径书写方式示例:

  • 绝对路径:C:\folder\file.c
  • 相对路径:folder\file.c 或者 …\folder\file.c (… 表示上一级目录)
绝对路径举例:
 c:\code\test.txt
相对路径举例:
..\\code\\test.txt

这里为什么要用“\\”而不是“\”呢?这就是涉及到转移字符的问题了,这里就不多说了。

有哪些打开方式呢?

文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)        为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据建立一个新的文件
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件         
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个新的二进制文件建立一个新的文件         
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件         

实例:

#include <stdio.h>
int main ()
{FILE * pFile;//打开文件pFile = fopen ("myfile.txt","w");//文件操作if (pFile!=NULL){fputs ("fopen example",pFile);//关闭文件fclose (pFile);}return 0;
}

 3.文件的顺序读写

功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入函数fread文件
二进制输出函数fwrite文件

3.1scanf/fscanf/sscanf的区别:

3.1.1 scanf

标准输入(即键盘)读取输入数据。它的函数原型为int scanf(const char *format, ...),其中format是格式字符串,用于指定输入数据的格式。scanf根据格式字符串解析输入数据,并将解析结果存储到对应的参数中。

3.1.2 fscanf

文件中读取输入数据。它的函数原型为int fscanf(FILE *stream, const char *format, ...),其中stream是指向文件的指针,format是格式字符串。fscanf根据格式字符串从文件中解析输入数据,并将解析结果存储到对应的参数中。

3.1.3 sscanf

字符串中读取输入数据。它的函数原型为int sscanf(const char *str, const char *format, ...),其中str是输入字符串,format是格式字符串。sscanf根据格式字符串从字符串中解析输入数据,并将解析结果存储到对应的参数中。

区别:

这些函数在功能上是类似的,但是适用的输入源不同。scanf适用于从标准输入(键盘)读取数据,fscanf适用于从文件读取数据,sscanf适用于从字符串读取数据。

需要注意的是,这些函数都可以返回成功匹配并读取的参数个数,用于判断读取是否成功。返回值为EOF(-1)表示读取失败。此外,它们的格式字符串的语法是相同的,都可以使用格式控制符来指定输入数据的类型和格式。

printf/fprintf/sprintf的区别跟上面的一样,只不过是输出数据。

4.文件的随机读写

4.1 fseek

根据文件指针的位置和偏移量来定位文件指针.

int fseek ( FILE * stream, long int offset, int origin );

举例:

#include <stdio.h>
int main()
{FILE* pFile;pFile = fopen("example.txt", "wb");fputs("This is an apple.", pFile);fseek(pFile, 9, SEEK_SET);fputs(" sam", pFile);fclose(pFile);return 0;
}

打开example.txt文件

我们发现“sam"被插入到了字符串"This is an apple."中第九个位置。

具体是怎么实现的呢?

来看这一行代码

fseek(pFile, 9, SEEK_SET);
  • pFile 是指向文件的指针;
  • 9 是 offset 参数,表示要移动的字节数或记录数;
  • SEEK_SET 是 origin 参数,用于指定相对位置。SEEK_SET 表示从文件的开头开始计算偏移量。

我们通过这一行代码将文件指针pFile的位置向后移动了9个位置,所以我们在接下来的写入操作时,是从第9个位置开始写入的。

4.2 ftell

返回文件指针相对于起始位置的偏移量

long int ftell ( FILE * stream );

举例:

int main()
{FILE* pFile;long size;pFile = fopen("example.txt", "rb");if (pFile == NULL) perror("Error opening file");else{fseek(pFile, 0, SEEK_END);   // non-portablesize = ftell(pFile);fclose(pFile);printf("Size of example.txt: %ld bytes.\n", size);}return 0;
}

这里我们先用fseek将文件指针移动到数据字节的最后一个位置,这个时候ftell返回的就是起始位置到末位置的距离。

 4.3 rewind

让文件指针的位置回到文件的位置

void rewind ( FILE * stream );

举例:


int main() {FILE* pf = fopen("example.txt", "w+");//读和写文件char arr[27] = { 0 };for (char i = 'a'; i <= 'z'; i++) {fputc(i, pf);}//将26个字母写入到文件中//此时文件指针的位置指向最后一个位置//输出此时文件指针偏移量int x = ftell(pf);printf("此时文件指针跟起始位置的偏移量为%d\n", x);rewind(pf);//将文件指针的位置移到起始位置fread(arr, 1, 26, pf);//将文件里的数据读入到arr中for (int i = 0; i < 27; i++) {printf("%c ", arr[i]);}fclose(pf);pf = NULL;return 0;
}

我们可以看到,通过frewind将文件指针移动到起始位置,我们才能从头开始将26个字母读入到字符数组arr中。

5.文本文件和二进制文件

5.1二进制文件

我们知道,在内存中所有数据都是以二进制的形式储存的。

如果不加任何转换就将其输入到文件中,那这种文件就是二进制文件,而这种文件里的数据我们通常是看不懂的。

举例:

int main() {FILE* pf = fopen("example.txt", "wb+");//二进制文件的读和写int a=10000;fwrite(&a, 4, 1, pf);fclose(pf);pf = NULL;return 0;
}

 

是不是看不懂?

用二进制编译器打开一看

10 27 00 00这是表示的是啥意思?

其实这就是二进制中的10000,只不过是用16进制表示出来了

5.2 文本文件?

求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文 本文件。

用以上例子在文本文件中又是怎么表示的呢?

10000在文本文件中就是五个字符排列在一起的,所以10000的ASCII码的形式存储形式是

 6.文件读取结束的判定

6.1被错误使用的feof

feof函数是C语言中的一个库函数,用于检测文件流的结束标志。它的作用是判断文件是否已经达到文件末尾。当文件读取到末尾时,feof函数返回非零值(真),否则返回零值(假)。

注意:

在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。 而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

1. 文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)。

2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。

正确使用:

1.文本文件中:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{int c; // 注意:int,非char,要求处理EOFFILE* fp = fopen("test.txt", "r");if(!fp) {perror("File opening failed");return EXIT_FAILURE;}//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOFwhile ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环{putchar(c);}//判断是什么原因结束的if (ferror(fp))puts("I/O error when reading");else if (feof(fp))puts("End of file reached successfully");fclose(fp);
}
2.二进制文件中:
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{double a[SIZE] = {1.,2.,3.,4.,5.};FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组fclose(fp);double b[SIZE];fp = fopen("test.bin","rb");size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组if(ret_code == SIZE) {//判断成功读取的的数据次数是否等于SIZEputs("Array read successfully, contents: ");for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]);putchar('\n');} else { // 不等于说明读取失败了,下面排查失败原因if (feof(fp))//读取到了末尾printf("Error reading test.bin: unexpected end of file\n");else if (ferror(fp)) {//没有读取到末尾perror("Error reading test.bin");}}fclose(fp);return 0;
}

 7.文件缓冲区

缓冲区概念:

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

同时,缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

为什么要引入缓冲区?

比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

总而言之,缓冲区使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

这里再介绍一个刷新缓冲区的函数fflush:

int fflush ( FILE * stream );

其中,stream 是要刷新的流的指针。如果 stream 为 NULL,则刷新所有流的缓冲区。

8.可持久化数据的具体项目例子

这里就给大家看一看我之前写的通讯录:

c语言小课设--通讯录(动态内存管理+可持久化数据)-CSDN博客该项目实现一个通讯录功能,除了能根据具体需求扩大空间之外,也实现了最基本基本的增删查改等功能,并在退出通讯录时销毁创造的空间,从而不造成内存泄露。另外,这个项目由三部分组成,函数功能的实现在Contact.c源文件中,各种头文件、函数等声明则由文件Contact.h来实现,最后测试在源文件test.c文件中进行。https://blog.csdn.net/qq_62987647/article/details/133466779?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22133466779%22%2C%22source%22%3A%22qq_62987647%22%7D

9.总结

文件操作的学习可以让我们持久化数据,同时也理解了文件数据是怎么输入输出的。

有啥用呢?

用处大多了,比如说我们在用c语言写一个程序的时候,我们就可以将程序中的数据存放在文件中,以便于我们下次打开程序还能读取到,这也就更加符合我们的实际需求。

其实呢,当你学到文件这一块内容时,你对c语言的认知基本上有了一个新的高度,但是并不意味着是c语言学习的终点,学海无涯,我们当以谦卑的心态学习。

事已至此,好好学习,给我点赞,加油!

这篇关于c语言终点站--文件操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非

C 语言基础之数组

文章目录 什么是数组数组变量的声明多维数组 什么是数组 数组,顾名思义,就是一组数。 假如班上有 30 个同学,让你编程统计每个人的分数,求最高分、最低分、平均分等。如果不知道数组,你只能这样写代码: int ZhangSan_score = 95;int LiSi_score = 90;......int LiuDong_score = 100;int Zhou

C 语言的基本数据类型

C 语言的基本数据类型 注:本文面向 C 语言初学者,如果你是熟手,那就不用看了。 有人问我,char、short、int、long、float、double 等这些关键字到底是什么意思,如果说他们是数据类型的话,那么为啥有这么多数据类型呢? 如果写了一句: int a; 那么执行的时候在内存中会有什么变化呢? 橡皮泥大家都玩过吧,一般你买橡皮泥的时候,店家会赠送一些模板。 上

动手学深度学习【数据操作+数据预处理】

import osos.makedirs(os.path.join('.', 'data'), exist_ok=True)data_file = os.path.join('.', 'data', 'house_tiny.csv')with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA