c语言进阶学习笔记六详解文件操作(看完这篇,你的文件操作就入门了!)(代码片段)

大家好我叫张同学 大家好我叫张同学     2022-12-14     631

关键词:

前言

在之前的学习过程中,我们所编写的程序在执行过程中所产生的数据及结果都只是临时存放在内存区域,一旦程序运行结束,该程序所涉及的内存空间全部返回给操作系统。这时候如果我们想要去查看这些数据和结果,显然是做不到的!那有没有什么方法能够解决这个问题呢?也就是说可以将程序运行过程所产生的过程数据和结果数据都保存起来,即便程序结束,我们也可以找到这些内容,甚至是在之后所写的其他程序中也能继续使用这些内容。
答案当然是-- - 有,这也就是我们这篇文章所要讨论的东西–文件。



为什么需要文件?

这个问题其实刚刚已经说到了,简而言之,我们需要文件是
(1)防止程序结束后,数据就消失了
(2)为了持久化的保存数据


什么是文件?

计算机中所说的文件是以计算机硬盘为载体,存储在计算机上的信息集合。例如我们熟悉的文本文件,图片文件,视频文件,程序等等。
文件有各种各样的类型,但是在程序设计中,我们一般谈的文件有两种∶程序文件、数据文件
(1)程序文件
包括源程序文件(后缀为.c ), 目标文件(windows环境后缀为.obj), 可执行程序(windows环境后缀为.exe)。
(2)数据文件
文件的内容不是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。


文件名

如果我们要区分不同的人,很多时候需要通过他 / 她的名字来进行区分;如果要区分不同的物品,也要通过不同的物品名称来区分一样。所以为了区分不同的文件,也需要给不同的文件不同的名称一样。
一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分∶文件路径 + 文件名主干 + 文件后缀
例如︰c : \\code\\test.txt
文件路径:c : \\code
文件名主干:test
文件后缀:.txt

为了方便起见,文件标识常被称为文件名。但是我们要了解此时所称呼的文件名,实际上包括以上3部分内容,而不仅仅是文件名主干。

1) 文件名主干的命名规则遵循标识符的命名规则。
2)后缀用来表示文件的性质, 如:doc(Word生成的文件), txt(文本文件),dat(数据文件), c(C语言源程序文件)'cpp(C++源程序文件), for(FORTRAN语言源程序文件),pas(Pascal语言源程序文件),obj(目标文件), exe(可执行文件).ppt(电子幻灯文件), bmp(图形文件)等。
3)文件路径:分为相对路径和绝对路径
绝对路径:c : \\code\\test.txt
相对路径:test.txt


文件缓冲区

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


文件指针

缓冲文件系统中, 关键的概念是“文件类型指针”, 简称“文件指针”。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的。
该结构体类型是由系统声明的,取名为FILE。

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

struct _iobuf

	char* _ptr;
	int  _cnt;
	char* _base;
	int  _f1ag;
	int  _file;
	int _charbuf;
	int _bufsiz;
	char* _tmpfname;
;
typedef struct _iobuf FILE;

(注意:以上代码了解即可,有兴趣可自行研究) 不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。 下面我们可以创建─个FILE* 的指针变量:

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

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件


文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE* 的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC规定使用fopen函数来打开文件,fclose来关闭文件。

fopen

函数原型:FILE* fopen(const char* filename, const char* mode);
函数功能:Open a file.(打开文件)
返回类型:Each of these functions returns a pointer to the open
file.A null pointer value indicates an error.(如果打开成功,返回指向文件信息区的指针,如果返回失败,返回空指针NULL)
函数参数1:filename(文件名,实际上包括3部分内容,而不仅仅是文件名主干。如果文件路径未写,则默认本路径)
函数参数2:Type of access permitted(文件打开方式)

文件打开方式如下表:

注意:‘w’打开的时候如果有同名文件,该文件中的内容会被销毁

