关于文件操作---C语言

news2024/9/22 19:32:44

引言

关于文件,想必大家或多或少都会有些了解,文件可以帮我们储存数据,不同格式的文件可以储存不同类型的数据,也可以将文件中的数据用不同的方式打开。电脑中的文件,是放在硬盘上的。在我们编写代码并运行的时候,如果没有文件,我们写的程序数据只会在电脑内存中,一旦我们退出程序,内存便会回收,数据会丢失,为了将数据持久化,我们可能需要使用文件。

关于文件

在我们的程序设计中,分为两种文件,一个是程序文件(用来存放运行代码,如:test.c,test.obj),另一个是数据文件(用来存放程序运行时读写的数据)。

其中,数据文件还可以分为:

1.文本文件:以ASCII字符形式存储

2.二进制文件:以二进制形式存储

每一个文件都有一个唯一表示而标识分为三部分:

文件路径 + 文件名主干 + 问价后缀

eg:C:\code\test.exe

关于文件操作

这里先给大家举个例子吧

#include<stdio.h>
int main()
{
	int a = 10000;
	FILE* pf = fopen("text.txt", "wb");//以二进制只写方式打开文件text.txt
	fwrite(&a, 4, 1, pf);//以二进制形式写入a
	fclose(pf);//关闭pf指向文件
	pf = NULL;//指针制空,避免野指针
	return 0;
}

根据以上这一份代码,目前不需要完全看懂,这时你只需要了解到操作文件的一个基本的流程

操作文件流程:

1.打开文件

2.读/写文件

3.关闭文件 

关于文件的操作,还引入了流这个概念。那么流到底是什么呢?其实你可以从字面上去理解它,就是一条小河,不过里面流动的不是水,而是数据,流作为一个连接程序运行和外部设备的一个中间媒介,起着传输数据的作用。有了流,程序在运行中才能读取键盘输入或文件中保存的数据;有了流,才能将程序运行时产生的数据打印到屏幕上或写到文件中。

将这一概念抽象成图是这样的:

流这个中间媒介是抽象出来的,具体的底层实现我们可以先不了解,但是我们需要了解如何正确打开和使用流。

我相信能看到文件操作读者以及对C语言有了相当的了解,可能你会问,为什么我们在之前写代码的时候直接就用scanf和printf函数了,没有见到打开和关闭流的操作啊?

那是因为,这个工作C语言系统已经事先帮我们做好了

在C语言程序中,默认打开三个流:

1.stdin:标准输入流

2.stdout:标准输出流

3.stderr:标准错误流

这几个流的类型都是FILE*(文件指针)

这又说到了文件指针

文件指针(文件类型指针)

文件在内存中有一个文件信息区,用来存放文件信息,这些信息被放在一个结构体变量中,这个结构体就是FILE

在C语言的Visual Studio 2013中,FILE是一个系统定义的结构体类型,用于表示一个文件的相关信息。在stdio.h头文件中,FILE结构体被定义为:

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


在这个结构体中,包含了文件名、文件状态和文件当前位置等信息。通过使用指向FILE结构体的指针,可以更加方便地对文件进行操作。

这个结构体格式也不需要大家背下来,只是便于大家理解FILE,不同C编译器中FILE内容不完全相同,但总的来说大同小异,只要最终实现的功能相同就没什么大问题。

我们在使用fopen打开文件的时候,将这一函数返回的FILE*类型的指针返回给pf。这样,在下次读写的时候,就可以根据pf指针来寻找需要读取和写入的文件了。

最后用完pf这一指针,也可以通过pf找到需要关闭的文件进行close操作。

文件的打开和关闭

fopen

FILE* fopen(const char* filename , const char* mode);

其中filename为文件名,mode为打开方式

关于文件打开方式mode:

  1. “r” 以"只读"的方式打开一个文本文件(只能读),同时所读的文件必须存在。
  2. “r+” 与"r"的区别在于可以"写"(只能写)。
  3. “rb” 打开一个二进制文件(只能读),同时所读的文件必须存在。
  4. “rb+” 与"rb"的区别在于可以"写",同时也可以读。
  5. “w” 以"写"的方式创建一个文本文件,如果这个文件不存在,将创建一个此文件名的文件写入;如果存在,则将原文件内容清空并覆盖。
  6. “w+” 与"w"的区别在于,增加了"读"。
  7. “wb” 以"写"的方式创建一个二进制文件。
  8. “wb+” 与"wb"的区别在于,增加了"读"。
  9. “a” 以"尾部追加"的方式打开一个文本文件(只能写)。
  10. “a+” 与"a"的区别在于,增加了"读"。
  11. “ab” 以"尾部追加"的方式打开一个二进制文件(只能写)。
  12. “ab+” 与"ab"的区别在于,增加了"读"。

