文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径 + 文件名主干 + 文件后缀 。例如:E:\gitee\jie-828
为了方便起见,文件标识常被称为文件名
二进制文件和文本文件
根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件。
数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的⽂件中,就是⼆进制⽂件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂本⽂件。
例如:整数10000以ASCII的形式存储,则磁盘中占用5个字节(一个字符一个字节)
例如:整数10000以二进制的形式存储,则磁盘中只占4个字节
文件的打开和关闭
流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出 操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念。
C程序针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。
标准流
C语言程序启动的时候,默认打开了3个流,以便我们从键盘上输入数据,向屏幕上输出数据。
stdin - 标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
stdout - 标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
stderr - 标准错误流,大多数环境中输出到显示器界面。
这是默认打开了这3个流,我们使用scanf、printf等函数就可以直接进行输入输出操作。
stdin、stdout、stderr三个流的类型是:FILE*,通常成为文件指针。
C语言中,就是通过FILE*的文件指针来维护流的各种操作的。
文件指针
每个被使⽤的⽂件都在内存中开辟了⼀个相应的⽂件信息区,⽤来存放⽂件的相关信息(如⽂件的名 字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名 FILE。
VS2013编译环境提供的stdio.h头文件中有以下的文件类型申明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
每当打开⼀个⽂件的时候,系统会根据⽂件的情况⾃动创建⼀个FILE结构的变量,并填充其中的信 息。
⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体变 量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接找到与它关联的⽂件。
FILE* pf;//文件指针变量
文件打开与关闭
⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。
在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了 指针和⽂件的关系。
ANSI C 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件。
//打开文件
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 | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件输入流 |
fwrite | 二进制输出 | 文件输出流 |
fgetc
从流中获取字符
int fgetc( FLIE* stream );
函数使用
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");//以只读的方式打开
if (pf == NULL)//判断文件是否为空
{
perror("fopen");
return 1;
}
//读文件
int ch = 0;
ch = fgetc(pf);//读取一个字符
printf("%c\n", ch);//因为fgetc函数的返回值为int类型,所以要以%c的形式打印
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
打印文件中的所有字符
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = 0;
while ((ch = fgetc(pf)) != EOF)//在位置指示符位于文件末尾前打印字符
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputc
将字符写入流
int fputc( int character, FILE* stream );
函数使用
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");//以只写的方式打开文件
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
int i = 'a';
for (i = 'a'; i <= 'z'; i++)
fputc(i, pf);//将字符写入流
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgets
从流中获取字符串
char* fgets( char* str, int num, FILE* stream );
函数使用
1.读取num-1个字符
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char data[30] = "xxxxxxxxxxxxxxxxxxxx";
fgets(data, 9, pf);//test.txt中存储abcdefghi
printf("%s\n", data);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2.达到换行符
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char data[30] = "xxxxxxxxxxxxxxxxxxxx";
fgets(data, 12, pf);
printf("%s\n", data);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3.达到文件末尾
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char data[30] = "xxxxxxxxxxxxxxxxxxxx";
fgets(data, 20, pf);//要复制的字符个数大于文本文件中的字符个数
printf("%s\n", data);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputs
将字符串写入流
int fputs( const char* str, FILE* stream );
函数使用
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");//以只写的方式打开
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputs("abcdef\n", pf);
fputs("ghi", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fprintf
将格式化数据写入流
int fprintf( FILE* stream, const char* format, ... );
函数使用
struct S
{
char name[20];
int age;
float score;
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
struct S s = { "zhangsan",20,80.5f };
fprintf(pf, "%s %d %.1f", s.name, s.age, s.score);//从结构体中读取格式化数据,并且写入流
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fscanf
从流中读取格式化数据
函数使用
struct S
{
char name[20];
int age;
float score;
};
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
struct S s = { "zhangsan",20,80.5f };
struct S tmp = { 0 };
/*fprintf(pf, "%s %d %.1f", s.name, s.age, s.score);*///从结构体中读取格式化数据,并且写入流
fscanf(pf, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
//从流中读取格式化数据,并根据参数格式将其存储到其他参数指向的位置
//通过fprintf将数据导入到了流中,通过fscanf将其取出,存储到结构体tmp中,调fscanf需要取地址
printf("%s %d %.1f", tmp.name, tmp.age, tmp.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fwrite
将数据块写入流
size_t fwrite( const void* ptr, size_t size, size_t count, FILE* stream);
函数使用
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { "zhangsan",20,80.5f };
//打开文件
FILE* pf = fopen("data.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fwrite(&s, sizeof(struct S), 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fread
从流中读取数据块
size_t fread( void* ptr, size_t size, size_t count, FILE* stream );
函数使用
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { 0 };
//打开文件
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
/*fwrite(&s, sizeof(struct S), 1, pf);*/
//从流中读取count元素数组,每个元素的大小为size字节,并将他们存储在ptr指定的内存块中
fread(&s, sizeof(struct S), 1, pf);
printf("%s %d %.1f", s.name, s.age, s.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
sprintf
将格式化数据写入字符串
int sprintf( char* str, const char* format, ... );
函数使用
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { "zhangsan",20,80.5f };
char arr[100] = { 0 };
sprintf(arr, "%s %d %.1f", s.name, s.age, s.score);//写入arr
printf("%s", arr);
return 0;
}
sscanf
从字符串中读取格式化数据
int sscanf( const char* s, const char* format, ... );
函数使用
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { "zhangsan",20,80.5f };
struct S tmp = { 0 };
char arr[100] = { 0 };
sprintf(arr, "%s %d %.1f", s.name, s.age, s.score);
/*printf("%s", arr);*/
//从arr中读取格式化数据存放到结构体tmp中
sscanf(arr, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
printf("%s %d %.1f", tmp.name, tmp.age, tmp.score);
return 0;
}
文件的随机读写
fseek
重新定位流位置指示器
int fseek( FILE* stream, long int offset, int origin );
函数使用
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
int ch = 0;
ch = fgetc(pf);//光标后移一位
printf("%c\n", ch);
ch = fgetc(pf);//光标后移一位
printf("%c\n", ch);
//SEEK_SET 文件开头
//SEEK_CUR 文件指针的当前位置
//SEEL_END 文件结束
fseek(pf, -2, SEEK_END);//将光标定位到从文件结束开始往前偏移量为2的位置
ch = fgetc(pf);
printf("%c\n", ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
ftell
获取流中的当前位置
long int ftell( FILE* stream );
函数使用
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读写文件
int ch = 0;
ch = fgetc(pf);//光标后移一位
printf("%c\n", ch);
//SEEK_SET 文件开头
//SEEK_CUR 文件指针的当前位置
//SEEL_END 文件结束
fseek(pf, -2, SEEK_END);//将光标定位到从文件结束开始往前偏移量为2的位置
ch = fgetc(pf);
printf("%c\n", ch);
printf("相对于起始位置的偏移量: %d\n", ftell(pf));
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
rewind
将流的位置设置为开头
void rewind( FILE* stream );
函数使用
int main()
{
int n;
FILE* pFile;
char buffer[27];
pFile = fopen("myfile.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, pFile);//往流中写入'A'~'Z'26个英文字母,文件指针位置指向最后一个字母
rewind(pFile);//让文件指针的位置回到文件的起始位置
fread(buffer, 1, 26, pFile);//将流中的26个字母存储在字符数组buffer中
fclose(pFile);
buffer[26] = '\0';
printf(buffer);
return 0;
}
文件读取结束的判定
feof
检查文件结束指示器
int feof( FILE* stream );
feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。
函数使用
1.文本文件读取是否结束
判断返回值是否为EOF(fgetc),或者NULL(fgets)
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w+");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int n = 0;
for (n = 'a'; n <= 'z'; n++)
fputc(n, pf);
rewind(pf);
int ch = 0;
//当fgetc读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((ch = fgetc(pf)) != EOF)
{
putchar(ch);
}
printf("\n");
//判断是什么原因结束的
if (ferror(pf))
puts("I/O error when reading");
else if(feof(pf))
puts("End of file reached successfully");
fclose(pf);
pf = NULL;
return 0;
}
2.二进制文件读取是否结束
判断返回值是否⼩于实际要读的个数。
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* fp = fopen("test.txt", "wb");
fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
fclose(fp);
double b[SIZE];
fp = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
if (ret_code == SIZE) {
puts("Array read successfully, contents: ");
for (int n = 0; n < SIZE; ++n)
printf("%f ", b[n]);
putchar('\n');
}
else { // error handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
}
文件缓冲区
ANSIC 标准采⽤“缓冲⽂件系统” 处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为 程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输 ⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓 冲区的⼤⼩根据C编译系统决定的。
文件缓冲区显示代码
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("abcdef", pf);
//代码先放在缓冲区
printf("睡眠10秒\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区,才将缓冲区的数据写到文件中
printf("再睡眠10秒\n");
Sleep(10000);
fclose(pf);//关闭文件时,也会刷新缓冲区
pf = NULL;
return 0;
}
总结
文件操作函数的正确使用能够帮助我们更好得将数据进行持久化的保存,每个函数都有不同的参数,并且对文件进行读写操作时的打开方式也要有所注意。