fclose

函数原型:int fclose(FILE* stream);
函数功能:Closes a stream(fclose) or closes all open streams(_fcloseall).(关闭文件)
返回类型:fclose returns 0 if the stream is successfully closed._fcloseall returns the total number of streams closed.Both functions return EOF to indicate an error(关闭文件成功返回0,关闭文件失败返回EOF(值为 - 1)来报错)
函数参数:Pointer to FILE structure(文件指针)

举例:
1)在项目工程所在文件路径下新建一个测试文件

2)测试代码

#include<stdio.h>
int main()

	//打开文件
	FILE* pf1 = fopen("test1.txt", "r");
	if (pf1 == NULL)
	
		perror("fopen");
		return 1;
	
	//写文件
	//......
	//关闭文件
	fclose(pf1);
	return 0;

3)文件打开结果

调整一下(为了进一步理解相对路径和绝对路径,这里的filename使用绝对路径的方式)

刚刚我们是以“读”的方式来打开文件,现在我们来尝试用“写”的方式来打开文件

#include<stdio.h>
int main()

	FILE* pf = fopen("file1.dat", "w");
	if (pf == NULL)
	
		perror("fopen");
		return 1;
	
	//写文件
	//... 
	printf("open file success!\\n");
	//关闭文件
	fclose(pf);

在我们刚刚的路径下是没有file1.dat这个文件的,因为我们用“w”的方式打开文件,所以会创建一个新的文件


现在我们来尝试将一些内容写入到打开的文件中:



流(stream)的概念

**输入输出是数据传送的过程, 数据如流水一样从一处流向另一处, 因此常将输入输出形象地称为流(stream), 即数据流。**流表示了信息从源到目的端的流动。在输入操作时, 数据从文件流向计算机内存, 在输出操作时, 数据从计算机流向文件(如打印机、磁盘文件)。文件是由操作系统进行统一管理的, 无论是用Word打开或保存文件, 还是C程序中的输入输出都是通过操作系统进行的。“流”是一个传输通道, 数据可以从运行环境(有关设备)流入程序中, 或从程序流至运行环境。

C语言把文件看作一个字符(或字节)的序列, 即由一个一个字符(或字节)的数据顺序组成。一个输入输出流就是一个字符流或字节(内容为二进制数据)流。
C的数据文件由一连串的字符(或字节)组成, 而不考虑行的界限, 两行数据间不会自动加分隔符,对文件的存取是以字符(字节)为单位的。输入输出数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制, 这就增加了处理的灵活性。
这种文件称为流式文件。

C语言程序,只要运行起来,就默认打开了三个流,类型均为FILE*
stdin-- - 标准输入流-- - 键盘
stdout-- - 标准输出流-- - 屏幕
stderr-- - 标准错误流-- - 屏幕

例如:

接下来我们进行读文件,将前面生成的文件file1.dat读出来

刚刚这种是从文件里面读,我们也可以从标准输入流stdin–键盘读

fgetc如果读取文件失败返回EOF(值为 - 1)

上面说的fputc\\fgetc都是一个个字符方式读取的,如果要用这两个函数来处理字符串的话,效率就会非常低,那么用没用类似于这两个函数,但是可以一行一行读取的函数呢?


这两个函数就是用来实现刚刚说的功能的。
举例

#include<stdio.h>
int main()

	FILE* pf = fopen("file2.dat", "w");
	if (pf == NULL)
	
		perror("fopen");
		return 1;
	
	//写文件 一行一行写
	fputs("Hello world", pf);
	//关闭文件
	fclose(pf);
	return 0;


如果想要写入的字符串换行,则需要在字符串内容中添加换行‘\\n’

读文件,读的时候会将‘\\’0放进去