下面写一份代码观察一下

#include<stdio.h>
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL) {//检查文件是否正常打开
		perror("fopen");//如没有正常打开,打印错误原因
		return 1;
	}
	//写文件
	//。。。
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

上述代码便是用只写的方式打开的

下面讲一点拓展知识,在你实际操作的过程中,也许你注意到data.txt的路径是在当前文件下的,但是,如果你想操作的文件不再当前路径或者想生成的文件不再当前路径怎么办呢?这时候,就可以使用我们的相对路径。

在文件操作中 表示当前目录,而 ..  表示上一级路径

比如说:./../../data.txt 表示的便是返回上两级路径。

文件的顺序读写和随机读写

文件的顺序读写

和gets,scanf和printf等函数一样,文件的读写也有其对应的函数,下面给大家列出来,然后两两讲解

1.fgetc和fputc

2.fgets和fputs

3.fscanf和fprintf

4.fread和fwrite

前三组可针对所有输入输出流

最后一组只能针对二进制文件输入输出

fgetc和fputc

int fgetc(FILE* stream);

int fputc(int charater,FILE* stream);

为了方便,我们先讲fputs,下面见代码

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	//写文件
	for (int i = 0; i < 26; i++) {
		fputc('a' + i, pf);
		fputc('\n', pf);
	}
	fclose(pf);
	pf == NULL;
	return 0;
}

可以在代码里运用fput来往文件中写字符 ,以下是写入文件中的结果

然后就是fgetc,大家应该就能猜到其作用了,这个函数可以从文件中一个个读取字符,每读取一个字符光标往后移动一位,见具体使用代码。

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);
	printf("%c\n", ch);
	fclose(pf);
	pf = NULL;
	return 0;
}

读取上次在fputc的时候写入的数据,就可以通过data.txt文件打印出a

下面展示一份可以将一个文件中的内容转移到另一个文件中的代码,也是关于,fgetc和fputc的运用

#include<stdio.h>
int main()
{
	FILE* pfread = fopen("data1.txt", "r");
	if (pfread == NULL) {
		perror("fopen->data1.txt");
		return 1;
	}
	FILE* pfwrite = fopen("data2.txt", "w");
	if (pfwrite == NULL) {
		perror("fopen->data2.txt");
		return 1;
	}
	char ch;
	while ((ch = fgetc(pfread)) != EOF)fputc(ch, pfwrite);
	fclose(pfwrite);
	fclose(pfread);
	pfread = pfwrite = NULL;
	return 0;
}

 

其中data1.txt中的文件是我随意粘上去的,最后全部写入了data2.txt中,这个代码功能还是很强大的。

fgets和fputs

char* fgets(char* str,int num,FILE* stream);读字符串(最多读num个包含'\0')

int fputs(const char* str,FILE* stream);写字符串(一行)

其中,str表示的就是字符串的读入和写出

#include<stdio.h>
int main()
{
	FILE* pfread = fopen("data1.txt", "r");
	if (pfread == NULL) {
		perror("fopen->data1.txt");
		return 1;
	}
	FILE* pfwrite = fopen("data2.txt", "w");
	if (pfwrite == NULL) {
		perror("fopen->data2.txt");
		return 1;
	}
	char arr[20];
	fgets(arr, 19, pfread);
	fputs(arr, pfwrite);
	fclose(pfwrite);
	fclose(pfread);
	pfread = pfwrite = NULL;
	return 0;
}

上述代码是将从data1.txt中读到的一行字符写入data2.txt中。

 fscanf和fprintf

int fprintf(FILE* stream,const char* format……);

int fscanf(FILE* stream,const char* format……);

在我看来,这两个函数是我介绍的io函数中功能相对强大的,这两个函数的功能与printf和scanf极其相似,只是多了前面的stream文件指针,可以见一下下面的代码见见其功能

#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct Stu s = {0};
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL) {
		perror("fopen");
		return 1;
	}
	FILE* pfw = fopen("data1.txt", "w");
	if (pfw == NULL) {
		return 1;
	}
	fscanf(pf, "%s %d %f", s.name, &s.age, &s.score);
	fprintf(pfw, "%s %d %.1f", s.name, s.age, s.score);
	fclose(pf);
	pf == NULL;
	return 0;
}

 

在运行结束后data.txt中的内容就成功写道data1.txt中去了 。

fread和fwrite

size_t fread(void* ptr,size_t size,size_t count,FILE* stream);

size_t fwrite(void* ptr,size_t size,size_t count,FILE* stream);

