目录
🌤️前言🌤️
在我们平时使用计算机时,基本上每次都要与文件打交道,用其于存储数据,又或是读取数据。那么作为平时生活中如此重要的一类,C语言中是否能够能对其进行相应操作呢?答案是肯定的,今天我们就来学习一下C语言中是如何对文件进行操作的。
一.什么是文件?⛅
计算机中的文件是指存储在外部介质(硬盘)上的数据集合。
但是在程序设计中,我们一般谈到的文件有两种:程序文件和数据文件
而本章中的文件都是以数据文件为对象
程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
在以前代码所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上件。
终端是与计算机系统相连的一种输入输出设备。例如键盘是输入终端,显示器是输出终端。
二.文件名⛅
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
三.文件类型⛅
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。
4.文件缓冲区⛅
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
5.文件指针⛅
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。(有系统声明,直接使用就行,不用自己再去定义这么一个结构体)
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
FILE* pf;//文件指针
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
6.文件的打开与关闭⛅
文件在读写之前应该先打开文件,使用结束后应该关闭文件。在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC(简称标准C) 规定使用fopen函数来打开文件,fclose函数来关闭文件。
在此之前先来了解一个概念:流
在计算机编程中,流就是一个类的对象,很多文件的输入输出操作都以类的成员函数的方式来提供。
计算机中的流其实是一种信息的转换。它是一种有序流,因此相对于某一对象,通常我们把对象接收外界的信息输入(Input)称为输入流,相应地从对象向外输出(Output)信息为输出流,合称为输入/输出流(I/O Streams)。对象间进行信息或者数据的交换时总是先将对象或数据转换为某种形式的流,再通过流的传输,到达目的对象后再将流转换为对象数据。所以,可以把流看作是一种数据的载体,通过它可以实现数据交换和传输。
上面这个版本没看明白的话,我们再来看另外一个版本的讲解:
1.流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流。这个数据源可以是文件,内存。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。
2.流的本质: 数据传输
3.流的划分:
输入流: 从磁盘(存储介质)---------->当前程序(内存)的过程
输出流: 从当前程序(内存)---------->磁盘的过程(存储介质)
(1)fopen函数🌤️
函数声明:
FILE * fopen ( const char * filename, const char * mode );
- 打开文件名在参数filename中指定的文件,并将其与一个流关联,该流可以在将来的操作中通过返回的文件指针识别。
- 流上允许的操作以及如何执行这些操作由mode参数定义。
- 返回的指针可以通过调用fclose或freopen与文件解除关联。所有打开的文件在正常程序终止时自动关闭。
read:打开文件进行输入操作。该文件必须存在。
write:为输出操作创建一个空文件。如果同名的文件已经存在,则其内容将被丢弃,并将该文件视为新的空文件。
(2)fclose函数🌤️
函数声明:
int fclose ( FILE * stream );
- 关闭与流关联的文件并解除关联。
- 所有与流关联的内部缓冲区都将与流解除关联并刷新:任何未写入的输出缓冲区的内容将被写入,而任何未读的输入缓冲区的内容将被丢弃。
- 即使调用失败,作为参数传递的流也不再与文件及其缓冲区相关联。
- 关闭文件后需将文件指针置为空指针
实例:
//文件的打开与关闭
#include<stdio.h>
int main()
{
//打开文件
// 在我们打开文件时,操作系统会在内存中开辟一块结构体类型的文件信息区,记录文件名,文件大小等
// 而fopen 函数在打开文件成功时会返回这个文件信息区的首地址,打开失败则返回空指针
//FILE * pf = fopen("data.txt", "r");//文件名的方式打开
FILE* pf = fopen("C:\\fopen\\data.txt", "r");//以绝对路径的方式打开
//这里必须加双斜杠,因为单斜杠会造成转义字符的问题
//判空
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件操作
//....
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
7.文件的顺序读写⛅
各函数介绍:
是输入操作还是输出操作,这是相对于程序而言的,例如:读取文件,读文件肯定是读取文件内的数据到程序中,对于程序来说就是输入,所以读文件是输入操作;那么相反,写文件就是往文件中写入数据,是从程序中写到文件里,对于程序来说就是输出,所以写文件是输出操作
//fopen--文件打开
//fclose--文件关闭
//fputc--输出操作->写文件-->写一个字符
// int fputc ( int character, FILE * stream );
// 成功返回读写的字符,失败返回EOF
//fgetc--输入操作->读文件-->读一个字符
// int fgetc ( FILE * stream );
//成功返回读取到的字符,读取到末尾返回EOF
//fputs--输出操作->写文件-->写一句
// int fputs ( const char * str, FILE * stream );
// 成功返回一个非负值,失败返回EOF
//fgets--输入操作->读文件-->读一句
// char * fgets ( char * str, int num, FILE * stream );
// 成功返回str,失败返回空指针
//fprintf--输出操作->写文件-->将格式化的数据写入流--针对所有输出流的格式化输出函数
// int fprintf ( FILE * stream, const char * format, ... );
// 成功返回写入的字符总数,失败返回一个负数
//fscanf--输入操作->读文件-->从流中读取格式化的数据--针对所有输入流的格式化输入函数
// int fscanf ( FILE * stream, const char * format, ... );
//
//sscanf--将一个字符串转化成为一个格式化数据
//sprintf--将一个格式化数据转化成一个字符串
//fread--以二进制的方式读文件
//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
// 返回成功读取的元素总数,失败返回0
//fwrite--以二进制的方式写文件
//size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
//返回成功写入的元素总数,失败返回0
读写文件:
(1)
//读写文件
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
写文件--输出操作(从程序输出到文件)
//char ch = 0;
//for (ch = 'a'; ch <= 'z'; ch++)
//{
// fputc(ch, pf);
//}
//读文件--输入操作(从文件输入到程序)
int c = 0;
while ((c = fgetc(pf)) != EOF)
{
printf("%c ", c);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(2)
//fgetc,fputc应用于所有输入输出流
#include<stdio.h>
int main()
{
//这里的fgetc,fputc针对的是标准输入输出流,上面是针对的文件流,所以其适应所有的输入输出流
int ch = fgetc(stdin);
//printf("%c\n", ch);
fputc(ch, stdout);
return 0;
}
(3)
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("C:\\fopen\\data.txt", "w");//打开文件时,“写”会销毁原来文件中的内容
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写操作---写一句
fputs("I miss you very much!\n", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(4)
#include<stdio.h>
int main()
{
char arr[256] = { 0 };
//打开文件
FILE* pf = fopen("C:\\fopen\\data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读操作---读一句
//fgets(arr, 256, pf);//最多读255个,剩余的一个会用来存放\0
while ((fgets(arr, 256, pf) != NULL))
{
printf("%s", arr);
}
//printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(5)
//文件与结构体之间的读入读出
#include<stdio.h>
struct S
{
char name[20];
int age;
double d;
};
int main()
{
//打开文件
FILE* pf = fopen("C:\\fopen\\data.txt", "w");
struct S s = { "张三", 20 ,95.50 };//将要写入文件的结构体信息
//判空
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件---输出操作
fprintf(pf,"%s %d %lf", s.name, s.age, s.d);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(6)
#include<stdio.h>
struct S
{
char name[20];
int age;
double d;
};
int main()
{
//打开文件
FILE* pf = fopen("C:\\fopen\\data.txt", "r");
struct S s = { 0 };//定义一个结构体变量用来存放从文件中读取的数据
//判空
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件---输入操作
fscanf(pf, "%s %d %lf", s.name, &(s.age), &(s.d));
//printf("%s %d %lf", s.name, s.age, s.d);
fprintf(stdout, "%s %d %lf", s.name, s.age, s.d);//stdout是输出到频幕上的一个标准输出流
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(7)
#include<stdio.h>
struct S
{
char name[20];
int age;
double d;
};
int main()
{
char buf[256] = { 0 };
struct S s = { "张三",20,95.5 };
struct S tmp = { 0 };
sprintf(buf, "%s %d %lf", s.name, s.age, s.d);
printf("%s\n", buf);//以字符串形式打印
sscanf(buf, "%s %d %lf", s.name, &(s.age), &(s.d));
printf("%s %d %lf", s.name, s.age, s.d);//以格式类型打印
return 0;
}
(8)
#include<stdio.h>
struct S
{
char name[20];
int age;
double d;
};
int main()
{
struct S s = { "zhangsan",20,85.8 };
//打开文件
//写文件--以二进制方式写
FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//以二进制方式写文件
fwrite(&s, sizeof(struct S), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(9)
#include<stdio.h>
struct S
{
char name[20];
int age;
double d;
};
int main()
{
struct S s = { 0 };
//打开文件
//读文件--以二进制方式读
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//以二进制方式读文件
fread(&s, sizeof(struct S), 1, pf);
printf("%s %d %lf\n", s.name, s.age, s.d);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
8.文件的随机读写⛅
文件的随机读写
fseek函数
int fseek ( FILE * stream, long int offset, int origin );
offset是指针偏移量,origin有三个值,分别是
SEEK_SET(指针指向开始位置)
SEEK_CUR(指针指向当前位置)
SEEK_END(指针指向末尾位置)
//随机读文件
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char ch = 0;
ch = fgetc(pf);
printf("%c\n", ch);//a
ch = fgetc(pf);
printf("%c\n", ch);//b
fseek(pf, 2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);//e
fseek(pf, -2, SEEK_END);//相当于\0的位置
ch = fgetc(pf);
printf("%c\n", ch);//e
fseek(pf, 3, SEEK_SET);
ch = fgetc(pf);
printf("%c\n", ch);//d
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//随机写文件
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}
fseek(pf, -6, SEEK_CUR);
fputc('M', pf);
fclose(pf);
pf = NULL;
return 0;
}
9.文件结束判定⛅
被错误使用的 feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
1. 文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
例如:
fgetc判断是否为EOF.
fgets判断返回值是否为NULL.
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。
正确的使用:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
}
本篇以文件操作为主题的博客到此就要结束了,希望本篇博客可以帮助到各位小伙伴,博主码字不易,如果本篇博客对你有所帮助,还望各位小伙伴点赞👍,收藏⭐+关注,感谢各位的支持!如果有什么不明白的地方或是另有其他的高见,也可以在评论区留言,博主也会及时回复或进行更改,欢迎指正,谢谢!