前面进行的是字符的输入输出, 而实际上数据的类型是丰富的。大家已很熟悉用printf 函数和scanf 函数向终端进行格式化的输入输出, 即用各种不同的格式以终端为对象输入输出数据。其实也可以对文件进行格式化输入输出, 这时就要用fprintf函数和 fscanf函数, 从函数名可以看到, 它们只是在printf和 scanf的前面加了一个字母f。它们的作用与printf 函数和scanf函数相仿, 都是格式化读写函数。只有一点不同: fprintf 和 fscanf 函数的读写对象不是终端而是文件。
它们的一般调用方式为

fprintf(文件指针, 格式字符串, 输出表列);
fscanf(文件指针, 格式字符串, 输入表列);

例如:

fprintf(fp, "%d,%6.2f", i, f);

举例:假如我们要将一个结构体类型的数据写入到我们的文件中(一般输入数据也叫做读取数据,输出数据也叫做写入数据)

#include<stdio.h>
struct S

	char ch[10];
	int a;
	float f;
;
int main()

	struct S s =  "student",10,3.333333 ;
	//打开文件
	FILE* pf = fopen("file3.dat", "w");
	if (pf == NULL)
	
		perror("fopen");
		return 1;
	
	//写数据
	fprintf(pf, "%s %d %f", s.ch, s.a, s.f);
	//关闭文件
	fclose(pf);
	return 0;


现在我们将输出的结构体文件重新读入到结构体变量中:

#include<stdio.h>
struct S

	char ch[10];
	int a;
	float f;
;
int main()

	struct S s =  0 ;
	//打开文件
	FILE* pf = fopen("file3.dat", "r");
	if (pf == NULL)
	
		perror("fopen");
		return 1;
	
	//读数据
	fscanf(pf, "%s %d %f", s.ch, &(s.a), &(s.f));

	//打印数据
	printf("%s %d %f\\n", s.ch, s.a, s.f);
	//关闭文件
	fclose(pf);
	return 0;

当然我们也可以用fprintf,stdout是标准输出流,也就是屏幕

上面我们说谈到的输入和输出实际上都是以文本数据的形式,也就是ASCII码形式。

那么如何以二进制的形式读写呢?

在程序中不仅需要一次输入输出一个数据, 而且常常需要一次输入输出一组数据(如数组或结构体变量的值),C语言允许用fread函数从文件中读一个数据块, 用fwrite函数向文件写一个数据块。在读写时是以二进制形式进行的。在向磁盘写数据时, 直接将内存中一组数据原封不动、不加转换地复制到磁盘文件上, 在读入时也是将磁盘文件中若干字节的内容一批读入内存。

函数原型
fread(buffer,size, count, fp);
fwrite(buffer, size, count, fp);

1)buffer:是一个地址。 对fread来说, 它是用来存放从文件读入的数据的存储区的地址。 对fwrite来说,是要把此地址开始的存储区中的数据向文件输出(以上指的是起始地址)。
2)size : 要读写的字节数。
3)count : 要读写多少个数据项(每个数据项长度为size)。
4) fp : FILE类型指针。 在打开文件时指定用二进制文件, 这样就可以用fread和 fwrite函数读写任何类型的信息。

例如 :

fread(f, 4, 10, fp);

其中, f是一个float型数组名(代表数组首元素地址)。这个函数从fp所指向的文件读入10个4个字节的数据, 存储到数组f中。
举例:

#include<stdio.h>
struct S

	char ch[10];
	int a;
	float f;
;
int main()

	struct S s =  "student",10,3.333333 ;
	//打开文件
	FILE* pf = fopen("file3.dat", "w");
	if (pf == NULL)
	
		perror("fopen");
		return 1;
	
	//写数据
	fwrite(&s, sizeof(struct S), 1, pf);
	//关闭文件
	fclose(pf);
	return 0;

因为是以二进制形式写的,所以我们用文本编辑器去打开,实际上得到的是一个乱码,是无法直接看懂的。
我们可以将其放到vs中,用二进制编辑器去看,就可以得到二进制相关信息,

