文件
文件的详细介绍
引子
文件.在我们前面学习通讯录时,程序运行起来时,可以给通讯录增删查改,但此时数据是存放在内存中,当程序退出时,通讯录的数据就消失了。
就很难受,为了使得数据持久化,我们就应该把数据存放在磁盘文件,存放到数据库等方式,磁盘上关机了重启还会在,使数据持久化,因此就需要用到文件
目录
1 什么是文件
在磁盘上所有的东西都是文件,c盘包括桌面的,
但一般在程序设计时,分两种:程序文件和数据文件(从文件的功能进行分类的)
程序文件有源程序文件.c,目标文件.obj,可执行文件.exe
数据文件,我们写的数据
2.文件名
文件名,一个文件有唯一的文件标识。便于用户使用和识别(绝对路径)
文件名包含3个部分:文件路径+文件名主干+文件名后缀
如c:\code\test.txt,其中c:\code\是路径名,test叫文件主干名,.txt叫文件的后缀2
3.文件指针
4.文件指针(文件的打开与关闭)
每个被使用的文件都开辟了一个相应的文件信息区,用于存放文件的相关信息,
(如文件的名字,文件状态及文件当前的位置)这些信息都保持在一个结构体变量中,该结构体类型名叫FILE
一般用FILE型指针来维护FILE结构的变量,这样使用起来更加方便
FILE* pf,pf是指向FILE型的变量,可以使用pf来指向某个文件信息区,通过文件信息区中的信息就可以访问该文件,即可以通过文件指针变量找到与他相关的文件,
4.文件的使用
文件在读写之前首先要打开文件fopen,在文件使用完之后要关闭文件fclose,fopen返回失败会返回一个空指针,因此每次使用都要判断
其中fopen原型是FILE * fopen(const char * path, const char * mode);
const char * path是使用的路径,即要在哪个文件中使用
const char * mode是fopen的打开形式
常见的模式有”r“,(read)以只读方式打开文件,该文件必须存在。
”w“(write)打开只写文件,若文件存在则文件长度清为零,即该文件内容会消失;若文件不存在则创建该文件。
wb是只写打开或新建一个二进制文件,只允许写数据。
例如
文件的打开与关闭
1.0
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *pf=fopen("tst.dat", "w");*///"test.dat"是文件名,"w"是打开方式(以写的形式进行创建)(只写)“w”为了 输出数据,打开一个文件,如果指定文件不存在,就新建一个(在此文件中开辟)
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//关闭文件(不想要了)
fclose(pf);
pf = NULL;//可类比动态内存开辟,补不置成空指针就会报错
return 0;
}
1.1
#include<stdio.h>
int main()
{
FILE *pf=fopen("D:\\2021\\class\\test.7\\test.dat", "w");//如果文件并不是在此文件下创建的化,同样也不会运行成功,但使用绝对路径是可以的
//,单斜杠情况下会将后面理解为转义字符,因此都要使用双斜杠,用于将\打印出来
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//关闭文件(不想要了)
fclose(pf);
pf = NULL;//
return 0;
}
1.3
#include<stdio.h>
int main()
{
FILE *pf = fopen("tst.dat", "r");//以读的方式打开
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//关闭文件(不想要了)
fclose(pf);
pf = NULL;//可类比动态内存开辟,
return 0;
}
文件的顺序读写
流
:流,高度抽象的概念,,我们写的程序有时想放屏幕,硬盘,u盘,光盘,网络,程序要操作各种硬件,也就需要各种的读写方式,因此在程序与硬件中抽象出流这个层次
我们只有把数据放到流里面去,写文件可以理解为文件流,
c语言程序运行起来,就默认打开了3个流
stdin-标准输入流--键盘, stdout标准输出流--屏幕, stderr--标准错误流--屏幕,类型都是FILE*的使用哪个流就在哪个流输出输入
代码2.0
fputc
int fputc(int char, FILE *stream)把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动
#include<stdio.h>
int main()
{
FILE *pf = fopen("test.dat", "w");//以w的形式打开,即使原来有内容也会清空掉,
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputc('b', pf);//文件字符输入,
fputc('i', pf);
fputc('t', pf);//便会向test.dat文件中输入bit
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.1
#include<stdio.h>
int main()
{
fputc('c', stdout);//便在屏幕上打印了c这字符
fputc('b', stdout);
fputc('a', stdout);
return 0;
}
2.2.
fgetc
int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动
#include<stdio.h>
//文本中输入abcdef
int main()
{
FILE *pf = fopen("test.dat", "r");//以w的形式打开,即使原来有内容也会清空掉,
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件,fgetc从文件首字符中一个一个读取
//int ret=fgetc(pf);//从文件流里面读一个字符,可以从文件,也可以从标准输入流中,文件字符输入,如果读取正常,会返回此字符的ascll值,如果读取失败,会返回EOF(-1)
//printf("%c", ret);
//ret = fgetc(pf);
//printf("%c", ret);//读取的都是test.dat
int ret = fgetc(stdin);//从标准屏幕输入流里面读一个字符,可以从文件,也可以从标准输入流中,文件字符输入,如果读取正常,会返回此字符的ascll值,如果读取失败,会返回EOF(-1)
printf("%c", ret);
ret = fgetc(stdin);
printf("%c", ret);//读取的都是test.dat,文件结束就会输出EOF为-1
fclose(pf);
pf = NULL;
return 0;
}
fgets fputs
char *fgets(char *str, int n, FILE *stream)从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
int fputs(const char *str, FILE *stream) 把字符串写入到指定的流 stream 中,但不包括空字符
我们不仅可以一个字符一个字符的输入与输出,
我们也可以一行一行的输入与输出
可以用到fgets,fputs
#include<stdio.h>
int main()
{
FILE*pf = fopen("test.dat", "w");//以写的形式打开
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件-按行写
fputs("abcdef\n",pf);//s-string写字符串打个\n就可以换行,pf是要写的文件流
fputs("ghijk\n", pf);
//用完关闭文件
fclose(pf);
pf = NULL;
return 0;
}
#include<stdio.h>
int main()
{
char arr[10] = { 0 };
FILE*pf = fopen("test.dat", "r");//以读的形式打开
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件-按行读
fgets(arr, 4, pf);//fgets(const char*string,const int n,const stream),n是可以读的最大字符,但多了一个\0n要+1
printf("%s\n", arr);//读完从c后开始读
fgets(arr, 4, pf);//最多读3个
printf("%s\n", arr);
//用完关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fprintf,fscanf,sscanf,sprintf
#include<stdio.h
struct s
{
char age[10];
int a;
float f;
};
int main()
{
struct s a = { "hello",20,5.5f };
char buf[100] = { 0 };
struct s tmp = { 0 };
//sprintf是把格式化的数据转化成字符串,
sprintf(buf, "%s %d %f", a.age, a.a, a.f);
printf("%s\n", buf);
//从buf这个字符串中还原出一个结构体
//从字符串中读出格式化的数据
sscanf(buf, "%s %d %f", tmp.age, &tmp.a, &tmp.f);
printf("%s %d %f", tmp.age, tmp.a, tmp.f);
return 0;
}
fprintf(FILE *stream, const char *format,......)除去前面的流,后都和printf一样,
struct a
{
char arr[10];
int num;
float sc;
};
int main()
{
struct a s = { "abcd",10,5.5f };
FILE* pf = fopen("test.dat", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf, "%s %d %f", s.arr, s.num, s.sc);//格式化输出,写入pf文件流,
return 0;
}
fscanf同scanf多了前面的流
int main()
{
struct a s = { "abcd",10,5.5 };
FILE* pf = fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fscanf(pf, "%s %d %f", s.arr, &(s.num), &(s.sc));//格式化输出,写入pf文件流
printf("%s %d %f", s.arr, s.num, s.sc);
return 0;
}
fwrite fread
fwrite二进制输出 (const void *ptr, size_t size, size_t nmemb, FILE *stream)
把 ptr 所指向的数组中的数据写入到给定流 stream 中
size这是要被写入的每个元素的大小,以字节为单位
nmemb -- 这是元素的个数,每个元素的大小为 size 字节
int main()
{
struct a s = { "abcd",10,5.5 };
FILE* pf = fopen("test.dat", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&s, sizeof(struct a), 1, pf);//二进制输出,写入pf文件流
fclose(pf);
pf = NULL;
return 0;
}
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)与fwrite等同二进制输出
int main()
{
struct a s = { "abcd",10,5.5 };
FILE* pf = fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(&s, sizeof(struct a), 1, pf);//二进制输入,读出pf文件流
printf("%s %d %f", s.arr, s.num, s.sc);
fclose(pf);
pf = NULL;
return 0;
}
文件的随机读取
fseek ftell rewind
文件的随机读取文件的随机读写,根据文件指针的位置和偏移量来定位文件指针
fseek(file stream,long offset,int origin)//offset偏移量,oringin起始位置,
origin有三种状态,SEEK_CUR(从当前位置开始偏移),SEEK_END(从文件末尾开始偏移)只能偏移负数,
SEEK_SET(从文件开头开始偏移
文件随机读取的函数有rewind,让文件指针重新回到起始地址
ftell判断此时文件指针对于起始位置的偏移量
int main()
{
FILE *pf=fopen("test.dat", "r");
if (pf == NULL)
{
perror("fopen");
}
//顺序读写,从头开始
int ch = fgetc(pf);
printf("%c", ch);
fseek(pf, 2, SEEK_CUR);//偏移的单位是字节,从当前位置向后偏移两个字节
ch = fgetc(pf);
printf("%c", ch);
ch = fgetc(pf);
printf("%c", ch);
int ret = ftell(pf);
printf("%d", ret);
//让文件指针回到起始位置用rewind()
rewind(pf);//回到起始位置
int d = ftell(pf);
printf("%d", d);
return 0;
}
数据文件与二进制文件
数据文件被称为二进制文件或二进制文件,
1000可以把内存中的数据转化为ascll值存储,文本文件,也可以以二进制存储就叫二进制文件
int main()
{
int a = 10000;//00002710小端存储会是10 27 00 00
FILE* pf = fopen("test.dat", "wb");
if (pf == NULL)
{
perror("fopen");
}
//
fwrite(&a, sizeof(int), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
文件读取结束的判定
文件读取结束的判定
fgetc函数在读取结束的时候,会返回eof,正常读取的是很好,返回的是读取的字符的ascll值
fgets函数在读取结束是,会返回null,正常读取的时候 ,返回的是字符串;
fread在读取结束时,返回的是实际读取到的完整元素的个数,如果发现读取到的完整元素的个数小于指定的元素的个数,这就是最后一次读取,
判断文件结束的原因的函数是feofferror判断文件结束的原因是出现错误
写一个代码把text.txt 文件拷贝一份,生成text2.txt
int main()
{
FILE *pfd = fopen("text.txt", "r");
if (pfd == NULL)
{
return 1;
}
FILE *pw = fopen("text2.txt", "w");
if (pw == NULL)
{
fclose(pfd);
pfd = NULL;
return 1;//把第一个文件关掉,当第一个文件返回失败才会
}
//文件打开成功
//读写文件
int ch = 0;
while ((ch = fgetc(pfd)) != EOF)//一个字符一个字符的输入直到eof
{
//写文件
fputc(ch, pw);
pw = NULL;
}
if (feof(pfd))
{
printf("遇到文件结束标志,文件正常结束");
}
else if (ferror(pfd))//判断是否遇到错误
{
printf("文件读取失败结束");
}
fclose(pfd);
pfd=NULL;
fclose(pw)
pw=NULL;
return 0;
}
enum { size = 5 };
int main()
{
double a[size] = { 1,2,3,4,5 };
FILE *pf = fopen("test.dat", "wb");//必须用二进制模式
fwrite(a, sizeof *a, size, pf);//写double的数组
fclose(pf);
double b[size];
pf = fopen("test.dat", "rb");
size_t ret_code = fread(b, sizeof*b, size, pf);//读double的数组
if (ret_code == size)
{
puts("arry read succseefully,contents");
for (int n = 0; n < size; n++)
{
printf("%f", b[n]);
putchar('\n');
}
}
else
{
if (feof(pf))
{
printf("error reading test.bin:unexpected end of file");
}
else if (ferror(pf))
{
perror("error reading test.bin");
}
}
return 0;
}
文件缓冲区
文件缓冲区
从内存中向硬盘输出数据,会先送到内存中的缓冲区,装满之后才会送到硬盘上,
如果硬盘向计算机读取数据,则硬盘文件中读取数据集输入到内存缓冲区,充满后
再从缓冲区逐个将数据送到程序数据区,
#include<windows.h>
int main()
{
FILE *pf = fopen("test.dat", "w");
fputs("abcde", pf);
printf("睡眠10秒-已经写数据,打开test.txt文件,文件没有内容\n");
sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区,才将输出缓冲区的数据写到文件(磁盘)
//fflush在高端vs上不能使用
printf("再睡眠10秒,打开test.txt文件,有内容了");
sleep(10000);
//缓冲区里的数据
fclose(pf);
pf = NULL;
return 0;
}
再学习完文件后,我们就可以将通讯录进行优化