1. 文件的打开和关闭
1.1 流和标准流
1.1.1 流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。
一般情况下,我们要想向流里写数据,或者从流中读取数据,都是要打开流,然后操作。
1.1.2 标准流
那为什么我们从键盘输⼊数据,向屏幕上输出数据,并没有打开流呢?
那是因为C语⾔程序在启动的时候,默认打开了3个流:
• stdin - 标准输⼊流,在⼤多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
• stdout - 标准输出流,⼤多数的环境中输出⾄显⽰器界⾯,printf函数就是将信息输出到标准输出流中。
• stderr - 标准错误流,⼤多数环境中输出到显⽰器界⾯。
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为文件指针。
C语⾔中,就是通过 FILE* 的文件指针来维护流的各种操作的。
1.2 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使⽤的⽂件都在内存中开辟了⼀个相应的文件信息区,⽤来存放⽂件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名 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;
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开⼀个文件的时候,系统会根据文件的情况⾃动创建⼀个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下⾯我们可以创建⼀个FILE*的指针变量:
FILE* pf;//⽂件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体变量)。通过该⽂件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件。
1.3 文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了指针和⽂件的关系。
ANSI C 规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件。
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );
mode表⽰⽂件的打开模式,下⾯都是⽂件的打开模式:
//例子
#include <stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
2. 文件的顺序读写
2.1 顺序读写函数介绍
上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输⼊流和其他输⼊流(如⽂件输⼊流);所有输出流⼀般指适⽤于标准输出流和其他输出流(如⽂件输出流)。
2.1.1 fputc
int fputc ( int character, FILE * stream );
代码:
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
//fputc('a', pf);
//fputc('b', pf);
//fputc('c', pf);
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果:
2.1.2 fgetc
int fgetc ( FILE * stream );
代码:
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", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
2.1.3 fputs
int fputs ( const char * str, FILE * stream );
代码:
int main()
{
//1. 打开文件
FILE*pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
fputs("hello world\n", pf);
fputs("i love you\n ", pf);
//2. 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果:
2.1.4 fgets
char * fgets ( char * str, int num, FILE * stream );
代码:
int main()
{
//1. 打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[20] = { 0 };
while (fgets(arr, 20, pf) != NULL)
{
printf("%s", arr);
}
//2. 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
2.1.5 fprintf
int fprintf ( FILE * stream, const char * format, ... );
代码:
#include <stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { "张三", 20, 65.5f };
//想把s中的数据存放在文件中
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件 - 是以文本的形式写进去的
fprintf(pf, "%s %d %f", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
结果:
2.1.6 fscanf
int fscanf ( FILE * stream, const char * format, ... );
代码:
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { 0 };
//想从文件test.txt中读取数据放在s中
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
//打印在屏幕上看看
//printf("%s %d %f\n", s.name, s.age, s.score);//
fprintf(stdout, "%s %d %f\n", s.name, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
2.1.7 sprintf
int sprintf ( char * str, const char * format, ... );
代码:
struct S
{
char name[20];
int age;
float score;
};
int main()
{
char buf[200] = { 0 };
struct S s = { "张三", 20, 65.5f };
sprintf(buf, "%s %d %f", s.name, s.age, s.score);
printf("1以字符串的形式: %s\n", buf);//1
struct S t = {0};
sscanf(buf, "%s %d %f", t.name, &(t.age), &(t.score));
printf("2按照格式打印 : %s %d %f\n", t.name, t.age, t.score);//2
return 0;
}
运行结果:
2.1.8 fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
代码:
int main()
{
int arr[] = { 1,2,3,4,5 };
FILE*pf = fopen("test.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写数据
int sz = sizeof(arr) / sizeof(arr[0]);
fwrite(arr, sizeof(arr[0]), sz, pf);//以二进制的形式写进去的
fclose(pf);
pf = NULL;
return 0;
}
结果(这是二进制):
2.1.9 fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
代码:
//以二进制的形式读取
int main()
{
int arr[5] = {0};
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读数据
fread(arr, sizeof(arr[0]), 5, pf);//以二进制的形式写进去的
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);//1 2 3 4 5
}
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
3. 文件的随机读写
3.1 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 = fgetc(pf);
printf("%c\n", ch);//a
//fseek(pf, 4, SEEK_CUR);
//fseek(pf, 5, SEEK_SET);
fseek(pf, -4, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);//f
fclose(pf);
pf = NULL;
return 0;
}
文本:
运行结果:
3.2 ftell
返回⽂件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
代码:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
//fseek(pf, -4, SEEK_END);
fseek(pf, 0, SEEK_END);
printf("%d\n", ftell(pf));//这样是不是也能得出字符个数
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
3.3 rewind
让⽂件指针的位置回到⽂件的起始位置
void rewind ( FILE * stream );
代码:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
int ch = fgetc(pf);
printf("%c\n", ch);//a
fseek(pf, -4, SEEK_END);
ch = fgetc(pf);
printf("%c\n", ch);//f
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);//a
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
4. 文件读取结束的判定
4.1 被错误使用的 feof
牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件的是否结束。
feof 的作用是:当文件读取结束的时候,判断是读取结束的原因是否是:遇到文件尾结束。
1. ⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
• fgetc 判断是否为 EOF .
• fgets 判断返回值是否为 NULL .
2. ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。
例如:
• fread判断返回值是否⼩于实际要读的个数。
代码1:
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);
}
//判断是什么原因导致读取结束的
if (feof(pf))
{
printf("遇到文件末尾,读取正常结束\n");
}
else if (ferror(pf))
{
perror("fgetc");
}
return 0;
}
运行结果:
代码2:
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}
//判断是什么原因导致读取结束的
if (feof(pf))
{
printf("遇到文件末尾,读取正常结束\n");
}
else if (ferror(pf))
{
perror("fputc");
}
return 0;
}
运行结果:
代码3:
//拷贝文件:test1.txt ---> test2.txt
int main()
{
FILE* pfin = fopen("test1.txt", "r");
if (pfin == NULL)
{
perror("fopen:test1.txt");
return 1;
}
FILE* pfout = fopen("test2.txt", "w");
if (pfout == NULL)
{
fclose(pfin);
perror("fopen:test2.txt");
return 1;
}
//读文件和写文件
int ch = 0;
while ((ch=fgetc(pfin)) != EOF)
{
fputc(ch, pfout);
}
//
fclose(pfin);
pfin = NULL;
fclose(pfout);
pfout = NULL;
return 0;
}
这里可以自己往test1.txt文件中写数据。
代码4:
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);
}
//判断是什么原因导致读取结束的
if (feof(pf))
{
printf("遇到文件末尾,读取正常结束\n");
}
else if (ferror(pf))
{
perror("fputc");
}
return 0;
}