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

相关文章

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

GO语言短变量声明的实现示例

《GO语言短变量声明的实现示例》在Go语言中,短变量声明是一种简洁的变量声明方式,使用:=运算符,可以自动推断变量类型,下面就来具体介绍一下如何使用,感兴趣的可以了解一下... 目录基本语法功能特点与var的区别适用场景注意事项基本语法variableName := value功能特点1、自动类型推

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作

Go语言使用Gin处理路由参数和查询参数

《Go语言使用Gin处理路由参数和查询参数》在WebAPI开发中,处理路由参数(PathParameter)和查询参数(QueryParameter)是非常常见的需求,下面我们就来看看Go语言... 目录一、路由参数 vs 查询参数二、Gin 获取路由参数和查询参数三、示例代码四、运行与测试1. 测试编程路

Go语言使用net/http构建一个RESTful API的示例代码

《Go语言使用net/http构建一个RESTfulAPI的示例代码》Go的标准库net/http提供了构建Web服务所需的强大功能,虽然众多第三方框架(如Gin、Echo)已经封装了很多功能,但... 目录引言一、什么是 RESTful API?二、实战目标:用户信息管理 API三、代码实现1. 用户数据

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.

Go语言使用sync.Mutex实现资源加锁

《Go语言使用sync.Mutex实现资源加锁》数据共享是一把双刃剑,Go语言为我们提供了sync.Mutex,一种最基础也是最常用的加锁方式,用于保证在任意时刻只有一个goroutine能访问共享... 目录一、什么是 Mutex二、为什么需要加锁三、实战案例:并发安全的计数器1. 未加锁示例(存在竞态)

C语言自定义类型之联合和枚举解读

《C语言自定义类型之联合和枚举解读》联合体共享内存,大小由最大成员决定,遵循对齐规则;枚举类型列举可能值,提升可读性和类型安全性,两者在C语言中用于优化内存和程序效率... 目录一、联合体1.1 联合体类型的声明1.2 联合体的特点1.2.1 特点11.2.2 特点21.2.3 特点31.3 联合体的大小1

MySQL中读写分离方案对比分析与选型建议

《MySQL中读写分离方案对比分析与选型建议》MySQL读写分离是提升数据库可用性和性能的常见手段,本文将围绕现实生产环境中常见的几种读写分离模式进行系统对比,希望对大家有所帮助... 目录一、问题背景介绍二、多种解决方案对比2.1 原生mysql主从复制2.2 Proxy层中间件:ProxySQL2.3