当前位置: 首页 > news >正文

C语言---文件操作

为什么使用文件

        简单的一句话概括,当我们想要持久化的保存数据的时候,可以使用文件。硬盘(磁盘)上我们看到的那些文件就是我们这里说的文件,是一个东西。

文件的分类以及文件名

        从文件功能的角度给文件分类可以分为程序文件数据文件。程序文件就包括我们在用vs写代码时候创建的那种.c文件,目标文件(.obj),还有.exe可执行文件。数据文件就是我们程序在运行的时候要从中读/写的那个文件。(本博客说的均是此文件类型)

        从文件的组织形式给文件分类可以分为二进制文件文本文件。二进制文件说的就是数据以二进制的形式存储在文件当中。文本文件就是数据以ASCII码的形式存储。

假设现在有一个整数10000

如果以二进制的形式就是补码:00000000 00000000 00100111 00010000

而以ASCII码的形式就要每一个数字当作字符来存储,字符1的ASCII码为49,转成二进制就为00110001,而字符0的ASCII码为48,转化成二进制就是00110000,因此10000以ASCII码的形式存储就是00110001 00110000 00110000 00110000 00110000

        至于文件名只要知道它的组成形式,文件路径 + 文件名主干 + 文件后缀 就行。

Eg:c:\code\test.txt

文件的打开和关闭

        流

        对于一个可执行程序来说,它可以从多个渠道输入数据,比如从键盘,(硬盘)文件,网络......这些外部设备。同样的,它想输出数据的时候也有许多的渠道。那如果对不同的外部设备都整一套方式去输入输出数据就太繁琐了,因此,流就是帮助我们把所有的数据全部都到流里边,我们只需要操作流就可以了,至于流怎么把这些数据放到外部设备里边不是我们要关心的事情。注意每次操作流都需要打开。

        标准流

        为什么在C语言里边好像从来没有操作过流?因为C语言程序在启动的时候已经默认打开了3个标准流。

stdin(标准输⼊流): 在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。

stdout(标准输出流): 在⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。

stderr(标准错误流): 在⼤多数环境中输出到显⽰器界⾯。

        由于默认打开了这三个流,所以我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。 

        文件指针

        文件指针就是一个指向文件类型的指针,其实每当我们在使用文件的时候,每个被使用的文件都会在内存中开辟一块属于自己的文件信息区,每个文件信息区都是用来存放文件的相关信息的。文件信息区是一个结构体,编译环境给结构体重命名为FILE,文件指针变量就是指向这一个结构体,用于维护数据,类型就是FILE*,上边三个标准流就是这个类型。通过文件指针变量就可以间接找到与它相关的文件。我们用代码维护的就是文件信息区。

        文件操作

        对于文件的操作就如同从瓶子里取水一样,基本分为3步,打开文件,执行读/写操作,关闭文件。关于打开和关闭文件,分别需要两个函数fopenfclose。

        打开文件

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

第一个参数表示文件名,第二个参数表示的是打开方式

由于文件信息区是打开文件的时候就创建的,如果打开文件成功,就返回一个文件指针(也就是文件信息区的地址)

如果打开失败,就返回NULL

        关于打开方式,有下边的一张图(本篇博客就先了解这三种最简单的)

        其中注意“w”是表示当前打开的这个文件的目的是往里边写入数据,在我们写入数据的时候,它会覆盖掉原来的数据。而“a”就不会,它会追加数据。 

        关闭文件

int fclose ( FILE * stream );

形参就表示当前正在使用的文件对应的文件指针

如果关闭成功,返回值为0

如果关闭失败,就返回EOF,也就是-1

        代码示例

#include<stdio.h>
int main()
{//在当前代码所在的路径下边有一个test.txt的文件//注意:如果是当前代码所在路径,文件的路径可以省略,但如果不是当前路径,就不能省略FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");exit(1);}//读或者写操作//关闭文件fclose(pf);pf = NULL;return 0;
}注意1.
如果打开的文件不是当前代码所在路径底下的文件,就要把文件路径给加上
假设现在在我的电脑桌面上有一个文件叫test.txt,它的路径为 C:\Users\Maybe\Desktop(绝对路径)
FILE* pf = fopen("C:\\Users\\Maybe\\Desktop\\test.txt","r");//防止转义字符注意2.(仅作了解)
相对路径.    ---当前路径. .   ---上一级路径

文件的读写

        顺序读写

顺序读写函数介绍:

函数名功能适⽤于
fgetc字符输⼊函数所有输⼊流(文件流/stdin)
fputc字符输出函数所有输出流(文件流/stdout)
fgets⽂本⾏(字符串)输⼊函数所有输⼊流
fputs⽂本⾏(字符串)输出函数所有输出流
fscanf格式化(各种数据类型)输⼊函数所有输⼊流
fprintf格式化(各种数据类型)输出函数所有输出流
fread⼆进制输⼊⽂件输⼊流
fwrite⼆进制输出⽂件输出流

        关于这些函数的形参和实参,都可以去cplusplus.com - The C++ Resources Network查看,本篇博客下边就直接给出操作以及注意事项。

        在了解这些函数之前,先来看一看关于读和写(都是针对程序而言)

        fgetc和fputc代码示例

