前言: 我们应该知道一般程序运行时产生的数据是存放在内存中的。但是如果程序关闭后这些内存就会被系统回收,如果内存内的有用的数据没有被保存下来,这些数据就丢失了。所以这个时候我们就可以使用磁盘来储存我们的数据。
目录
程序文件的分类
程序文件一般分为两类:程序文件和数据文件。
程序文件: 源代码(.c文件) 、目标文件(.obj) 、 可执行程序(.exe)
数据文件:用于保存数据的文件。 区别于用来运行程序的程序文件。
文件名
每个文件都有一个路径和名称, 这是用来寻找该文件的途径。 便于用户使用。
文件名一般包含三个部分: 文件路径, 文件主干, 文件后缀
二进制文件和文本文件
一个数据,我们都知道数据在计算机中是以二进制的方式进行储存, 如果这个数据从计算机中不加转换的就输出到外存的文件中, 就是二进制文件。
但是如果这个数据转换成ASCII码的形式, 那么就是文本文件。
字符一律通过ASCII码的形式进行储存, 数值则ASCII和二进制储存皆可。
比如10000这个数值, 如果用ASCII就花费五个字节
但是用二进制就只需要4个字节。
文件的打开和关闭
流的概念:
程序数据进行输出到外部设备时, 以及程序的数据从外部设备获取数据时, 不同的外部设备的输入输出操作是各不相同的。 为了方便对各种设备进行方便的操作呢, 人们抽象出了流的概念。 可以将其抽象成一条充满字符的河。
我们通过向这条河中写数据, 或者从这条河中拿数据, 这就是数据的输入和输出。
标准流:
我们打开一个程序, 那么我们就默认打开了三个流
一个是标准输入流stdin:标准输⼊流,scanf就是从标准输入流读取数据。
一个是标准输出流stdout: 标准输出流, printf就是将数据输出到标准输出流中。
一个是标准错误流stderr:用于输出错误信息。
三个流的类型是FILE*, 通常称为文件指针。
文件打开和关闭
在文件读写之前应该打开文件,在文件读写之后应该关闭文件。
在我们打开文件之后, 都会返回一个FILE*类型的指针指向该文件, 就建立了文件和指针之间的练习。
FILE是一个结构体类型。 它里面的成员变量是用来保存文件信息的。 当我们打开了一个文件, 就会在内存区生成一块对应的文件信息区。然后这块这块文件信息区的类型就是FILE, 里面储存的就是打开的文件的信息。 fopen就是返回这块文件信息区的地址。 然后我们是由FILE* 类型的指针进行接收:
打开文件函数:
FILE* fopen( const char* filename, const char* mode);
关闭文件函数:
int fclose( FILE* stream);
其中mode为文件的打开方式。
有以下几种打开方式:
⽂件使⽤⽅式 含义 如果指定⽂件不存在 “r”(只读) 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 出错 “w”(只写) 为了输出数据,打开⼀个⽂本⽂件 建⽴⼀个新的⽂件
“a”(追加) 向⽂本⽂件尾添加数据 建⽴⼀个新的⽂件 “rb”(只读) 为了输⼊数据,打开⼀个⼆进制⽂件 出错 “wb”(只写) 为了输出数据,打开⼀个⼆进制⽂件 建⽴⼀个新的⽂件 “ab”(追加) 向⼀个⼆进制⽂件尾添加数据 建⽴⼀个新的⽂件 “r+”(读写) 为了读和写,打开⼀个⽂本⽂件 出错 “w+”(读写) 为了读和写,建议⼀个新的⽂件 建⽴⼀个新的⽂件 “a+”(读写) 打开⼀个⽂件,在⽂件尾进⾏读写 建⽴⼀个新的⽂件 “rb+”(读写) 为了读和写打开⼀个⼆进制⽂件 出错 “wb+”(读写) 为了读和写,新建⼀个新的⼆进制⽂件 建⽴⼀个新的⽂件 “ab+”(读写) 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 建⽴⼀个新的⽂件
文件的顺序读写函数:
fgetc() 字符输入函数 所有输入流
fputs() 字符输出函数 所有输出流
fgets() 字符串输入函数 所有输入流
fputs() 字符串输出函数 所有输出流
fscanf() 格式化输入函数 所有输入流
fprintf() 格式化输出函数 所有输出流
fread() 二进制输入函数 文件
fwrite() 二进制输出函数 文件
上述得所有所有输入流包括标准输入流和其他输入流(如文件输入流), 上述的所有输出流包括标准输出流和其他输出流(如文件输出流)。
这些函数的使用方式如下:
向文件中读取数据, 然后保存到内存中开辟的空间中的函数是fgetc, fgets, fscanf, fread。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//
char arr[20] = { 0 };
fscanf(pf, "%s", arr);
printf("%s", arr);
//
fclose(pf);
return 0;
}
fscanf格式化输入, 区别于其他的读取流插入函数的特点是它可以插入任意类型的值。 读入格式由我们自己控制。 而像fgets, fgetc只能读取字符。将文件中的数据读取到arr中。arr代表了要读取到的内存空间, pf代表从pf文件流中读取。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//
char arr[20] = { 0 };
while(fgets(arr, 10, pf) != NULL)
{
printf("%s", arr);
}
//
fclose(pf);
return 0;
}
fgets, 向内存中获取数据, pf是文件流, 从pf文件流中获取十个字符到arr之中。
arr代表了内存空间, pf是文件流。
fgets如果读取正常, 返回的是存储读到的字符数组的内存的地址, 如果读取过程中读到了文件末尾, 或者发生了错误。 就返回NULL。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//
char ch;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c", ch);
}
//
fclose(pf);
return 0;
}
fgtc同样是从文件中读取字符的操作 , 它是一个一个的读取, 在一次的程序运行之中, 流的位置,会随着使用它进行读取向后偏移。
fgetc如果读取正确, 返回的是读取字符的ASCII码的值。 读取的过程中读到了文件的末尾或者发生了错误,就返回EOF。
向文件中写数据的函数是: fputs, fputc, fprintf, fwrite
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//
fputs("abcdef\n", pf);
fputs("hello world\n", pf);
//
fclose(pf);
return 0;
}
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//
char arr[] = { "hello world\nabcdefg\nslfdjk" };
fputs(arr, pf);
//
fclose(pf);
return 0;
}
fseek
int fseek (FILE* stream, long int offset, int origin);
fseek是根据流的初始位置和偏移量来定位一个文件指针位置。
以下为实例:
int main()
{
FILE* Open = fopen("text", "wb");
fputs("hello world", Open);
fseek(Open, 5, SEEK_SET);
fputs("earth", Open);
fclose(Open);
return 0;
}
这里传的参数SEEK_SET, 其实就是文件的起始位置。 然后fseek的第二个位置的参数是偏移量, 这里的fseek代表的意思就是让Open文件指针指向文件相对于起始位置的第五个偏移量的位置, 也就是第六个字符的位置。
ftell
long int ftell ( FILE * stream );
返回文件指针相对于起始位置的偏移量.
下图为实例:
int main()
{
FILE* Open;
long size;
Open = fopen("text", "rb");
if(Open == NULL)
{
exit(-1 );
}
fseek(Open, 0, SEEK_END);
size = ftell(Open);
fclose(Open);
printf("%d", size);
}
ftell就是告诉我们文件指针此时所在的相对起始位置的偏移量。如图就是使用size进行接收Open相对于起始位置的偏移量。
rewind
void rewind (FILE* stream);
让文件指针的位置返回到起始位置。
以下为实例:
int main()
{
FILE* Open = fopen("text", "w+");
char arr[27];
for(int n = 'A'; n <= 'Z'; n++)
{
fputc(n, Open);
}
rewind(Open);
fread(arr, 1, 26, Open);
fclose(Open);
arr[26] = '\0';
puts(arr);
return 0;
}
使用rewind之后, open文件留置针会重新指向文件的起始位置。
第一个for循环是将A~Z输出到文件中, 然后此时文件流指向了文件的末位置的下一个位置, 然后rewind, 此时文件流就指向了起始位置。 然后fread的操作就是将文件中的数据以二进制的形式读取到arr代表的空间之中。
注意:fread的声明:size_t fread(void* ptr, size_t size, size_t num, FILE* pf)
这里的size代表每一个要读取的元素的大小, num代表要读取多少个元素。
文件读取结束的判定
应该要牢记, 判断文件是否读取结束不要直接使用feof。
feof的作用是:当文件读取结束的时候, 判断文件读取结束的原因是不是文件到了末尾。
当读取文本文件时, 判断返回值:EOF(fgetc)或者NULL(fgets)。
当读取二进制文件时, 判断返回值是否小于实际要读取的个数。
feof的返回值是int类型。 检测是否遇到了文件末尾,一个文件流读取文件到了末尾会设置一个标志。对于feof来说, 一个文件流的文件末尾标志。如果被设置的话, 那么就返回一个非零的值。
所以对于feof来说, 如果返回了一个非零的值, 那么说明文件读取正常结束了。
否则的话就应使用ferror, ferror用来检测流上的错误标记是否被设置。 如果错误标记被设置, 那么ferror返回一个非零的值。
如图是读取正常结束代码
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == nullptr)
{
return -1;
}
//
char ch = 0;
while (ch = fgetc(pf) != EOF)
{
//
}
if (feof(pf))
{
cout << "文件正常结束" << endl;
}
else if (ferror(pf))
{
perror("fputc");
}
return 0;
}
如贴图为读取错误代码, 这个时候我文件打开方式是读, 但是我进行了写的操作。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == nullptr)
{
return -1;
}
//
char ch = 0;
fputc(ch, pf);
if (feof(pf))
{
cout << "文件正常结束" << endl;
}
else if (ferror(pf))
{
perror("fputc");
}
return 0;
}
文件缓冲区的概念
ANSIC标准采用缓冲文件系统处理数据文件的。缓冲文件系统就是指,系统自动的在内存中为程序中的每一个正在使用的文件开辟一块”文件缓冲区”,从内存向磁盘输入数据应该先通过文件缓冲区, 等到文件缓冲区满了再一起送到磁盘上。如果是磁盘向计算机读入数据,则从磁盘文件中读取数据到文件缓冲区, 然后等到满了再一起读到计算机内存中。
int main()
{
FILE*pf = fopen("text", "w");
fputs("abcdef", pf);
printf("正在写数据,这时候我们可以打开⽂件,因为要观看是否写上内容,因为有10秒缓冲。但是我们发现⽂件没有内容。\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//这个时候其实写上了。
printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");
Sleep(10000);
fclose(pf);//刷新缓冲区
pf = NULL;
return 0;
}
因为有缓冲区的存在, c语言操作文件的时候, 需要做刷新缓冲区或者再文件操作结束的时候关闭文件。 否则可能导致读写文件的问题。