第一个参数代表位置,第二个代表大小,第三个代表个数,第四个是文件指针

下面来看代码使用

#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};
int main()
{
	struct Stu s = {0};
	FILE* pf = fopen("data.txt", "rb+");
	if (pf == NULL)return 1;
	fread(&s, sizeof(s), 1, pf);
	printf("%s %d %.1f", s.name, s.age, s.score);
	fwrite(&s, sizeof(s), 1, pf);
	fclose(pf);
	pf = NULL;
	return 0;
}

最后就可以以二进制的形式将文件写入另一个文件了,只不过由于是文本文件打开的原因,其解码形式是文本文件的形式,所以数字打印出来的码是看不懂的。

文件的随机读写

文件的随机读写,意思不是随机的读写文件中的数据,而是可控的控制光标读写入和读取文件中的数据。相关的函数有,fseek,ftell和rewind。

fseek

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

其中stream表示的是文件指针,offset表示的是光标偏移量,origin可以控制光标的起始位置

ftell

long int ftell(FILE* stream);

stream表示文件指针

rewind

void rewind(FILE* stream);

stream表示文件指针

使用见代码

#include<stdio.h>
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL) {
		return 1;
	}
	fseek(pf, -4, SEEK_CUR);//从当前光标向前移动4位
	fseek(pf, -6, SEEK_END);//从末尾光标向前移动6位
	fseek(pf, 0, SEEK_SET);//将光标移到初始位
	int n = ftell(pf);//返回光标相对起始位置的偏移量
	rewind(pf);//将文件指针返回起始位置
	fclose(pf);
	pf = NULL;
	return 0;
}

 关于对文件读取结束的判定

错误使用feof:不能用其返回值来判断文件是否结束,feof是用来判断文件是否是因为文件正常读到文件尾而结束。

判断是否结束可以判断(EOF)fgetc,或(NULL)fgets

fread函数返回值是其读取元素个数

文件缓冲区

ANSIC采用“缓冲区系统”处理数据文件

系统自动为程序每一个正在使用的文件开辟一块“文件缓冲区”

刷新缓冲区函数

fflush(FILE* stream);

注:fclose也有刷新缓冲区的效果 

因为有缓冲区的存在,C语言在操作文件的时候,需要刷新缓冲区或者在文件操作结束时关闭文件。如果不做,可能会导致读写文件相关的问题。

结语

今天分享了关于文件操作相关的知识,创作不易,如果觉得本篇博客内容还不错的话,还请点小小的赞再走,也可以收藏以下本篇博客,如果感兴趣的话还可以给我点个关注,后续还会分享更多有意思的内容---比心♥

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1316124.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

KAKFA实践零碎记录

这里写目录标题 1 内存泄露2 生产者报错 1 内存泄露 错误信息 反复执行&#xff1a;创建消费者->关闭消费者后&#xff0c;内存缓慢上升且GC不能回收内存 错误原因 关闭消费者需要执行KafkaConsumer#close()函数 public void close() {this.close(Duration.ofMillis(30000…

12.12_黑马数据结构与算法笔记Java

目录 079 优先级队列 无序数组实现 080 优先级队列 有序数组实现 081 优先级队列 堆实现 1 082 优先级队列 堆实现 2 083 优先级队列 堆实现 3 084 优先级队列 e01 合并多个有序链表1 084 优先级队列 e01 合并多个有序链表2 085 阻塞队列 问题提出 086 阻塞队列 单锁实…

double DQN 跑 Pendulum-v1

gym-0.26.1 Pendulum-v1 环境详细信息 double DQN 实验环境 是为了体现 double DQN对高估的缓解, 因为 Pendulum-v1 reward最大是为0&#xff0c;可以有明显的对比。 相关论文 Deep Reinforcement Learning with Double Q-Learning 对动手深度强化学习里的代码做了一些修改。…

Java连接数据库实现用户登录和注册功能

目录 需求内容如下 示例代码 数据库studb Java代码 效果图 需求内容如下 1&#xff0c;创建数据库studb 2&#xff0c;库中添加用户表userinfo,包含如下字段 用户id ,用户名&#xff0c;用户密码&#xff0c;用户权限 &#xff08;数据类型和约束自己定义&#xff09…

SpringBoot2—开发实用篇2

目录 数据层解决方案 SQL NoSQL SpringBoot整合Redis SpringBoot整合MongoDB SpringBoot整合ES 数据层解决方案 SQL 回忆一下之前做SSMP整合的时候数据层解决方案涉及到了哪些技术&#xff1f;MySQL数据库与MyBatisPlus框架&#xff0c;后面又学了Druid数据源的配置&am…

