文件的简单读取
一,流的介绍
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出操作各不相同,为了方便程序员对各种设备进行操作,我们抽象出了流的概念,我们可以把流想象成流淌着字符的河。
在C语言程序中,针对⽂件、画⾯、键盘等的数据输⼊输出操作都是通过流操作的。
⼀般情况下,我们要想向流⾥写数据,或者从流中读取数据,都是要打开流,然后操作。
但是,我们平常在设计一些常见的程序的时候并不需要考虑流的问题
那是因为C语⾔程序在启动的时候,默认打开了3个流:
• stdin—标准输⼊流,在大多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。
• stdout—标准输出流,大多数的环境中输出⾄显示器界面,printf函数就是将信息输出到标准输出
流中。
• stderr—标准错误流,大多数环境中输出到显示器界⾯。
这是默认打开了这三个流,我们使⽤scanf、printf等函数就可以直接进⾏输⼊输出操作的。
stdin、stdout、stderr三个流的类型是: FILE* ,通常称为文件指针。
C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的
二,文件介绍
什么是文件,一般来说,磁盘(硬盘)上的⽂件是⽂件。
但是在程序设计中,我们⼀般谈的⽂件有两种:程序⽂件、数据⽂件。
2.1程序文件
程序文件包括源程序⽂件(后缀为.c),⽬标⽂件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
2.2数据文件
⽂件的内容不⼀定是程序,⽽是程序运⾏时读写的数据,⽐如程序运⾏需要从中读取数据的⽂件,或
者输出内容的⽂件。
根据数据的组织形式,数据⽂件被分为文本文件或者二进制文件。
二进制文件:数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的⽂件中,就是⼆进制⽂件
文本文件:如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的⽂件就是⽂本⽂件
演示:
文本文件
二进制文件
注意,二进制文件我们是不能直接打开的,
在VS上打开二进制文档的正确方式应该为:
最终打开的结果就为:
2.3文件指针
在前面我们说过流的概念,
C语⾔程序在启动的时候,默认打开了3个流stdin、stdout、stder,这三个流的类型是: 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;
不同的C编译器的FILE类型包含的内容不完全相同,但是⼤同⼩异。每当打开一个文件,系统会根据文件情况自动创建一个FILE结构的变量,⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
我们可以定义一个指针变量指向FILE类型数据;
FILE*pf;//文件指针变量
pf指向某个文件的文件信息区,通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接找到与它关联的⽂件,并对其进行修改。
2.4文件的打开与关闭
要读写文件前我们应该先打开一个文件,在读写结束后再将文件关闭。在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,相当于建⽴了指针和⽂件的关系。
ANSIC规定使⽤ fopen 函数来打开⽂件, fclose 来关闭⽂件。
我们来看一下这两个函数的原型:
//fopen
FILE * fopen ( const char * filename, const char * mode );
//fclose
int fclose ( FILE * stream );
fopen函数需要两个参数,第一个参数为需要打开的文件名,
mode则表⽰⽂件的打开模式,下⾯都是⽂件的打开模式:
⽂件使⽤⽅式 | 含义 | 如果指定⽂件不存在 |
---|---|---|
“r”(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
“w”(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建⽴⼀个新的⽂件 |
“a”(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“rb”(只读) | 为了输⼊数据,打开⼀个⼆进制⽂件 | 出错 |
“wb”(只写) | 为了输出数据,打开⼀个⼆进制⽂件 | 建⽴⼀个新的⽂件 |
“ab”(追加) | 向⼀个⼆进制⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“r+”(读写) | 为了读和写,打开⼀个⽂本⽂件 | 出错 |
“w+”(读写) | 为了读和写,建议⼀个新的⽂件 | 建⽴⼀个新的⽂件 |
“a+”(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 | 建⽴⼀个新的⽂件 |
“rb+”(读写) | 为了读和写打开⼀个⼆进制⽂件 | 出错 |
“wb+”(读写) | 为了读和写,新建⼀个新的⼆进制⽂件 | 建⽴⼀个新的⽂件 |
ab+”(读写) | 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 | 建⽴⼀个新的⽂件 |
这里我们简单测试一下“r”和“w”;
演示:“r”
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
可以看到,当不存在我们所输入的文件的文件名时,perror函数进行了错误提醒;
但,当存在我们所输入的文件的文件名时,就可以正常的打开:
演示:“w”:
程序运行前:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
运行后:
可以看到,系统正常运行并创建了一个text.txt文档。
注意,我们关闭文件后要及时将文件指针置为空
三,文件读写
3.1文件的顺序读写
顺序读写函数介绍
函数名 | 功能 | 适用于 |
---|---|---|
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件 |
fwrite | 二进制输出 | 文件 |
这些函数对文件进行读写时,光标都是从前往后按顺序进行读写的,所以被称为顺序读写。
下面分组演示各组函数的读写方式
1,fgetc与fputc
fputc
#include<stdio.h>
//*fputc*字符输出函数
int main()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputc('a',pf);
fputc('b',pf);
fputc('c',pf);
fclose(pf);
pf = NULL;
return 0;
}
fputc可以将所给字符输出到指针文件所指向的文件当中。
fgetc
我们首先要知道,使用fgetc去读取文件中的字符时,若读取成功,返回的是读取的字符的ASCII码值,若读取失败,返回的是EOF(-1);
//*fgetc*字符输入函数
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
ch=fgetc(pf);
printf("%c", ch);
fclose(pf);
pf = NULL;
return 0;
}
我们使用fgetc函数对上面使用fputc函数写入字符的文件进行读取,并打印
2,fgets与fputs
fputs
fputs函数原型为:
int fputs ( const char * str, FILE * stream );
就是将一个字符串写到该文件指针指向的文件中
//*fputs*文本行输出函数
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("how are you\n", pf);
fputs("I am fine\n", pf);
fclose(pf);
pf = NULL;
return 0;
}
fgets
fgets函数原型为:
char * fgets ( char * str, int num, FILE * stream );
该函数可以从指针指向的文件中读取num个字符放入字符串str中,
#include<stdio.h>
//*fges*文本行输入函数
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[100] = { 0 };
fgets(arr,20,pf);
printf("%s", arr);
fgets(arr, 20, pf);
printf("%s", arr);
fclose(pf);
pf = NULL;
return 0;
}
注意
fgets在读取文件中的内容时,只会读取num-1个字符,最后一个字符会自动填入‘\0’;
实例:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[100] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
fgets(arr,6, pf);
printf("%s", arr);
fclose(pf);
pf = NULL;
return 0;
}
fgets在读取文件时,读取成功返回一个指针,若到结尾或者读取失败则返回一个空指针。
3.2文件的随机读写
上面的几个函数在进行读写时,光标都是按顺序逐个读取并移动的,我们可以通过以下几个函数来对光标的位置进行控制来实现文件的随机读写
3.2.1fseek
该函数的作用是根据⽂件指针的位置和偏移量来定位⽂件指针。函数原型为:
int fseek ( FILE * stream, long int offset, int origin );
表示将文件指针在文件中的位置以某个位置(origin)为起点,偏移offset个单位长度。
int origin表示偏移的起始位置,起始位置有三个选择
Constant | Reference position |
---|---|
SEEK_SET | Beginning of file(文件的起始位置) |
SEEK_CUR | Current position of the file pointer(文件指针的当前位置) |
SEEK_END | End of file *(文件的末尾位置) |
演示:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("This is an apple.", pf);
fseek(pf, 9, SEEK_SET);//将文件指针向后偏移9位
fputs(" sam", pf);//从偏移后的位置输出
fclose(pf);
pf = NULL;
return 0;
}
第一次puts后,“text.txt”文件中的内容为:
然后使用fseek将文件指针从文件起始位置向后偏移九位,然后进行第二次puts,最终结果为:
注意,若从某个位置向后偏移,则offset为正整数,若要向前偏移,则offset为负整数
3.2.2ftell
该函数的作用是返回⽂件指针相对于起始位置的偏移量。
函数原型为:
long int ftell ( FILE * stream );
函数演示:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fputs("abcdefg", pf);
fseek(pf,-3, SEEK_END);//将文件指针从文件末尾向前偏移3位
printf("%d\n", ftell(pf));
fclose(pf);
pf = NULL;
return 0;
}
偏移完后,文件指针位于e前,此时相对于起始位置偏移量为4,所以输出结果为4
3.2.1rewind
该函数的作用是让⽂件指针的位置回到⽂件的起始位置
函数原型为:
void rewind ( FILE * stream );
函数演示:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf,-3, SEEK_END);//将文件指针从文件末尾向前偏移3位
int ch = 0;
ch=fgetc(pf);//读取当前的字符并打印,读取完后指针会自动向后偏移一位
printf("%c\n", ch);
//此时指针指向的是f,让指针回到起始位置在读取一个字符并打印
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
四,⽂件读取结束的判定
4.1feof与ferror
feof函数的作用是当⽂件读取结束的时候,判断读取结束的原因是否是:遇到⽂件尾结束。注意!不能⽤feof函数的返回值直接来判断⽂件的是否结束
在判断文件读取结束时,有两个函数能用来判断读取结束的原因为:
feof和ferror
文件读取结束一般有两个原因:
1,遇到文件末尾;
2,读取过程中出现错误;
而在我们打开一个流的时候,流上会有两个标记值:
1,是否遇到文件末尾;(feof来检查)
2,是否发生错误;(ferror来检查)
下面,我们来详细介绍如何使用feof判断读取结束的原因是否是:遇到⽂件尾结束。
4.1.1feof
1,文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
·fgetc判断返回值是否为EOF;
·fgets判断返回值是否为NULL;
我们来看一下feof的函数原型:
int feof ( FILE * stream );
可以看到,feof需要的参数是一个文件指针,而其返回值为一个int整形,在实际使用的时候,feof会去检查对应的标记值,若文件读取结束是因为遇到末尾,那么其就会返回一个非零值,如果不是,则返回一个零,
演示:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.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("读取到文件末尾,正常结束");
}
fclose(pf);
pf = NULL;
return 0;
}
4.2.2ferror
ferror函数原型为:
int ferror ( FILE * stream );
该函数的参数为文件指针,返回值为一个整形值,在实际使用时,当文件读取遇到问题结束时,流上的标记值会被设置,函数就会去检查该标记:
函数演示:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
for (char ch = 'a';ch <= 'z';ch++)
{
putc(ch, pf);
}
if (feof(pf))//先检查是否是正常结束
{
printf("读取到文件末尾,正常结束");
}
else if(ferror(pf))
{
perror("fputc:");
}
fclose(pf);
pf = NULL;
return 0;
}
这里我们在读取文件的条件下进行写入,会报错,feof和ferror会检查错误原因:
注意,feof和ferror要一起使用,因为两函数判断结束的原因并不一样