一.为什么要使用文件
当我们运行程序时,数据都是存放在内存中的,当程序退出后,内存中的数据会还给操作系统,因此,为了实现数据的持久化,我们可以将数据存放到电脑的硬盘上。
二.什么是文件
文件分为程序文件和数据文件,文件名是唯一的文件标识。
1.程序文件
例如源程序文件(.c),目标文件(windows环境下后缀为.obj),可执行程序(windows环境下后缀为.exe)
2.数据文件
程序运行时,输入和输出的数据。
ps:文件名分为绝对文件名和相对文件名,绝对文件名适用于桌面上的文件(都在c盘),应用“\\”可以操作改目录的上一目录,\\这个符号可以多次叠用。
三.文件的打开和关闭及顺序读写
文件的打开和关闭已经所有有关对文件的操作,都离不开“文件指针”。
文件指针:全称“文件类型指针”,实质是一个由系统声明的结构体变量。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息,取名为FILE,通常写为FILE* pf
1.文件的打开
ANSIC规定使用fopen函数来打开文件
FILE* fopen(const char* filename,const char* mode);
ps:打开文件失败fopen返回空指针,需要进行判断。
2.文件的关闭
用fclose函数
int fclose(FILE* stream);
ps:关闭文件后需要及时置空指针!
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
//打开文件 相对路径
FILE* pf = fopen("..\\data.txt", "w");//..可以创建到上一文件里
//绝对路径
//FILE* pf = fopen("D:\\C语言代码\\test_8_28\\ test.c\\date.txt","w");
//判断是否为空
if (fopen == NULL)
{
perror("fopen");
return 1;
}
//读写数据
//关闭文件
fclose(pf);
//及时将文件指针置空,避免成为野指针
pf = NULL;
return 0;
}
3.文件的使用方式(mode)
ps:以下示例的文件为data.txt存放着abcde
①fgetc
int fgetc ( FILE * stream );
读取文件流中指针所指向的单个字符,如下:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
}
②fputc
int fputc ( int character, FILE * stream );
存放单个字符到流所维护的文件里。
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//写文件
fputc('P',pf);
//关闭文件
fclose(pf);
pf = NULL;
}
③fgets
char * fgets ( char * str, int num, FILE * stream );
将文件流里的字符串交给一个数组,通过打印数组来读文件里的字符串。(只会读n-1个字符)
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//读文件
char arr[10] = { 0 };
fgets(arr, 3, pf);
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
}
④fputs
int fputs ( const char * str, FILE * stream );
将字符串写入文件中。
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//文件
fputs("ABC", pf);
//关闭文件
fclose(pf);
pf = NULL;
}
⑤fscanf与fprintf
int fscanf ( FILE * stream, const char * format, ... );
int fprintf ( FILE * stream, const char * format, ... );
仅仅比scanf和printf多了第一个参数,scanf和printf默认标准输入流,fscanf和fprintf可指定文件流
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r+");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//写文件
struct S s = { 100,3.14 };
fprintf(pf, "%d %f", s.a,s.b);
//读文件
/*struct S s = { 0 };*/
fscanf(pf, "%d %f", &(s.a), &(s.b));
printf("%d %f\n", s.a, s.b);
//关闭文件
fclose(pf);
pf = NULL;
}
/* fscanf example */
#include <stdio.h>
int main()
{
char str[80];
float f;
FILE* pFile;
pFile = fopen("myfile.txt", "w+");
fprintf(pFile, "%f %s", 3.1416, "PI");
rewind(pFile);
fscanf(pFile, "%f", &f);
fscanf(pFile, "%s", str);
fclose(pFile);
//printf("I have read: %f and %s \n", f, str);
return 0;
}
⑥fread(返回读到的数据个数,可用来判断文件是否读取结束)和fwrite
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
int main()
{
struct S s = { 100, 2.5, "haha" };
// 打开文件
FILE* pf = fopen("data.txt", "wb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
// 写文件
fwrite(&s, sizeof(struct S), 1, pf);
// 关闭文件
fclose(pf);
pf = NULL;
// 打开文件以读取
pf = fopen("data.txt", "rb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
// 读文件
int pos = fread(&s, sizeof(struct S), 1, pf);
if (pos != 1)
{
perror("fread");
return 1;
}
else
printf("文件读取成功\n");
// 关闭文件
fclose(pf);
// 打印读取到的数据
printf("%d %f %s\n", s.a, s.b, s.arr);
printf("读到了%d个\n", pos);
return 0;
}
四.文件的随机读写
1.fseek函数:根据文件指针的位置和偏移量来定位文件指针。
int fseek (FILE* stream,long int offset,int origin);
#include<stdio.h>
//int main()
//{
// FILE* pFile;
// pFile = fopen("example.txt", "wb");
// fputs("This is an apple.", pFile);
// fseek(pFile, 9, SEEK_SET);
// fputs(" sam", pFile);
// fclose(pFile);
// return 0;
//}
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//写文件
fputs("abcdef", pf);
//随机读
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);//这步会读指针所指向的一个字符
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
}
ps:第三个参数可谓SEEK_SET SEEK_CUR SEEK_END
2.ftell函数:返回文件指针相对于起始位置的偏移量
long int ftell(FILE* stream);
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r+");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//写文件
fgetc(pf);
fgetc(pf);
fgetc(pf);
int pos = ftell(pf);
printf("%d", pos);
//关闭文件
fclose(pf);
pf = NULL;
}
3.rewind函数(令文件指针回到起始位置)
void rewind ( FILE * stream );
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//读文件
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
//ftell返回指针相对于起始位置的偏移量
int pos = ftell(pf);
printf("%d\n", pos);
//rewind让文件指针回到起始位置
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
五.文件读取结束的判定
1.fgetc的返回值是否为EOF
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r+");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
//写文件
int ch = fgetc(pf);
ch = fgetc(pf);
if (ch != EOF)
{
printf("文件读取成功\n");
printf("%c", ch);
}
else
printf("读取结束");
//关闭文件
fclose(pf);
pf = NULL;
}
2.fgets的返回值是否为NULL
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r+");
if (NULL == fopen)
{
perror("fopen");
return 1;
}
char arr[10] = { 0 };
char* pf1=fgets(arr, 3, pf);
if (pf1 != NULL)
{
printf("文件读取成功\n");
}
else
printf("读取结束");
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.fread的返回值是否小于实际要读取的个数
int main()
{
struct S s = { 100, 2.5, "haha" };
// 打开文件
FILE* pf = fopen("data.txt", "wb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
// 写文件
fwrite(&s, sizeof(struct S), 1, pf);
// 关闭文件
fclose(pf);
pf = NULL;
// 打开文件以读取
pf = fopen("data.txt", "rb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
// 读文件
int pos = fread(&s, sizeof(struct S), 1, pf);
if (pos != 1)
{
perror("fread");
return 1;
}
else
printf("文件读取成功\n");
// 关闭文件
fclose(pf);
// 打印读取到的数据
printf("%d %f %s\n", s.a, s.b, s.arr);
printf("读到了%d个\n", pos);
return 0;
}
ps:feof是在文件读取结束后,判断是否是遇到文件尾而结束的。
//判断文件结束的原因
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("data.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);
}
六.文本文件和二进制文件 (数据的组织形式不同)
文本文件:以ASCII字符的形式储存的文件。
二进制文件:数据在内存中以二进制的形式储存,不加以转换输出到外存(硬盘)。
1.数据在内存中如何储存
字符一律以ACSII值储存,数值型数据既可以用ASCII储存,也可以用二进制形式储存。
以10000为例,以ASCII形式为5个字符(5个字节),看作整形为4个字节。实际上10000在文件中是以整型存储的。
int main()
{
int a = 10000;
// 打开文件
FILE* pf = fopen("data.txt", "wb");
if (NULL == pf)
{
perror("fopen");
return 1;
}
// 写文件
fwrite(&a, 4, 1, pf);
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
七.文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
文件缓冲区的设定是为了提高数据的输入输出效率,可以这样理解,假定一位老师正在备课,此时如果一位学生每隔一分钟问老师一个问题,那么老师在这五分钟内就不能备课。但如果这名学生五分钟之内攒够五个问题,一次性请教老师,那么老师既可以为学生解决问题,又完成了备课工作。文件缓冲区也是同样的道理,避免了少量多次文件的输入和输出,以提高效率。
下面,我们通过编写一个程旭来感受文件缓冲区的存在。
#include <stdio.h>
#include <stdlib.h>
#include<windows.h>
int main(void)
{
FILE* fp = fopen("txta.txt", "w");//写
fputs("abcdef", fp);
printf("睡眠10秒-已经写数据了,打开data.txt,发现没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(fp);
printf("再睡眠10秒-此时再打开data.txt,发现有内容了\n");
Sleep(10000);
fclose(fp);//关闭文件也会刷新缓冲区
fp = NULL;
}