TrustGeo代码理解(一)main.py

代码链接:https://github.com/ICDM-UESTC/TrustGeo 一、导入各种模块和数据库 # -*- coding: utf-8 -*- import torch.nnfrom lib.utils import * import argparse, os import numpy as np import random from lib.model import * import copy from thop import profile imp…

文本聚类——文本相似度(聚类算法基本概念)

一、文本相似度 1. 度量指标&#xff1a; 两个文本对象之间的相似度两个文本集合之间的相似度文本对象与集合之间的相似度 2. 样本间的相似度 基于距离的度量&#xff1a; 欧氏距离 曼哈顿距离 切比雪夫距离 闵可夫斯基距离 马氏距离 杰卡德距离 基于夹角余弦的度量 公式…

【从零开始学习JVM | 第七篇】深入了解 堆回收

前言&#xff1a; Java堆作为内存管理中最核心的一部分&#xff0c;承担着对象实例的存储和管理任务。堆内存的高效使用对于保障程序的性能和稳定性至关重要。因此&#xff0c;深入理解Java堆回收的原理、机制和优化策略&#xff0c;对于Java开发人员具有重要的意义。 本文旨在…

竞赛保研 python图像检索系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python图像检索系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c…

Python基础04-数据容器

零、文章目录 Python基础04-数据容器 1、了解字符串 &#xff08;1&#xff09;字符串的定义 字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。<class ‘str’>即为字符串类型。一对引号…

计算机毕业设计 基于SpringBoot的二手物品交易管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【一文带你掌握Java中方法定义、调用和重载的技巧】

方法的定义和调用 方法的定义 方法&#xff08;method&#xff09;是一段用于实现特定功能的代码块&#xff0c;类似于其他编程语言中的函数&#xff08;function&#xff09;。方法被用来定义类或类的实例的行为特征和功能实现。方法是类和对象行为特征的抽象表示。方法与面向…

『PyTorch』张量和函数之gather()函数

文章目录 PyTorch中的选择函数gather()函数 参考文献 PyTorch中的选择函数 gather()函数 import torch a torch.arange(1, 16).reshape(5, 3) """ result: a [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12],[13, 14, 15]] """# 定义两个index…

圆通速递查询,圆通速递单号查询,一键复制查询好的物流信息

批量查询圆通速递单号的物流信息&#xff0c;并将查询好的物流信息一键复制出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 圆通速递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册&…

002.Java实现两数相加

题意 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示两数之和的新链表。 示例 输入&#xff1a;l1[2,4,3],l2[5,6,4] 输出…

ChatGPT4 Excel 高级复杂函数案例实践

案例需求: 需求中需要判断多个条件进行操作。 可以让ChatGPT来实现这样的操作。 Prompt:有一个表格B2单元格为入职日期,C2单元格为员工等级(A,B,C),D2单元格为满意度分数(1,2,3,4,5)请给入职一年以上,员工等级为A级并且满意度在3分以上的人发4000元奖金,给入…

普冉(PUYA)单片机开发笔记(10): I2C通信-配置从机

概述 I2C 常用在某些型号的传感器和 MCU 的连接&#xff0c;速率要求不高&#xff0c;距离很短&#xff0c;使用简便。 I2C的通信基础知识请参见《基础通信协议之 IIC详细讲解 - 知乎》。 PY32F003 可以复用出一个 I2C 接口&#xff08;PA3&#xff1a;SCL&#xff0c;PA2&a…

计算机组成原理-函数调用的汇编表示(call和ret指令 访问栈帧 切换栈帧 传递参数和返回值)

文章目录 call指令和ret指令高级语言的函数调用x86汇编语言的函数调用call ret指令小结其他问题 如何访问栈帧函数调用栈在内存中的位置标记栈帧范围&#xff1a;EBP ESP寄存器访问栈帧数据&#xff1a;push pop指令访问栈帧数据&#xff1a;mov指令小结 如何切换栈帧函数返回时…

深度学习记录--矩阵维数

如何识别矩阵的维数 如下图 矩阵的行列数容易在前向和后向传播过程中弄错&#xff0c;故写这篇文章来提醒易错点 顺便起到日后查表改错的作用 本文仅作本人查询参考(摘自吴恩达深度学习笔记)

Flink系列之:SQL提示

Flink系列之&#xff1a;SQL提示 一、动态表选项二、语法三、例子四、查询提示五、句法六、加入提示七、播送八、随机散列九、随机合并十、嵌套循环十一、LOOKUP十二、进一步说明十三、故障排除十四、连接提示中的冲突案例十五、什么是查询块 SQL 提示可以与 SQL 语句一起使用来…