具体步骤如下:
(1)鼠标右键源文件,添加现有项,将file3.dat添加进来

(2)鼠标右键点击file3.dat文件,选择“打开方式”

(3)下拉到最底部,选择“二进制编辑器”

(4)打开后的效果如下:

除了这种方法外,我们还可以通过fread函数去看fwrite函数写的内容,因为两个都是以二进制数据为操作对象,所以用fwrite写的内容,fread肯定是看得懂的,比方说刚刚的例子,我们还可以采用这种方式:

#include<stdio.h>
struct S

	char ch[10];
	int a;
	float f;
;
int main()

	struct S s =  0 ;
	//打开文件
	FILE* pf = fopen("file3.dat", "r");
	if (pf == NULL)
	
		perror("fopen");
		return 1;
	
	//读数据
	fread(&s, sizeof(struct S), 1, pf);
	//打印数据
	printf("%s %d %f\\n", s.ch, s.a, s.f);
	//关闭文件
	fclose(pf);
	return 0;


对比一组函数:

scanf / fscanf / sscanf
printf / fprintf / sprintf

scanf与printf是一组-- - 从标准输入流(stdin) / 输出流(stdout)中输入 / 输出格式化的数据
fscanf与fprintf一组-- - 从所有流类型(包括标准输入流\\输出流,文件流等各种流)输入 / 输出格式化的数据
sscanf与sprintf一组-- - 从一个字符串中输入 / 输出格式化数据

函数原型
int sscanf(const char*
buffer, const char* format[, argument] …); int sprintf(char* buffer,
const char* format[, argument] …);

使用举例:

#include<stdio.h>
struct S

	char ch[15];
	int age;
	float f;
;
int main()

	//将一个结构体的数据转换成一个字符串
	struct S s =  "Hello_world",20,5.5 ;
	struct S temp =  0 ;
	char buffer[100] =  0 ;
	sprintf(buffer, "%s %d %f", s.ch, s.age, s.f);
	printf("%s\\n", buffer);
	//将buffer的字符串内容还原成一个结构体
	sscanf(buffer, "%s %d %f", temp.ch, &(temp.age), &(temp.f));
	printf("%s %d %f\\n", temp.ch, temp.age, temp.f);

	return 0;


文件随机读取

一般情况下, 在对字符文件进行顺序读写时, 文件位置标记指向文件开头, 这时如果对文件进行读的操作, 就读第1个字符, 然后文件位置标记向后移一个位置, 在下一次执行读的操作时, 就将位置标记指向的第⒉个字符读入。依此类推, 遇到文件尾结束。

可以根据读写的需要, 人为地移动文件位置标记的位置。文件位置标记可以向前移、向后移, 移到文件头或文件尾, 然后对该位置进行读写,
显然这就不是顺序读写了, 而是随机读写。

fseek函数

根据文件指针的位置和偏移量来定位文件指针。
函数原型:int fseek(FILE * stream,long int offset,int origin);
fseek(文件类型指针, 偏移量, 起始点)

起始点的选项有三个

举例:
(1)在源文件所在路径下创建test.txt,输入一串字符

(2)编写相应的打开文件及读文件的程序

#include<stdio.h>
int main()

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	
		perror("fopen");
		return;
	
	//读文件
	int ch = fgetc(pf);
	printf("%c\\n", ch);
	ch = fgetc(pf);
	printf("%c\\n", ch);
	ch = fgetc(pf);
	printf("%c\\n", ch);
	//关闭文件
	fclose(pf);
	return 0;

(3)运行结果

现在我们运用fseek函数对当前文件指针位置进行偏移,比如:

本来pf读了a之后,应该移动到b的位置,但是用fseek让其偏移 - 1,也就是向左偏移一个位置(一个字节),又回到了a。

当起始点为SEEK_END 的时候,只能向左(向前)偏移,也就是偏移量只能为负值
当起始点为SEEK_SET 的时候,只能向右(向后)偏移,也就是偏移量只能为正值


