C语言-第九章:文件读写

2024-09-07 14:52
文章标签 语言 读写 第九章

本文主要是介绍C语言-第九章:文件读写,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

传送门:C语言-第八章:指针进阶

目录

第零节:准备工作

 第一节:文件的简单介绍

        1-1.绝对路径与相对路径

        1-2.文件的作用

第二节:文件操作

        2-1.打开文件

        2-2.关闭文件

        2-3.读操作

                2-3-1.fgetc 读取一个字符

                2-3-2.fgets 读取一行内容

                        2-3-3.fscanf 格式化读取数据

        2-4.写操作

                2-4-1.fputc 写入一个字符

                        文件缓冲区

                2-4-2.fputs 写入一个字符串

                2-4-3.fprintf 格式化写入数据

下期预告:


第零节:准备工作

        在学习文件的读写,我们先要把电脑中显示完整文件名(包括后缀)的功能打开,否则看到的文件名是不完整的:

        然后在新的项目里创建一个名为“text.txt”的文本文件,接下来我们将操作这个文件:

        

 第一节:文件的简单介绍

        1-1.绝对路径与相对路径

        我们在C语言中操作的文件和平时使用来记录文字的文件是同一个东西,平时在电脑上打开文件时,我们可能需要点进各种文件夹中,这个过程就是根据文件的路径找到文件,文件路径在windows的窗口中也有显示:

        它的路径就是"此电脑 \ Windows-SSD(C:) \ Windows \ addins",这种从最外面到最里面的路径叫做绝对路径 

        还有一种路径叫做相对路径,比如图片中我在 “addins” 文件夹下,它里面有一个文件:

        我就可以用 “./ FXSEXT.ecf” 找到这个文件,其中 “.” 代表当前文件。

        在C语言中,我们就是通过绝对路径或者相对路径找到文件的。

        1-2.文件的作用

        想想我们之前为什么使用文件,我们使用文件就是为了将信息记录下来,方便以后的阅读。

信息记录就需要向文件里写入数据,这就是文件的写操作;阅读就需要从文件读出数据,这就是文件的读操作

        在对文件进行读写之前,我们需要先打开文件,使用完文件后又需要关闭文件

        接下来将逐一介绍上述的文件操作。

第二节:文件操作

        2-1.打开文件

        打开文件需要用到 fopen 函数,所需头文件是<stdio.h>,函数原型如下:

        filename 表示文件名,包括文件的路径+文件主干+文件后缀;mode表示文件的打开方式;如果打开文件成功就会返回一个 FILE 类型的结构体指针(文件指针),指向的结构体存放了文件的信息,否则返回NULL。

        文件的打开方式见下图,目前只看 r、w、a 方式即可:

        还记得之前我们创建的文件 “text.txt” 吗,我们现在以读方式打开它:

#include <stdio.h>int main()
{FILE* pf = fopen(".\text.txt", "r"); // 相对路径 if (pf == NULL){perror("文件打开失败\n");return 0;}printf("文件打开成功\n");return 0;
}

  

        文件居然打开失败了,错误原因是fopen被传入了一个非法的参数,它的问题出在“.\text.txt”中,这是因为在C语言中 ‘\’ 不是单纯的右斜杠,而是转义字符,比如 换行符 '\n'、字符串的结束标志 '\0'

        '\' 会和它的下一个字符组成另一个字符,就是这个字符的意思被改变了,上述“.\text.txt”中的“\t” 就变成了另外一种字符。

        那么如何在C语言中表示 ‘\’ 呢?我们给 ‘\’ 转义即可,即 ‘\\’ 表示单右斜杠,所以在C语言中表示 ‘\’ 要用双斜杠:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r"); // 相对路径 if (pf == NULL){perror("文件打开失败\n");return 0;}printf("文件打开成功\n");return 0;
}

        用绝对路径打开文件时也要注意 ‘\’ 的转义作用:

#include <stdio.h>int main()
{FILE* pf = fopen("C:\\Users\\lyc53\\Desktop\\C语言代码\\C语言测试\\C语言测试\\text.txt", "r"); // 绝对路径,注意'\'的转义作用 if (pf == NULL){perror("文件打开失败");return 0;}printf("文件打开成功\n");return 0;
}

        2-2.关闭文件

        当对文件的操作结束时就可以关闭文件了,文件在打开它的程序结束时会关闭,我们也可以调用 fclose 函数关闭它,函数原型如下:

        stream 就是文件指针,fclose 会关闭它,关闭文件后就不能对文件进行任何的读写操作了。

        2-3.读操作

        文件里要有数据才能读出数据来,首先我们手动给文件加入一些数据:

                

        注意:在文件种存储的数据都是以字符的形式存储的,比如上述的 2024 就是字符串“2024”,而不是整数2024

        然后我们用读方式 (r) 打开,文件:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r"); // 读方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}return 0;
}

        读文件有许多方式,我们一个一个的介绍:

                2-3-1.fgetc 读取一个字符

        fgetc用于从文件中读取一个字符,函数原型如下:

        stream 就是要从哪个文件中读取,读取成功返回这个字符的ascll码值,读取失败返回文件结束符EOF(它实际上是-1,由define定义),以下是一个使用方法:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}char c;c = fgetc(pf);printf("%c\n",c);return 0;
}

        确实读到了字符 ‘2’,那如果我再次调用,它还会读到字符 ‘2’ 吗?请看以下案例:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}char c,d;c = fgetc(pf); // 第一次读取 d = fgetc(pf); // 第二次读取printf("%c\n", c);printf("%c\n", d);return 0;
}

        我们会发现它读取到了文件里的第二个字符。

        明明调用一样的函数,为什么结构不同呢?这是因为文件指针里有一个文件位置指示器,它刚开始是指向文件开头的,但是只要每读取1字节的数据,指针就会向后偏移1位。这个指针我们可以用其他函数任意调整它的位置,这个以后会说。

        接下来我们一直提取文件的数据,直到全部读完:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}char c;while (EOF != (c = fgetc(pf))) // 读到EOF说明是文件末尾了,不需要再读了{printf("%c", c);}return 0;
}

                2-3-2.fgets 读取一行内容

        除了一个一个的读取,我们还可以一次读取多个,fgets 函数的原型如下:

        str 就是接收读取内容的字符串,num 是读取个数,stream是被读取的文件,返回值是 str 的首元素地址,读取失败返回NULL,它的具体用法如下:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}char str[20];fgets(str, 4, pf); // 读取4个字符printf("%s\n", str);return 0;
}

        传入的读取个数是4,为什么只得到三个字符呢?这是因为C语言的字符串需要 ‘\0’ 作为结束标志,它也要占用一个位置,所以实际读取的个数是 3

        当读到EOF时,即使读取的字符数不足,也不会继续读取了:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}char str[20];fgets(str, 20, pf); // 读取4个字符printf("%s\n", str);return 0;
}

        文件里的字符数是12(空格也是一个字符),传入的读取数是19(加上一个'\0'),但是也不会读取一些奇奇怪怪的数据到 str 中,因为读到文件末尾读取就停止了。

                        2-3-3.fscanf 格式化读取数据

        我们之前常用的 scanf 函数就是从键盘读取数据到指定位置,而且可以用 %d、%c等格式化参数指定读取的数据类型,而 fscanf 是从文件读取数据到指定位置,它也可以使用格式化参数指定读取的数据类型,它的函数原型如下:

        stream 就是要读取的文件;format 就是读取的数据类型,. . .名为可变参数,即参数的数量是不定的,就像一个 scanf 可以同时改变一个变量或两个变量的值一样;函数的返回值是一个整数,返回读取到的字符数,这个函数的具体用法如下:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}char str[20];fscanf(pf,"%s",str); // 提取一个字符串printf("%s\n",str);return 0;
}

        我们发现它并没有读完所有内容,而是读到空格就结束了,这是这个函数的特性,它将空格换行符作为两个独立数据的分隔符,所以对于需要读取的数据,在存储时也要用空格分开。

        我们再来看看这个函数读取其他类型的数据的情况:

        (1)读取整型

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}int num;fscanf(pf,"%d",&num); // 提取一个整型printf("%d\n",num);return 0;
}

        我们发现它只会读取数字字符,当读取到非数字字符时,它就不读取了,然后把所有读到的数字字符转成整型。

        (2)读取字符

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "r");if (pf == NULL){perror("文件打开失败");return 0;}char c;fscanf(pf,"%c",&c); // 提取一个字符串printf("%c\n",c);return 0;
}

        我们发现它会把文件中的2作为字符看待,所以只读取了一个字符 '2'。

        所以对于相同的文件内容,不同的读取方式决定了函数对读取的态度。

        2-4.写操作

        进行写文件之前我们先把文件中的内容清空。

        我们以写方式(w)打开文件:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "w"); // 写方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}return 0;
}

                2-4-1.fputc 写入一个字符

        fputc 对标的是 fgetc,都是操作一个字符,函数原型如下:

         character:要写入的字符

        stream:被写入的文件

        返回值:如果写入成功,返回被写入字符;如果写入失败,返回EOF

        它的具体用法如下:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "w");if (pf == NULL){perror("文件打开失败");return 0;}fputc('a',pf); // 写入'a'return 0;
}

        执行完程序后我们打开文件,字符 'a' 就成功被写入了:

        那么如果我们把写入的字符换成 'b' 再次运行,文件的内容会是 ''ab'' 吗,请看执行结果:

        结果只有最新写入的 'b' ,这是因为以写方式(w)打开文件时,fopen 函数会清空文件内容,接下来我们再次以w方式打开文件,但是不写入任何内容,就会发现文件内容被清空了:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "w");if (pf == NULL){perror("文件打开失败");return 0;}return 0;
}

        如果我们不想清除文件内容,而是对文件内容进行追加,我们可以用追加方式(a)打开文件,它除了可以写入数据,还会把文件位置指示器指向文件末尾:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "a"); // 以追加的方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}fputc('a',pf); // 写入areturn 0;
}

        多次运行上述代码,文件的内容是:

        一共4个 'a' 说明上述代码运行了四次。

        现在我们清空文件内容,作下一个测试。

                        文件缓冲区

        我们以调试的方式运行文件,运行完 fputc 函数时就停止:

        fputc 函数以及执行完了,那么 'a' 应该已经写入文件了,但是此时文件内容仍然为空:

        这是为什么呢?这是因为在程序与文件之间还有一个文件缓冲区,程序所有的写入操作都是写入到文件缓冲区中,然后由文件缓冲区真正写入到文件中。

        那么文件缓冲区什么时候向文件中写入数据呢?

        (1)文件关闭时:

        程序结束时文件会关闭,也可以调用 fclose 函数让文件关闭:

        此时文件关闭了,我们看看文件里是否有内容了:

 

        不出所料,因为文件关闭了,字符 'a' 就被写到文件中了。

        (2) fflush 函数

        fflush 函数的作用是强制文件缓冲区向文件写入数据,又叫冲刷缓冲区,函数原型如下:

        stream:要冲刷的文件的缓冲区

        返回值:返回0表示冲刷成功;返回EOF表示冲刷失败

        它的用法如下:

        此时文件没有关闭,但是 fflush 函数以及调用完毕了, 此时文件已经有了写入的内容:

        那么为什么要有文件缓冲区呢?

        提高程序与文件的IO速度:实际上无论读写都要用到文件缓冲区,因为文件的位置在磁盘,距离CPU较远,交互慢。如果每次读取/写入数据都要访问一次文件就太慢了,所以把数据打包好(放入缓冲区),一次性全部处理好那么就只需要访问一次了,效率就大大提高了。

                2-4-2.fputs 写入一个字符串

        fputs 对标的是 fgets ,但是不能控制写入的字符个数,读取到 '\0' 时就结束写入,函数原型如下:

        str:要写入的数据 (字符串)

        stream:写入的文件

        返回值:成功返回一个非负值;失败返回EOF

        它的具体用法如下:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "w"); // 以写方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}char str[] = "Hello world";fputs(str, pf); // 写入字符串return 0;
}

  

                2-4-3.fprintf 格式化写入数据

        fprintf 对标 fscanf ,它可以将不同的数据组合成一个字符串写入文件中,函数原型如下:

        stream:被写入的文件

        format: 写入的数据类型

        . . .:可变参数,,不解释

        返回值:成功则返回写入的字符总数;失败就返回一个负数

        它的用法如下:

#include <stdio.h>int main()
{FILE* pf = fopen(".\\text.txt", "w"); // 以写方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}char name[] = "Eric";char ID[] = "202244567";int age = 18;fprintf(pf,"%s %s %d",name,ID,age); // 写入多个信息return 0;
}

        文件的内容是:

        我们还可以使用这个函数将结构体的数据存储在文件中:

#include <stdio.h>struct student
{char name[20];char ID[20];int age;
};int main()
{FILE* pf = fopen(".\\text.txt", "w"); // 以写方式打开文件if (pf == NULL){perror("文件打开失败");return 0;}// 学生信息struct student Jack = { "Jack","202212345",17 };struct student Bob = { "Bob","202212347",18 };struct student Steve = {"Steve","202212348",19};fprintf(pf, "%s %s %d\n", Jack.name, Jack.ID, Jack.age);fprintf(pf, "%s %s %d\n", Bob.name, Bob.ID, Bob.age);fprintf(pf,"%s %s %d\n", Steve.name, Steve.ID, Steve.age);return 0;
}

 

下期预告:

        下一次讲述的是文件位置指示器的操作,键盘、屏幕与文件(scanf 与 fscanf,printf 与 fprintf)的关系,文件的二进制操作

        

这篇关于C语言-第九章:文件读写的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