#include<stdio.h>
int main()
{//向文件里边写数据,以"w"的形式打开文件,该文件就是当前我代码路径底下的文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");exit(1);}//向文件里边写入26个英文字母for (int i = 'a'; i <= 'z'; i++){fputc(i, pf);}//关闭文件fclose(pf);pf = NULL;//以读的方式打开文件,获取里边的数据FILE* pf2 = fopen("test.txt", "r");if (pf2 == NULL){perror(fopen);exit(1);}//从文件里读数据,返回字符的ASCII码值//由于fgetc函数在向文件里边读取文件失败或者读到文件末尾之后会返回EOF//并且分别会设置读取失败和遇到文件末尾的状态值int ch = 0;while ((ch = fgetc(pf2)) != EOF)//如果函数的返回值不等于EOF,说明读取成功{printf("%c ", ch);}//关闭文件fclose(pf2);pf2 = NULL;return 0;
}

        注意上边代码里边的循环体,由于fgetc在文件里边读取字符的时候会默认有一个光标指向所要读取的第一个字符,你可以简单理解为下标为0的字符的地址处,每当fgetc读取完一个字符之后,光标就会自动的往后移动。所以就实现了循环读取,但遇到返回EOF的时候,读取就结束了。

        fputs和fgets的代码示例

#include<stdio.h>
int main()
{//打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");exit(1);}fputs("hello world", pf);//关闭文件fclose(pf);pf = NULL;//以读的形式打开文件FILE* pf2 = fopen("test.txt", "r");if (pf2 == NULL){perror("fopen");exit(1);}从文件里边读一个字符串对于fgets函数有三个形参第一个参数表示一个C风格的字符串str(就是“ ”里边的字符串),用字符数组存储,表示从文件里边读取的字符串就拷贝到里边去第二个参数表示最多要读取num个字符到str里边,注意它会给 '\0' 留一个位置,也就是实际读取的字符数是num - 1个第三个参数就是一个文件指针,表示你想从哪个文件里边去读取数据。还有一个要注意的点就是如果我要读取20个字符,但文件里边的字符有换行的情况那么这时fgets读到 '\n' 就停止了,并且'\n'后边也会带上'\0'最后一种情况就是读到文件末尾会自动停下来char str[12] = { 0 };fgets(str, 12, pf2);printf(str);//关闭文件fclose(pf2);pf2 = NULL;return 0;
}

        fscanf和fprintf的代码示例 

#include<stdio.h>
int main()
{//以写的形式打开文件FILE* pf = fopen("test.txt", "w");if (pf == NULL){perror("fopen");return 1;}//写法1//double a = 1.1;//int b = 10;//char str[] = "xxc";//fprintf(pf, "%lf\n %d\n %s\n", a, b, str);//写法2struct s{double a;int b;char str[20];};struct s ss = { 1.1,10,"xxc"};fprintf(pf, "%lf %d %s", ss.a, ss.b, ss.str);//关闭文件fclose(pf);pf = NULL;//以读的形式打开文件FILE* pf2 = fopen("test.txt", "r");if (pf2 == NULL){perror("fopen");return 1;}double e = 0;int f = 0;char g[10];fscanf(pf2, "%lf %d %s", &e, &f, g);printf("%lf %d %s", e, f, g);//关闭文件fclose(pf2);pf2 = NULL;return 0;
}

        关于上边这两个函数其实跟printf和scanf的写法是一样的,注意第一个参数是你想要读和写的文件就行了。  

        对于以上提到的所有操作文件读/写的函数都是针对所有的输入/输出流的,这就意味着如果把这些函数的FILE* stream这一个形参改为stdin/stdout,其实这些函数就变成了针对屏幕操作了,可以参考scanf/printf函数。(就以fprintf和fscanf举例)

struct s
{double a;int b;char str[20];
};#include<stdio.h>
int main()
{//当这么写的时候,其实和scanf和printf没有什么区别了struct s ss = { 0 };fscanf(stdin, "%lf %d %s", &ss.a, &ss.b, ss.str);fprintf(stdout, "%lf %d %s", ss.a, ss.b, ss.str);return 0;
}

        对比几组函数

        对比scanf/printf,fscanf/fprintf,sscanf/sprintf这三组函数的区别

对于scanf和printf,它们就是针对标准输入/输出的格式化输入/输出函数。

对于fscanf和fprintf,它们就是针对所有输入/输出的格式化输入/输出函数。

        sscanf和sprintf:

        sprintf可以把格式化的数据转化成一个字符串,而sscanf就是从字符串里边读取格式化的数据。就是将字符串转化成格式化的数据。

        代码示例:

#include<stdio.h>
struct s
{double a;int b;char c[20];
};int main()
{char str[30] = { 0 };struct s ss = { 1.1,1,"xxc" };sprintf(str, "%lf %d %s", ss.a, ss.b, ss.c);printf("%s\n", str);struct s ss2 = { 0 };sscanf(str, "%lf %d %s", &(ss2.a), &(ss2.b), ss2.c);printf("%lf %d %s", ss2.a, ss2.b, ss2.c);return 0;
}

        再强调一个点,就是本篇博客的所有输入输出都是以程序的角度输出,如果是标准输入输出流,输入就指的从键盘输入到程序里边,输出就指的从程序到屏幕,而文件流的输入输出,输入就是文件到程序,输出就是程序到文件。而sscanf/sprintf的输入输出就是对字符串的。

        fread和fwrite:

        这两个函数只针对文件流,而且是二进制文件。

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream )
fwrite就是写count个大小为size的数据到文件里边去,其中数据来自数组(ptr)。

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

fread就是从文件里边读count个大小为size的数据到数组ptr里边去。

        代码示例:

#include<stdio.h>
int main()
{//以二进制的形式写文件FILE* pf = fopen("test.txt", "wb");if (pf == NULL){perror("fopen");return 1;}//int arr[] = { 1,2,3,4,5 };fwrite(arr, sizeof(arr[0]), 5,pf);//也可以一次性将数组里的元素写进文件里边//fwrite(arr,20,1,pf);//关闭文件fclose(pf);pf = NULL;//以二进制的形式读文件FILE* pf2 = fopen("test.txt", "rb");if (pf2 == NULL){perror("fopen");return 1;}//int ar[5] = { 0 };fread(ar, sizeof(int), 5, pf2);for (int i = 0;i < 5; i++){printf("%d ", ar[i]);}//关闭文件fclose(pf2);pf2 = NULL;return 0;
}

        随机读写(fseek,ftell,rewind)

        fseek叫定位文件指针(文件内容的光标),它可以修改读取文件的时候当前的偏移量,请看下边的截图。

        左边的abcdef就是文件test.txt里边的内容,而右边红色框出的fseek的三种写法都是对的,最终的运行结果都是打印ad。

一开始从文件里边读了字符a,并且通过fputc打印到屏幕上。接着用fseek改变偏移量找到字符d,最后用fputc打印。

这里说的偏移量可以简单理解为跟指针差不多,在还没有读任何数据的时候偏移量为0,指向字符a,读取了字符a并打印之后偏移量就变成了1,指向了字符b

///

关于fseek的参数,大致的意思就是从(第三个形参)的位置偏移(第二个形参)个偏移量。具体可以去看看cplusplus网站。

        ftell就是用于返回文件指针相较于起始位置的偏移量。

        rewind可以让文件指针返回起始的位置,也就是偏移量为0的位置。

        完整代码

#include<stdio.h>int main()
{//以读的形式打开文件FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}int ch = fgetc(pf);fputc(ch, stdout);fseek(pf, 3, SEEK_SET);/*fseek(pf, 2, SEEK_CUR);fseek(pf, -3, SEEK_END);*/ch = fgetc(pf);fputc(ch, stdout);//注意ftell的返回值为 long intlong int n = ftell(pf);printf("%ld\n", n);rewind(pf);ch = fgetc(pf);fputc(ch, stdout);//关闭文件fclose(pf);pf = NULL;return 0;
}
http://www.dtcms.com/a/316113.html

相关文章:

  • 上传文件至华为云OBS
  • 分布式微服务--Nacos 集群部署
  • 【CTF】命令注入绕过技术专题:变量比较与逻辑运算
  • Spring Boot 整合 Thymeleaf
  • 【qt5_study】1.Hello world
  • 中国地级及以上城市人均GDP数据集(1990-2022年)
  • 【运动控制框架】WPF运动控制框架源码,可用于激光切割机,雕刻机,分板机,点胶机,插件机等设备,开箱即用
  • 37.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--增加Github Action
  • 400V降24V,200mA,应用领域:从生活到工业的 “全能电源管家”
  • Windows 11 使用Windows Hello使用人脸识别登录失败,重新录入人脸识别输入PIN后报Windows Hello安装程序白屏无响应的问题解决
  • LeetCode347.前K个高频元素(hash表+桶排序)
  • scikit-learn工具介绍
  • 五十、【Linux系统shell脚本】case语句 、 函数及中断控制演示
  • kafka部署集群模式
  • 力扣-128.最长连续序列
  • # Kafka 消费堆积:从现象到解决的全链路分析
  • AI智能体开发流程与产品设计
  • Java商城开发的难点与解决方案
  • ShapeLLM-Omni 论文解读
  • JVM(Java Virtual Machine,Java 虚拟机)超详细总结
  • 《Linux编译器:gcc/g++食用指南》
  • 【Golang】本地缓存go-cache
  • 前端实用工具方法 —— 持续更新中...
  • 暑期算法训练.14
  • 朴素贝叶斯(Naive Bayes)算法详解
  • 前端实现大模型流式响应方案
  • 播放器音频后处理实践(一)
  • LeetCode——2683. 相邻值的按位异或
  • 3. 为什么 0.1 + 0.2 != 0.3
  • Physics Simulation - UE中Projectile相关事项