文件的分类:文件从功能上分为程序文件和数据文件。一般而言,放在硬盘中的也被称为文件
注意:内存中的叫做进程,不是文件,进程会随着程序的结束而终止,但是文件不会,文件保存在硬盘中
程序文件:典型的有.c文件.cpp文件.obj文件.exe文件
数据文件:通俗理解就是可以通过代码进行读和写的文件。C语言文件操作针对的文件是数据文件
文件的顺序读写:
打开文件:fopen
关闭文件:fclose
#includeint main() { FILE* pf = fopen("test.txt", "w"); if (pf == NULL) { perror("open fail"); } else//打开成功 { //可以对文件进行操作 fclose(pf); pf = NULL; } return 0; }
在打开文件时,需要指定文件名称和文件的打开方式,其中"w"指明是以写的方式打开文件。如果打开文件成功,会创建一个文件信息区,文件信息区里面保存有该文件的相关信息,这个文件信息区是一个名称为FILE的结构体。
typedef struct _iobuf
{
void* _Placeholder;
} FILE;
使用fopen成功打开文件,会返回一个FILE*的指针,通过该指针维护这个文件信息区,同时可以通过该指针对文件进行读写操作。如果打开失败,fopen会返回空指针,不会创建文件信息区
文件常见的几种打开方式:
w:以写的方式进行打开,如果文件不存在,会创建该文件。如果文件存在,会清空文件中的内容
r:以读的方式打开文件,如果文件不存在,会报错,文件存在,可以正常从文件中读取内容
wb,rb:以二进制的方式进行文件的读写
写字符到文件中,使用字符输出函数fputc.向文件中写入内容,对应输出函数
#include#pragma warning(disable:4996) int main() { FILE* pf = fopen("test.txt","w"); if (pf == NULL) { perror("open fail"); } else { int a ='1'; while (a<= '9') { fputc(a, pf); a++; } fclose(pf); pf = NULL; } return 0; }
字符输出函数fputc可以连续像文件中写入
从文件中读取字符,使用字符输出函数fgetc,fgetc返回读取到的字符的ASCII码值。如果读到文件结束或者读取失败会返回EOF。可以使用循环和fgetc连续读取
#include#pragma warning(disable:4996) int main() { FILE* pf = fopen("test.txt","r"); if (pf == NULL) { perror("open fail"); } else { int a =0; while ((a = fgetc(pf)) != EOF) { printf("%c ", a); } fclose(pf); pf = NULL; } return 0; }
字符输出函数fputc和字符输入函数fgetc适用于所有输入输出流
int main()
{
//字符输入函数fgetc
int tmp = fgetc(stdin);
//字符输出函数fputc
fputc(tmp, stdout);
return 0;
}
当一个C程序运行时,默认打开了3个流,标准输入流stdin,就是键盘;标准输出流stdout,就是屏幕;还有一个标准错误流stderr。字符输入函数fgetc可以从标准输入流中get到数据,字符输出函数fputc可以将字符输出到标准输出流
标准输入流stdin,标准输出流stdout,标准错误流stderr都是FILE*类型的
文本行的输入函数fgets和文本行的输出函数fputs
fgets可以一次读取一行,fputs可以一次写入一行
int main()
{
FILE* pf = fopen("test.txt", "w");
if (!pf)
{
perror("open fail");
return 0;
}
fputs("qwert", pf);
fputs("12345", pf);
fclose(pf);
pf = NULL;
return 0;
}
fgets一次读取一行,连续使用fgets时,会在读完一行之后文件指针自动跳转到下一行,然后再次读取下一行的内容。fgets第二次读取到的内容存到数组中会覆盖数组中的原有内容
格式化的输出函数fprintf和格式化的输入函数fscanf.
这两个函数与scanf和printf不同的是,scanf只适用于标准输入流,printf只适用于标准输出流,而fprintf适用于所有输出流,fscanf适用于所有输入流。
将格式化的数据写入文件:
#include#pragma warning(disable:4996) struct Person { int age; char name[20]; float score; }p1={20,"Tom",90.5f}; int main() { FILE* pf = fopen("test.txt", "w"); if (!pf) { perror("open fail"); return 0; } fprintf(pf, "%d %s %f", p1.age, p1.name, p1.score); fclose(pf); pf = NULL; return 0; }
fprintf适用于所有输出流,所以fprintf也适用于stdout
#include#pragma warning(disable:4996) struct Person { int age; char name[20]; float score; }p1={20,"Tom",90.5f}; int main() { FILE* pf = fopen("test.txt", "w"); if (!pf) { perror("open fail"); return 0; } fprintf(stdout, "%d %s %f", p1.age, p1.name, p1.score); //相当于printf("%d %s %f", p1.age, p1.name, p1.score) return 0; }
fscanf适用于所有输入流,可以格式化的读取数据
#include#pragma warning(disable:4996) struct Person { int age; char name[20]; float score; }p1; int main() { FILE* pf = fopen("test.txt", "r"); if (!pf) { perror("open fail"); return 0; } fscanf(pf, "%d %s %f", &p1.age, p1.name, &p1.score); fprintf(stdout, "%d %s %f", p1.age, p1.name, p1.score); fclose(pf); return 0; }
fscanf适用于所有输入流,包括stdin
struct Person
{
int age;
char name[20];
float score;
}p1;
int main()
{
fscanf(stdin, "%d %s %f", &p1.age, p1.name, &p1.score);
fprintf(stdout, "%d %s %f", p1.age, p1.name, p1.score);
return 0;
}
对比scanf,printf,fscanf,fprintf,sscanf,sprintf
scanf:针对标准输入流stdin,是标准输入函数
fscanf:针对所有输入流,是格式化的输入函数
sscanf:与流无关,sscanf的作用是把字符串转化为格式化的数据
sscanf举例:
struct Person
{
int age;
char name[20];
float score;
}p1;
int main()
{
char arr[] = "10 Tom 60";
//sscanf将字符串转化为格式化的数据
sscanf(arr, "%d %s %f", &p1.age, p1.name, &p1.score);
fprintf(stdout, "%d %s %f", p1.age, p1.name, p1.score);
return 0;
}
printf:针对标准输出流stdout,是标准输出函数
fprintf:针对所有输出流,是格式化的输出函数
sprintf:与输出流无关,作用是把格式化的数据转化为字符串存起来
sprintf举例
struct Person
{
int age;
char name[20];
float score;
}p1={10,"Tom",60};
int main()
{
char tmp[40] ={0};
sprintf(tmp, "%d %s %f", p1.age, p1.name, p1.score);
printf("%s", tmp);
return 0;
}
fputc,fgetc,fputs,fgets,sprintf,sscanf都适用于所有输入输出流,包括文件和标准输入输出流
fread与fwrite
fread与fwrite适用文件流,不适用于标准输入输出。fread和fwrite是二进制的方式读写文件
fwrite:以二进制的方式写入
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
struct Person
{
int age;
char name[20];
float score;
}p1={10,"Tom",60};
int main()
{
FILE* pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("open fail");
return 0;
}
//将p1的数据以二进制写入到文件中
fwrite(&p1, sizeof(struct Person), 1, pf);
fclose(pf);
return 0;
}
fread:二进制的方式读取,返回值是实际读取到的元素的个数
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
struct Person
{
int age;
char name[20];
float score;
}p1;
int main()
{
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL)
{
perror("open fail");
return 0;
}
//将p1的数据以二进制写入到文件中
fread(&p1, sizeof(struct Person), 1, pf);
fprintf(stdout, "%d %s %f", p1.age, p1.name, p1.score);
fclose(pf);
return 0;
}
文件的随机读写:
上面的fputc,fgetc,fputs,fgets,fscanf,fprintf,fread.fwrite只支持文件的顺序读写,即从前往后进行文件的读写操作。除此之外,文件是支持随机读写的
文件的随机读写通过文件指针实现。使用fseek函数改变文件指针的位置,使得读写文件都从新的文件指针的位置开始。
int fseek( FILE *stream, long offset, int origin );
其中offset表示偏移量,origin表示从什么位置开始偏移,origin有三类:
SEEK_CUR
Current position of file pointer
SEEK_END
End of file
SEEK_SET
Beginning of file
例如:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("open fail");
return 0;
}
fseek(pf, -2, SEEK_END);//表示把文件指针定位到文件尾部向前2位的位置
fseek(pf, 2, SEEK_SET);//表示把文件指针定位到文件开始处向后2位的位置
fseek(pf, 1, SEEK_CUR);//表示把文件指针定位到当前位置的下一个位置
fclose(pf);
return 0;
}
可以使用fseek函数来调整文件指针的位置。如果想要直到当前文件指针的位置,可以使用ftell函数。
ftellGets the current position of a file pointer.
long ftell( FILE *stream );
ftell可以得到文件指针相对于起始位置的偏移量
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("open fail");
return 0;
}
fseek(pf, -2, SEEK_END);
printf("%d", ftell(pf));
//如果fseek中的offset=-2,origin=SEEK_END,则ftell(pf)+2可以得到文件中有多少个字符
fclose(pf);
return 0;
}
重置文件指正,让其回到起始位置的方法:
1,通过fseek函数,设置偏移量为0,origin为SEEK_SET
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("open fail");
return 0;
}
fseek(pf, -2, SEEK_END);
fseek(pf, 0, SEEK_SET);
printf("%d", ftell(pf));
fclose(pf);
return 0;
}
2,直接使用rewind函数来进行重置
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("open fail");
return 0;
}
fseek(pf, -2, SEEK_END);
rewind(pf);
printf("%d", ftell(pf));
fclose(pf);
return 0;
}
rewind(pf)的功能相当于fseek(pf,0,SEEK_CUR)
C语言文件操作中容易被误解的函数feof
Tests for end-of-file on a stream.
int feof( FILE *stream );
feof函数用于判断文件读取结束的原因,到底是什么原因造成文件读取结束了?是因为文件读取完毕,读到EOF文件读取结束,还是因为在读取文件的时候发生了一些错误导致文件读取发生意外而结束?即feof不是判断文件是否读取结束,是判断文件读取结束的原因
文件读取结束的标志:
使用fgetc读取文件,当文件读取结束时,会返回EOF
使用fgets读取文件,当文件读取结束时,会返回NULL
使用fread读取文件,当实际读取到的个数小于想要读取到的个数时,说明读取结束
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
fread函数的返回值是实际读取到的个数,count是想要读取到的个数,当返回值小于count的时候,说明读取结束。
注意文件读取结束并不代表文件读完了,也有可能是发生错误,读取失败,也会使文件读取结束。而feof就是来判断读取结束的原因
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("open fail");
return 0;
}
int tmp = 0;
while ((tmp = fgetc(pf)) != EOF)
{
printf("%c ", tmp);
}
//文件读取结束
if (ferror(pf))
{
printf("文件读取结束的原因是读取过程中发生错误n");
}
else if (feof(pf))
{
printf("文件读取结束的原因是文件正常读取完毕,遇到EOF结束n");
}
fclose(pf);
return 0;
}
perror函数的返回值如果为真,表示文件读取结束的原因是读取过程中发生错误
feof函数的返回值如果为真,表示文件读取结束的原因是遇到EOF
判断二进制读取完毕的原因:
int main()
{
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL)
{
perror("open fail");
return 0;
}
char arr[50] = { 0 };
int i = 0;
while (fread(arr + (i++), 1, 1, pf))
{
;
}
//文件读取结束
if (ferror(pf))
{
printf("文件读取结束的原因是读取过程中发生错误n");
}
else if (feof(pf))
{
printf("文件读取结束的原因是文件正常读取完毕,遇到EOF结束n");
}
fclose(pf);
return 0;
}
将文件中的内容按行读取,判断读取完成的原因:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("open fail");
return 0;
}
char arr[50] = { 0 };
int i = 0;
while (fgets(arr,50,pf))//读取完毕返回NULL
{
printf("%s",arr);
}
//文件读取结束
if (ferror(pf))
{
printf("文件读取结束的原因是读取过程中发生错误n");
}
else if (feof(pf))
{
printf("文件读取结束的原因是文件正常读取完毕,遇到EOF结束n");
}
fclose(pf);
return 0;
}



