📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL..
📚以后会将数据结构收录为一个系列,敬请期待
● 本期内容讲解C语言如何对文件进行操作,包括读取,写入以及文件结束相关的知识
文章目录
1.文件的定义和使用方法
1.1 文件的定义
(1)什么是文件?
文件是存储在磁盘上的文件。
(2)为什么使用文件
我们在程序运行结束后,该程序中所有的变量在下一次使用时全部消失了,但是我们如果将这次的数据信息记录到文件中,保存在电脑的硬盘上,下次就可以直接用程序读取文件信息即可,做到了数据的持久化。
(3)文件的分类
文件分为程序文件和数据文件:
程序文件:包括源文件(.c),目标文件(.obj),可执行程序(.exe)
数据文件:程序运行时读写的数据
1.2 文件的打开和关闭
文件打开和关闭需要引用函数,fopen是文件打开,fclose是文件关闭
FILE *fopen( const char *filename, const char *mode );
int fclose( FILE *stream );
fopen返回类型是文件指针类型,函数参数是文件名和打开方式,注意fopen也可能打开失败,打开失败返回NULL,所以一定要进行判断。
fclose比较简单,就是将文件指针作为参数即可。
#include<stdio.h>
int main()
{
FILE * pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
文件打开的方式:
文件的使用方式 | 含义 | 文件不存在 |
"r"(只读) | 为了输入数据,打开一个已经存在的文件文件 | 出错 |
"w"(只写) | 为了输出数据,打开一个文本文件 | 创建新文件 |
"a"(追加) | 向文本文件尾添加数据 | 创建新文件 |
"rb"(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
"wb"(只写) | 为了输出数据,打开一个二进制文件 | 创建新文件 |
"ab"(追加) | 向一个二进制文件尾添加数据 | 创建新文件 |
"r+"(读写) | 为了读和写,打开一个文本文件 | 出错 |
"w+"(读写) | 为了读和写,建立一个新的文本文件 | 创建新文件 |
"rb+"(读写) | 为了读和写,打开一个二进制文件 | 出错 |
"wb+"(读写) | 为了读和写,建立一个新的二进制文件 | 创建新文件 |
"ab+"(读写) | 打开一个二进制文件,在文件尾进行读写操作 | 创建新文件 |
1.3 文件顺序读写
(1)顺序读写函数介绍
功能 | 函数名 | 适用于 |
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
介绍一下以上函数:
(1)fputc函数:字符输出函数
函数简介:
将字符写入流(fputc、fputwc)或 stdout(_fputchar、_fputwchar)。
int fputc( int c, FILE *stream );
想利用这个函数写入26个字母到data.txt这个文件中,看以下示例:
#include<stdio.h>
int main()
{
//打开文件
FILE * pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
int i = 0;
for (i = 0; i < 26; i++)
{
fputc('a'+i, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
输出结果:
(2)fgetc函数:字符输入函数
从流 (fgetc, fgetwc) 或 stdin (_fgetchar, _fgetwchar) 中读取字符。
int fgetc( FILE *stream );
#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
ch = fgetc(pf);
printf("%c\n", ch);//c
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
输出结果:
(3)fputs
将字符串写入流。
int fputs( const char *string, FILE *stream );
比方说我想把hello world 和 hello everybody写入文件中
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
char arr1[] = "hello world\n";
char arr2[] = "hello everybody";
fputs(arr1, pf);
fputs(arr2, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(4)fgets
从流中获取字符串。
char *fgets( char *string, int n, FILE *stream );
想把文件中已经有的hello world 和 hello everybody打印出来看一下
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
char arr1[20];
char arr2[20];
fgets(arr1, 13, pf);
fgets(arr2, 16, pf);
printf("%s", arr1);
printf("%s", arr2);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(5)fprintf函数
将格式化的数据打印到流。
int fprintf( FILE *stream, const char *format [, argument ]...);
我们上面四个函数,都是字符或者字符串,其他类型不行吗?答案是可以的
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
int a = 100;
float b = 3.14f;
fprintf(pf, "%d %f", a, b);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(6)fscanf函数
从流中读取格式化的数据。
int fscanf( FILE *stream, const char *format [, argument ]... );
从文件中提取数据到程序中
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
int a ;
float b ;
fscanf(pf, "%d %f", &a, &b);
printf("%d %f", a, b);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(7)fwrite函数
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
以二进制输出文本
#include<stdio.h>
struct S
{
char a;
int b;
float c;
char arr[20];
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
struct S s = { 'a',1,1.5,"woaini" };
fwrite(&s, sizeof(struct S), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
实际上在我们真正去看这个txt文件是看不懂的,里面是给程序看的。
(8)fread函数
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
读取二进制文件到程序中
#include<stdio.h>
struct S
{
char a;
int b;
float c;
char arr[20];
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
struct S s;
fread(&s, sizeof(struct S), 1, pf);
//关闭文件
printf("%c %d %f %s", s.a, s.b, s.c, s.arr);
fclose(pf);
pf = NULL;
return 0;
}
1.4 两个常用的输入输出函数的对比
(1)scanf/fscanf/sscanf
第一,scanf是最常用的,是从标准输入(键盘)读取格式化的数据到程序中。
第二,fscanf是比scanf是高级的,也可说fscanf包含了scanf,这个函数可以从所有输入流中读取格式化数据。
第三,sscanf是从字符串中读取格式化的数据。这个比较陌生我举个例子
(2)printf fprintf sprintf
第一,printf是标准输出函数,就是把格式化的数据标准输出到(屏幕)上。
第二 fprintf是包含printf的功能,他是将格式化的数据标准输出到咱们的屏幕、文件等所有输出流
第三 sprintf是把格式化的数据转换输出为字符串,例子上文已有。
1.5 文件的随机读写
(1)fseek函数
根据文件指针的位置和偏移量来定位文件指针。
int fseek( FILE *stream, long offset, int origin );
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
char ch;
fseek(pf, 1, SEEK_SET);
ch = fgetc(pf);
printf("%c", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(2)ftell函数
ftell主要是判断返回文件指针相对于起始位置的偏移量
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
char ch;
int a;
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);//偏移量为4
a = ftell(pf);
printf("\n%d", a);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
(3)rewind函数
让文件指针的位置回到文件的起始位置
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
char ch;
int a;
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);//偏移量为4
a = ftell(pf);
printf("\n%d", a);
rewind(pf);
ch = fgetc(pf);
printf("\n%c", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.文本文件和二进制文件
2.1 文本文件
文本文件在内存中以二进制存储,如果在存储前转换,以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律按ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以用二进制形式存储。
通俗来说,文件我们看得懂的就是文件存储,看不懂的就是二进制存储
2.2 二进制文件
我们可以用VS中的导入文件用二进制方式打开来看一看在二进制文件中是如何存储的
先创建一个二进制文本,里面放int 类型 数值是800
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//想写文件
int a = 800;//0011 0010 0000
fwrite(&a, sizeof(int), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
以二进制形式打开:
为什么以二进制打开这个文本里面放着这些东西呢?那么其实我们分析一下即可,看图
3.文件读取结束的判定
3.1 被错误使用的feof
牢记:在文件的读取过程中,不能用feof函数的返回值来直接判断文件是否结束
feof的作用是:当文件读取结束后,判断读取结束的原因是否是:遇到文件尾结束
正确判断文件结束的方法:
1、文本文件读取是否结束,判断返回值是否为EOF 或者是NULL
例如:
● fgets返回NULL
● fgetc返回EOF
2、二进制文件的读取结束判断,主要是判断返回值是否小于实际要读的个数
正确的使用:
文本文件的例子:
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.txt","w");
char ch = 0;
for (int i = 0; i < 26; i++)
{
fputc('a' + i, pf);
}
fclose(pf);
pf = NULL;
pf = fopen("data.txt", "r");
int c;
while ((c = fgetc(pf)) != EOF)
{
printf("%c ", c);
}
if (ferror(pf))
puts("I/0 error when reading");
else if (feof(pf))
puts("End of file reached successfully");
fclose(pf);
pf = NULL;
return 0;
}
二进制文件的例子:
#include<stdio.h>
#define SIZE 5
int main()
{
double a[SIZE] = { 1,2,3,4,5 };
double b[SIZE];
FILE* pf = fopen("data.txt","wb");
fwrite(a, sizeof(a[0]), SIZE, pf);
fclose(pf);
pf = NULL;
pf = fopen("data.txt", "rb");
size_t ret =fread(b, sizeof(a[0]), SIZE, pf);
if (ret == SIZE)
{
puts("Array read successfully,contents:");
for (int j = 0; j < SIZE; j++)
{
printf("%lf ", b[j]);
}
printf("\n");
}
else
{
if (feof(pf))
printf("Error reading test.c : unexpeted end of file \n");
else if (ferror(pf))
printf("Error reading test.c");
}
pf = NULL;
return 0;
}
4. 文件缓冲区
ANSIC标准采用“缓冲文件系统”处理数据文件的,就是系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。
总结
上文就是文件操作的相关知识了,下一节会给大家更新数据结构系列。
如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。