ftell函数

返回文件指针相对于起始位置的偏移量
函数原型:long int fte11(FILE * stream);


rewind

让文件指针的位置回到文件的起始位置
函数原型:void rewind(FILE * stream);

汇总相关代码:

#include<stdio.h>
int main()

	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	
		perror("fopen");
		return;
	

	int ch = fgetc(pf);
	printf("%c\\n", ch);
	//在当前位置进行偏移
	fseek(pf, -1, SEEK_CUR);
	ch = fgetc(pf);
	printf("%c\\n", ch);
	ch = fgetc(pf);
	printf("%c\\n", ch);

	//使用ftell来确定当前位置与起始点之间的偏移量
	int ret = ftell(pf);
	printf("%d\\n", ret);

	//让文件指针回到起始位置
	rewind(pf);
	ch = fgetc(pf);
	printf("%c\\n", ch);

	//关闭文件
	fclose(pf);
	return 0;


文件类型

根据数据的组织形式,数据文件可分为文本文件和二进制文件。
数据在内存中是以二进制形式存储的, 如果不加转换地输出到外存,就是二进制文件,
可以认为它就是存储在内存的数据的映像,所以也称之为映像文件(image file)。
如果要求在外存上以ASCII代码形式存储,
则需要在存储

nginx学习?nginx可以做什么?看完这篇你就懂了!

https://mp.weixin.qq.com/s/Viv9T1kSTePNI6O2xDzyRg 查看详情

c语言进阶文件数据操作详解(万字教你真正理解文件使用)(代码片段)

文件操作目录​​​​​​一、我们为什么需要文件?二、究竟什么才是文件程序文件数据文件三、文件名四、文件类型五、文件缓冲区六、文件指针七、打开文件和关闭文件fopenfclose八、文件顺序读写表流(stream)f... 查看详情

java中的集合(list,set,map)(知识点详解)(看完这篇就够了)

Java集合一、Java集合框架集合框架前序:集合框架概述(一)集合框架概述(二)(集合的使用场景)集合框架概述(三)(Collection接口继承树)集合框架概述(三)(Map接口继承树)二、Collection接口方法①Collection接口②Collection接口方法三、It... 查看详情

java的反射机制,看完这篇轻松应对高级框架(超详细总结)(代码片段)

...解反射注解 1.反射机制概述反射机制有什么作用?java语言中反射机制可以操作字节码文件。优点:可以直接读和修改字节码文件。通过反射机制可以操作class文件。反射在框架中使用很多,掌握它,你注定不凡。在java.lang.refle... 查看详情

看完这篇http,跟面试官扯皮就没问题了(代码片段)

我是一名程序员,我的主要编程语言是Java,我更是一名Web开发人员,所以我必须要了解HTTP,所以本篇文章就来带你从HTTP入门到进阶,看完让你有一种恍然大悟、醍醐灌顶的感觉。最初在有网络之前,我们... 查看详情

⭐redis分布式——主从复制sentinel集群彻底吃透⭐(看完这篇万字长文,你的redis水平将会上升一个层次)(代码片段)

...❤️《Redis精通系列》❤️上千人点赞收藏,全套Redis学习资料,大厂必备技能!目录一、主从复制1、简介2、主从复制的演进2.1版本2.8以前2.2版本2.8-4.02.3版本4.0二、Sentinel1、简介2、Sentinel初始化与网络连接2.1初始化Se... 查看详情

看完这篇操作系统,和面试官扯皮就没问题了(代码片段)

解释一下什么是操作系统操作系统是运行在计算机上最重要的一种软件,它管理计算机的资源和进程以及所有的硬件和软件。它为计算机硬件和软件提供了一种中间层通常情况下,计算机上会运行着许多应用程序,它们都需要对... 查看详情

