文件的定义
文件名
c盘,d盘上面的文件都是文件,文件名就是它的路径,如图
磁盘上的文件被定义为文件,文件(功能角度)主要分为两大类,一类是程序文件,另一类是数据文件。
程序文件
程序文件包括源程序文件,也就是后缀为.c的文件,目标文件(后缀为obj),可执行程序(后缀为exe)。(以上均为在Windows环境下)
数据文件
有些文件内存储的是数据,程序在执行时从中读取数据,数据是从磁盘上的文件提取到内存,或者输出内容到文件,最终输出到的是磁盘,这种文件都被称作数据文件。
本篇介绍数据文件
数据文件可以分为文本文件和二进制文件两类
文本文件
以ASCII码存储的文件就是文本文件。
二进制文件
以二进制存储在内存中,当被提取到外存时,仍然是二进制的输出形式的文件被称为二进制文件。
数据具体的存储方式:字符一律按ASCII码值存储,数值型既可以按ASCII码值存储,也可以按照二进制形式存储。
下面用一段代码来看一下存储形式。
#include<stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
fwrite(&a, 4, 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
运行成功的结果:
然后查看,首先鼠标右键单击资源文件,然后找到现有项
再找到创建的test.txt文件
找到后右击选择打开方式
往下找一找找到二进制编辑器选择上然后点击确定
最终展现的是以二进制形式存储的10000的十六进制形式
文件的打开和关闭
流、标准流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出 操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了流的概念,我们可以把流 想象成流淌着字符的河。
我们在进行键盘上的输入输出时没有进行打开流或者关闭流的操作,实质上c语言程序在启动时就默认打开了三个流:stdin 标准输入流,stdout 标准输出流,stderr标准错误流,这使得程序能用键盘进行输入,屏幕进行输出,还有报错。
文件指针
c语言中,就是通过文件指针(FILE*)来进行对流的操控的,其全称为文件类型指针。
当我们在创建文件,给文件内部进行输入信息的时候,每个文件都对应在内存中开辟一块空间进行对文件信息的存储,这些信息都保存在一个结构体变量内,而这个结构体变量是系统声名的,叫做FILE。
创建文件指针变量
通过文件指针变量我们可以读取或者输出内内容到其对应的文件内。
文件的打开和关闭
ANSI C规定使用fopen来打开文件,用fclose来关闭文件。
fopen有两个参数,第一个是你要操作访问的文件指针,第二个参数是你要进行的操作方式,
操作方式如下
还包括a(在文件末尾添加)、rb(读二进制文件)、wb(写入二进制文件)、ab(在一个二进制文件末尾进行添加)等
实例:
#include<stdio.h>
int main()
{
int a = 10000;
FILE* pf = fopen("test.txt", "wb");
if (pf != NULL)
{
fwrite(&a, 4, 1, pf);
}
fclose(pf);
pf = NULL;
return 0;
}
文件的顺序读写
顺序读写的一些函数:
fputc:
#include<stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "w");
if (fp == NULL)
{
perror("fopen");
return 1;
}
//打开文件
//写入文件
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, fp);
}
//关闭文件
fclose(fp);
fp = NULL;
return 0;
}
fgetc:
#include<stdio.h>
int main()
{
FILE* fp = fopen("test.txt", "r");
if (fp == NULL)
{
perror("fopen");
return 1;
}
//打开文件
//读入文件
int ch = 0;
while ((ch = fgetc(fp)) != EOF)
{
printf("%c", ch);
}
//关闭文件
fclose(fp);
fp = NULL;
return 0;
}
fputs:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("hello world\n", pf);
fputs("hello bit", pf);
fclose(pf);
pf == NULL;
return 0;
}
fgets:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[10] = { 0 };
fgets(arr, 10, pf);
//换行之后不读 留一个位置给\0 函数自己写的\0
fclose(pf);
pf == NULL;
return 0;
}
fprintf:
#include<stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { "zhangsan",20,65.5 };
//存储到文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf, "%s %d %f", s.name, s.age, s.score);
//写文件 以文本的方式
fclose(pf);
pf = NULL;
return 0;
}
fscanf:
#include<stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { 0 };
//读文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fscanf(pf,"%s %d %f", s.name, &(s.age), &(s.score));
//写文件 以文本的方式
//printf("%s %d %f", s.name, s.age, s.score);
fprintf(stdout,"%s %d %f", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
fwrite:
参数分别为数组名(数组地址)、数组元素大小、数组元素个数、文件指针
#include<stdio.h>
int main()
{
int arr[4] = { 1,2,3,4 };
FILE* pf = fopen("tstt.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int sz = sizeof(arr) / sizeof(arr[0]);
fwrite(arr, sizeof(arr[0]), sz, pf); //以二进制的形式写入
fclose(pf);
pf = NULL;
return 0;
}
fread:
参数分别为数组名(数组地址)、数组元素大小、数组元素个数、文件指针
#include<stdio.h>
int main()
{
int arr[4] = { 0 };
FILE* pf = fopen("tstt.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int sz = sizeof(arr) / sizeof(arr[0]);
fread(arr, sizeof(arr[0]), sz, pf); //以二进制的形式读到arr数组里面
for (int i = 0; i < sz; i++)
{
printf("%d", arr[i]);
}
return 0;
}
文件的随机读写
fseek:
fseek有三个参数,第一个参数就是你要进行操作的文件指针,第二个参数就是对于第三个参数(起始位置)的偏移量,第三个参数就是设置起始位置,起始位置有三个:SEEK_SET 文件开头、SEEK_CUR 文件指针当前的位置、SEEK_END文件末尾。
ftell:
ftell函数用来判断文件指针相对于当前位置的偏移量,它有一个参数,就是你当前操作的文件的文件指针。
rewind:
rewind函数的作用是将你当前正在操作的文件指针重置到起始位置。它的一个参数是你正在进行操作的文件。
使用实例:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
//fseek(pf, 4, SEEK_CUR);
//fseek(pf, 5, SEEK_SET);
fseek(pf, -4, SEEK_END);
//printf("%d\n", ftell(pf));
//计算指针偏移量
ch = fgetc(pf);
printf("%c\n", ch);
rewind(pf);
//指针回到起始位置
printf("%d\n", ftell(pf));
fclose(pf);
pf = NULL;
return 0;
}
文件读取结束状态判定
文本文件读取是否结束:判断返回值是否为EOF(用fgetc),或者NULL(fgets)
文本文件
#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);
}
//判断是什么原因结束的
printf("\n");
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
fp = NULL;
}
其中的fgetc当作while循环的条件用来判断读取是否结束,下面有两个函数,判断读取结束的原因。下面会提到两个函数。
二进制文件
二进制文件是否读取结束,判断的依据就是返回值是否小于实际要读的个数。
#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.bin", "wb");
fwrite(a, sizeof * a, SIZE, fp); // 写double的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读double的数组
if (ret_code == SIZE)
{
puts("Array read successfully, contents: ");
for (int n = 0; n < SIZE; ++n)
printf("%f ", b[n]);
putchar('\n');
}
else // error handling
{
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp))
{
perror("Error reading test.bin");
}
}
fclose(fp);
fp = NULL;
}
ferror用来判断文件指针读取结束原因是否为发生错误。
feof用来判断文件指针读取结束原因是否为遇到了文件末尾。
注意:这两个函数都不能用来判断是否读取结束,只能在已知它们结束了,然后去判断结束的原因。
文件缓冲区
ANSIC规定了一个文件缓冲系统用来处理数据文件,从内存向磁盘输出数据会先送到内存中的缓 冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘⽂件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小是由编译器决定的。
程序数据区 —————— 中间有一个(输出)(输入)缓冲区——————硬盘
文件缓冲区存在的意义:为了不一直调用系统保存文件,在文件缓冲区满的时候再调用系统,存到硬盘当中。