前言
对于程序设计来说,如果按文件功能的角度来分类的话,我们一般将文件划分为程序文件和数据文件;通常情况下,如果我们写的程序中没有使用到文件的话,我们输入/输出的所有数据都是存储在电脑的内存中,一但程序发生崩溃或退出,内存就会被回收,从而造成数据的丢失。
#文件名: 文件名包含3部分:文件路径 + 文件名主干 + 文件后缀,文件名是文件标识,如:D:\code\test.txt
这就是一个文件名。
1. 数据文件
根据数据的组织形式,我们的数据文件可以分为文本文件和二进制文件。
#二进制文件: 数据在内存中以二进制的形式进行存储,当我们直接以二进制输出到外存(磁盘)的文件中,以二进制形式存在的文件。
#文本文件: 如果想要文件在外存上以文本形式存在,即以ASCLL码
的形式表现,则要求我们在输出到外存(磁盘)前就进行ASCLL字符
形式的转换。
那么在了解了什么是二进制文件和文本文件之后,我们会好奇数据在文件中是以怎样的形式存储的呢?
字符一律使用
ASCLL码
形式存储,而数值型数据既可以以ASCLL码
的形式存储,也可以使用二进制的形式存储。如:对于一个整数10000,以两种不同的形式输出到外存中对磁盘的占用不同。
以·ASCLL码
输出到外存上会占用5个字节,以二进制形式输出到外存上会占用4个字节。
2. 与文件有关的信息
2.1 流和标准流
程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念。也就是说C程序对于文件、键盘、画面等数据的输入输出操作都是针对流进行的。
我们知道上述数据的输入输出都是针对流来进行操作的,那么为什么当我们从键盘上输入数据到向屏幕上输出数据的时候,为什么没有打开流来进行操作呢?
C语言程序在启动时,默认会打开3个标准流:
stdin
: 标准输入流,scanf函数
就是从标准输入流(键盘等)中读取数据。stdout
:标准输出流,printf函数
就是将信息输出到标准输出流(显示器界面)中的。stderr
:标准错误流,大多数情况下输出到显示器界面
上述三个标准流的类型都是FILE *
,也就是我们常说的文件指针,通过文件指针FILE *
我们可以用来维护这些标准流的各种操作。
2.2 文件指针
文件指针,也就是文件类型指针,是用来对我们的文件信息进行维护的。
#文件信息区: 每个被使⽤的⽂件都会在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名 FILE
当我们打开一个文件的时候,系统会根据这个文件的情况自动创建一个
FILE
结构的变量,并填充其中的信息,然后我们通过一个指向FILE结构的指针来维护这个FILE结构体的变量。
从上述图片中,我们可以知道通过文件指针变量就能够间接找到与它相关联的文件。
2.3 文件的打开和关闭操作
在读写文件前应先打开文件,在试用结束后应该关闭文件,打开文件和关闭文件的函数:
fopen函数
//打开文件
FILE* fopen(const char * filename,const char * mode)
//关闭文件
int fclose(FILE * stream)
示例:
#include <stdio.h>
int main ()
{
FILE * pFile;
//打开⽂件
pFile = fopen ("filename.txt","w");
//⽂件操作
if (pFile==NULL)
{
return -1;
}
//文件操作
//...
//关闭⽂件
fclose (pFile);
return 0;
}
3. 文件的顺序读写
函数名 | 功能 | 适用于 |
---|---|---|
int fgetc(FILE *stream) | 从指定的流 stream (指向FILE对象的指针)获取下一个字符(一个无符号字符),并把位置标识符往前移动。 | 所有输入流 |
int fputc(int char, FILE *stream) | 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。 | 所有输出流 |
char *fgets(char *str, int n, FILE *stream | 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。 | 所有输入流 |
int fputs(const char *str, FILE *stream) | 把字符串写入到指定的流 stream 中,不包括标识字符串结束的\0 。 | 所有输出流 |
int fscanf(FILE *stream, const char *format, …) | 从流 stream 读取格式化输入。 | 所有输入流 |
int fprintf(FILE *stream, const char *format, …) | 发送格式化输出到流 stream 中 | 所有输出流 |
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) | 从给定流 stream 读取数据到 ptr 所指向的数组中。 | 文件 |
ize_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) | 把 ptr 所指向的数组中的数据写入到给定流 stream 中。 | 文件 |
示例: |
//(1) fgetc
int test1 ()
{
FILE *fp;
char ch;
fp = fopen("filename.txt","r");
if(fp == NULL)
{
perror("打开文件时发生错误");
return(-1);
}
//判断fgetc 读取字符是否遇到文件结尾/读取失败
while((ch = fgetc(pf)) != EOF) {
putchar(ch);
}
printf("\n");
fclose(fp);
return(0);
}
//(2) fputc
int test2 ()
{
FILE *fp;
char ch;
fp = fopen("filename.txt", "w+");
for( ch = 'A' ; ch <= "Z"; ch++ )
{
fputc(ch, fp);
}
fclose(fp);
return(0);
}
//(3) fgets
int test3()
{
FILE *fp;
char str[60];
/* 打开用于读取的文件 */
fp = fopen("filename.txt" , "r");
if(fp == NULL) {
perror("打开文件时发生错误");
return(-1);
}
if( fgets (str, 60, fp)!=NULL ) {
/* 向标准输出 stdout 写入内容 */
puts(str);
}
fclose(fp);
return(0);
}
//(4) fputs
int test4 ()
{
FILE *fp;
fp = fopen("filename.txt", "w+");
fputs("这是 C 语言。", fp);
fputs("这是一种系统程序设计语言。", fp);
fclose(fp);
return(0);
}
//(5) fscanf
int test5()
{
char str1[10], str2[10], str3[10];
int year;
FILE * fp;
fp = fopen ("filename.txt", "w+");
fputs("We are in 2024", fp);
rewind(fp);
fscanf(fp, "%s %s %s %d", str1, str2, str3, &year);
printf("Read String1 |%s|\n", str1 );
printf("Read String2 |%s|\n", str2 );
printf("Read String3 |%s|\n", str3 );
printf("Read Integer |%d|\n", year );
fclose(fp);
return(0);
}
//(6) fprintf
int test6()
{
FILE * fp;
fp = fopen ("filename.txt", "w+");
fprintf(fp, "%s %s %s %d", "We", "are", "in", 2024);
fclose(fp);
return(0);
}
//(7) fread
int test7()
{
FILE *fp;
char c[] = "This is freadTest";
char buffer[20];
/* 打开文件用于读写 */
fp = fopen("filename.txt", "wb+");
/* 写入数据到文件 */
fwrite(c, strlen(c) + 1, 1, fp);
/* 查找文件的开头 */
fseek(fp, 0, SEEK_SET);
/* 读取并显示数据 */
fread(buffer, strlen(c)+1, 1, fp);
printf("%s\n", buffer);
fclose(fp);
return(0);
}
//(8) fwrite
int test8 ()
{
FILE *fp;
char str[] = "This is fwriteTest";
fp = fopen( "filename.txt" , "wb" );
fwrite(str, sizeof(str) , 1, fp );
fclose(fp);
return(0);
}
注意:fread和fwrite是以二进制的形式输入输出,所以在打开文件时,注意选择对应的二进制使用模式
4. 文件的随机读写
4.1 fseek函数
//根据文件指针的位置和偏移量来定位文件指针位置
int fseek(FILE *stream, long int offset, int whence)
示例:
#include <stdio.h>
int main ()
{
FILE *fp;
fp = fopen("filename.txt","w+");
fputs("This is fseek test", fp);
fseek( fp, 7, SEEK_SET );
fputs(" C Programming Langauge", fp);
fclose(fp);
return(0);
}
4.2 ftell函数
//返回文件指针相当于起始位置的偏移量
long int ftell(FILE *stream)
示例:
#include <stdio.h>
int main ()
{
FILE *fp;
int len;
fp = fopen("filename.txt", "r");
if( fp == NULL )
{
perror ("打开文件错误");
return(-1);
}
fseek(fp, 0, SEEK_END);
len = ftell(fp);
fclose(fp);
printf("file.txt 的总大小 = %d 字节\n", len);
return(0);
}
4.3 rewind函数
//让文件指针的位置回到文件的起始位置
void rewind(FILE *stream)
示例:
#include <stdio.h>
int main()
{
char str[] = "This is rewind test";
FILE *fp;
int ch;
/* 首先让我们在文件中写入一些内容 */
fp = fopen( "filename.txt" , "w" );
fwrite(str , 1 , sizeof(str) , fp );
fclose(fp);
fp = fopen( "filename.txt" , "r" );
while((ch = fgetc(fp)) != EOF)
{
putchar(ch);
}
printf("\n");
//回到起始位置
rewind(fp);
while((ch = fgetc(fp)) != EOF)
{
putchar(ch);
}
printf("\n");
fclose(fp);
return(0);
}
#被误用的feof:
feof函数
的返回值不是用来判断文件是否结束的,而是用来判断当文件读取结束时,判断读取结束的原因是否为:遇到文件结尾
对于文件读取结束的判断条件:
- 文本文件的读取结束条件为判断其返回值是否为
EOF
/NULL
- 对于二进制文件的读取结束条件则为判断其返回值是否小于实际要读的个数