ShardingSphere之读写分离方式

《ShardingSphere之读写分离方式》:本文主要介绍ShardingSphere之读写分离方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录ShardingSphere-读写分离读写分离mysql主从集群创建 user 表主节点执行见表语句项目代码读写分

Go语言中使用JWT进行身份验证的几种方式

《Go语言中使用JWT进行身份验证的几种方式》本文主要介绍了Go语言中使用JWT进行身份验证的几种方式,包括dgrijalva/jwt-go、golang-jwt/jwt、lestrrat-go/jw... 目录简介1. github.com/dgrijalva/jwt-go安装:使用示例:解释:2. gi

Go 语言中的 Struct Tag 的用法详解

《Go语言中的StructTag的用法详解》在Go语言中,结构体字段标签(StructTag)是一种用于给字段添加元信息(metadata)的机制,常用于序列化(如JSON、XML)、ORM映... 目录一、结构体标签的基本语法二、json:"token"的具体含义三、常见的标签格式变体四、使用示例五、使用

Go语言使用slices包轻松实现排序功能

《Go语言使用slices包轻松实现排序功能》在Go语言开发中,对数据进行排序是常见的需求,Go1.18版本引入的slices包提供了简洁高效的排序解决方案,支持内置类型和用户自定义类型的排序操作,本... 目录一、内置类型排序:字符串与整数的应用1. 字符串切片排序2. 整数切片排序二、检查切片排序状态:

基于Go语言实现Base62编码的三种方式以及对比分析

《基于Go语言实现Base62编码的三种方式以及对比分析》Base62编码是一种在字符编码中使用62个字符的编码方式,在计算机科学中,,Go语言是一种静态类型、编译型语言,它由Google开发并开源,... 目录一、标准库现状与解决方案1. 标准库对比表2. 解决方案完整实现代码(含边界处理)二、关键实现细

如何合理管控Java语言的异常

《如何合理管控Java语言的异常》:本文主要介绍如何合理管控Java语言的异常问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、Thorwable类3、Error4、Exception类4.1、检查异常4.2、运行时异常5、处理方式5.1. 捕获异常

C语言中的常见进制转换详解(从二进制到十六进制)

《C语言中的常见进制转换详解(从二进制到十六进制)》进制转换是计算机编程中的一个常见任务,特别是在处理低级别的数据操作时,C语言作为一门底层编程语言,在进制转换方面提供了灵活的操作方式,今天,我们将深... 目录1、进制基础2、C语言中的进制转换2.1 从十进制转换为其他进制十进制转二进制十进制转八进制十进