c语言学习笔记(18)文件操作2(代码片段)

文章目录五.文件的随机读写fseekftellrewind六.文本文件和二进制文件七.文件读取结束的判定错误的使用feof拷贝一份文件八.文件缓冲区五.文件的随机读写fseek根据文件指针的位置和偏移量来定位文件指针。#include<stdio.h>intmain() ... 查看详情

看完这篇你还能不懂c语言/c++内存管理?(代码片段)

C语言内存管理指对系统内存的分配、创建、使用这一系列操作。在内存管理中,由于是操作系统内存,使用不当会造成毕竟麻烦的结果。本文将从系统内存的分配、创建出发,并且使用例子来举例说明内存管理不当... 查看详情

史上最全自然拼读法,看完这篇就够了

...,是自然形成的一种发音规则,作为英语母语国家的孩子学习英语读音和拼写的教学法,它主要教授英文字母(letter)与语音(sound)间的对应关系。现在瑞思学科英语小编吐血整理了史上最全自然拼读法的规则及用法,转给有... 查看详情

看完这篇不要告诉我不会封装antdesign弹框组件了(代码片段)

...易但是坚持一定很酷微信公众号关注前端小歌谣获取前端学习知识1设计需求封装一个弹框组件直接调用接口2技术栈antdesign+react设计第一步绘制样式<ModalmaskClosable=falsevisible=visibletitle=\'签收协议\'onOk=this.handleSignForonCancel=()=>thi... 查看详情

看完这篇不要告诉我不会封装antdesign弹框组件了(代码片段)

...易但是坚持一定很酷微信公众号关注前端小歌谣获取前端学习知识1设计需求封装一个弹框组件直接调用接口2技术栈antdesign+react设计第一步绘制样式<ModalmaskClosable=falsevisible=visibletitle=\'签收协议\'onOk=this.handleSignForonCancel=()=>thi... 查看详情

c语言进阶六.预处理

 (1)程序的翻译环境和执行环境在ANSIC的任何一种实现中,存在两个不同的环境。第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。包含编译加链接第2种是执行环境,它用于实际执行代码。(2)详解编译... 查看详情

mac上的爬虫软件怎么选?看完这篇就够了

...能在MAC上使用,因此今天这篇文章我们单独介绍一下在MAC操作系统中有哪些好用的爬虫软件,给大家做一个参考。 还是先说结论吧,赶时间的同志可以看完就闪人了,有两种选择方案:1、免费不花钱,不需要积分的注意:... 查看详情

看完这篇还不懂hashmap的扩容机制,那我要哭了~(代码片段)

HashMap发出的Warning:这是《Java程序员进阶之路》专栏的第56篇。那天,小二垂头丧气地跑来给我诉苦,“老王,有个学弟小默问我‘HashMap的扩容机制’,我愣是支支吾吾讲了半天,没给他讲明白,讲到... 查看详情

c语言基础学习笔记+c语言进阶学习笔记总结篇(坚持才有收获!)

【C语言基础学习笔记系列】【C语言基础学习笔记】一、初始C语言(1)【C语言基础学习笔记】一、初始C语言(2)【C语言基础学习笔记】一、初始C语言(3)【C语言基础学习笔记】一、初始C语言(总... 查看详情

看完这篇自己都可以写springioc容器bean对象实例化--乐字节java

...口,提供获取bean方法定义Bean工厂接口实现类,解析配置文件,实例化Bean对象实现获取Bean方法定义Bean属性对象packagecom.xxxx.spring;/***bean对象*用来接收配置文件中bean标签的id与class属性值*/publicclassMyBean{privateStringid;//bean对 查看详情

rtsp协议分析——看完这篇直接毕业

http://blog.csdn.net/bytxl/article/details/50407413 版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+]   1.概述: RTSP(RealTimeStreamingProtocol),实时流传输协议,是TCP/IP协议体系中的一个应用层协议... 查看详情