C/C++爬虫篇(1)文件管理续篇之文件结束符


目录

  • C语言文件结束处理
  • C++语言文件结束处理

1- C语言文件结束处理

在C语言中,读取一个文件的操作步骤如下
在这里插入图片描述

在读取一个的时候,程序是如何知道文件读取结束,并返回的呢?
读取文件的方法有以下:
1、fgetc(FILE*fp)
逐个字符读取文件,当遇到换行符(\r\n或\n)、空格的时候,该函数并不会停止对文件的读取,只有当该函数读取到文件结尾的时候,即读取完文件最后一个字符的时候,才结束对文件的读取,并返回文件结束符(EOF);当读取到换行符时,换行符会保存换行符到数组中。
2、fgets(char *s, int size, FILE*fp)
第一个参数:保存读取文件数据的内存空间(数组等)
第二个参数:将要读取文件size个字节。以换行符(\r\n或\n)或EOF为结束读取文件的标记,当fgets读到这些字符的时候,就会返回并结束对文件的读取。当读取到换行符时,结束读取的同时,换行符会保存换行符到数组中。并且如果数组内存空间足够大,当结束读取文件之后,则还会在数组最后一个字符后面添加一个’\0’字符,反之不添加。
第三个参数:存储各种数据的流。标准流有:stdin,stdout;文件流有自定义的FILE *fp。
(类似逐行读取)
3、fscanf(FILE*fp,"%s.%d…",&a,&b…)
格式化读取文件,这个函数可以类比成scanf的使用。scanf是从标准流中读取数据,而fscanf即能从标准流中读取数据,也能从其他流中读取数据,比如文件流。fscanf 对空格、制表符、换行符是相同对待的,不加区分的;%s会跳过前面的空白符,但是不会跳过后面的空白符;%c不会跳过空白符。当遇到结尾(EOF)时,返回文件结束符(EOF)。

注意:文件结束符并不存在于文件中,我们常说的文件结束符只是一个标记,是不存在文件的,只能简单的判断是否读取文件结束。当读取文件错误时,相关函数也会返回文件结束符(EOF)的。

获取文件大小的方法
方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio>
int main()
{
    //打开一个文件
    FILE*fp=open("test.txt","rb");
    //获取文件大小
    int size=0;
    //将文件指针偏移到文件末尾。即从文件首个字符,偏移到文件最后一个字符的后面(-1)。
    fseek(fp,0,SEEK_END);
    //计算偏移量并返回
    size=ftell(fp);
    //重置文件指针,即将文件指针指向第一个字符
    rewind(fp);
    cout<<"文件大小为:"<<size<<"字节"<<endl;
    return 0;
}

假如test.txt文件内容如下

123 456
abc

则控制台显示的结果如下

文件大小为:12字节

为什么不是9个字节或者10个字节或者13个字节呢?原因很简单,123到456之间有一个空格符,而456后面有个换行符(\r\n),这两个符号也会计算在内,空格符当做一个字节,\r当做一个字节,\n也是一个字节,但文件结束符(EOF)是不存在于文件中的,EOF也就是不计算在内,所以是12个字节。

再比如,test.txt文件内容如下:

123 456
abc\r\n

则控制台显示的结果如下

文件大小为:16字节

这又是为什么呢?原因是,读取文件的时候,并不会把\r\n当换行符来处理,而是视之为一串字符串来处理,\是一个字符,r和n也是一个字符。所以一共16个字节。(注意文件结束符不计算在内)
声明:以上方式计算文件大小都不会把文件结束符统计到文件大小。

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int main()
{
    //打开一个文件
    FILE*fp=open("test.txt","rb");
    //获取文件大小
    int size=0;
    char ch;
    while(feof(fp)==0)
    {
        ch=fgetc(fp);
        size+=1;
    }
    cout<<"文件大小为:"<<size<<"字节"<<endl;
    return 0;
}

假如test.txt文件内容如下

123 456
abc

则控制台显示的结果如下

文件大小为:13字节

咦?为甚是13?而不是12?原因是feof函数在做鬼.
feof(FILE*fp):其功能是检测流上的文件结束符,如果文件结束,则返回非0值,否则返回0(即,文件结束:返回非0值;文件未结束:返回0值)。

注意:feof判断文件结束是通过读取函数fread/fscanf等返回错误来识别的,故而判断文件是否结束应该是在读取函数之后进行判断。比如,在while循环读取一个文件时,如果是在读取函数之前进行判断,则如果文件最后一行是空白行,可能会造成内存错误。
EOF是文本文件结束的标志。在文本文件中,数据是以字符的ASCⅡ代码值的形式存放,普通字符的ASCⅡ代码的范围是32到127(十进制),EOF的16进制代码为0xFF(十进制为-1),因此可以用EOF作为文件结束标志。
当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ASCI C提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
“C”语言的“feof()”函数和数据库中“eof()”函数的运作是完全不同的。数据库中“eof()”函数读取当前指针的位置,“C”语言的“feof()”函数返回的是最后一次“读操作的内容”。多年来把“位置和内容”相混,从而造成了对这一概念的似是而非。
那么,位置和内容到底有何不同呢?举个简单的例子,比如有人说“你走到火车的最后一节车箱”这就是位置。而如果说“请你一直向后走,摸到铁轨结束”这就是内容。也就是说用内容来判断会“多走一节”。这就是完全依赖于“while(!feof(FP)){…}”进行文件复制时,目标文档总会比源文档“多出一些”的原因。
——引用百度百科

也就是说,在feof前面没有任何读取文件的操作时,无论文件是否为空,feof总是执行多一次判断文件,并这一次总是返回0。它的作用原理,有点类似do{}while(条件)循环,无论条是否满足,总是会先执行一次循环体。所以上面文件内容大小为12+1=13。

再比如,一个文件test.txt内容为空,如下:

则控制台显示的结果如下

文件大小为:1字节

这个字节,就是因为:在feof前面没有任何读取文件的操作时,无论文件是否为空,feof总是执行多一次判断文件,并这一次总是返回0。

假如代码改成如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
int main()
{
    //打开一个文件
    FILE*fp=open("test.txt","rb");
    //获取文件大小
    int size=0;
    char ch;
    ch=fgetc(fp);//在feof前面加上先读取一次文件的操作
    while(feof(fp)==0)
    {
        ch=fgetc(fp);
        size+=1;
    }
    cout<<"文件大小为:"<<size<<"字节"<<endl;
    return 0;
}

比如,一个文件test.txt内容为空,如下:

则控制台显示的结果如下

文件大小为:0字节

原因是,在feof前面的fgetc把文件指针先移动到了文件末尾,文件末尾的内容是文件结束符eof(文件结束符并不存在文件中,只是读取一个文件的一个标记符)。所以,feof函数在判断的时候,读取到了文件指针指向的内容为eof,因此返回非0值,文件读取结束。

总结:

1、总的来说,文件大小的获取可以通过偏移量来计算,还可以通过,在读取文件的时候,判断是否读取文件结束,来计数文件大小来获取。在feof前面没有任何读取文件的操作时,feof总是多判断一次文件结束,并这一次总是返回0。原因是,feof判断的是文件的内容,而不是位置。

比如,
在一个数组大小为1的a数组,经过fseek偏移到末尾之后,统计偏移量函数ftell会返回0,因为fseek偏移的是位置,而数组a只有1个内存大小,所以首部位置和尾部位置都是同一个位置,也就是偏移量为0。而feof函数会先判断数组a的内容,是否符合feof返回的条件,符合返回0,不符合,则返回非0.

2、统计文件大小的时候,会把换行符(\r \n或\n),空格符(),制表符(\t)等计算在内
3、

(1)EOF(End of file)是C/C++里面bai的宏定义du,具体定义式是#define EOF -1,表示的是文件的结束标志,值等于-1,一般用在文件读取的dao函数里面,比如fscanf fgetc fgets等,一旦读取到文件最后就返回EOF标志并结束函数调用
(2)’\0’是转义字符,值等于0,主要用在C风格字符串的末尾,表示字符串结束标志。通常用在和字符串相关的函数里面,如strcmp strcpy等会用到它
(3)’\n’表示换行符,通常用作一些读取函数的读取结束标志,比如scanf,getchar(),gets()等,一旦遇到’\n’就结束读取并返回
——引用百度百科

4、当文件打开方式为二进制的时候,换行符会被解析成"\r\n",否则会被解析成"\n"存储。

2- C++语言文件结束处理

在C++语言中,读取一个文件的操作步骤如下
在这里插入图片描述

读取文件的方式:
(1)>>:
当遇到换行符或文件结束符时,结束读取文件。换行符不会被存储到数组中,但会从流中取走,下一次再读取的时候,不会读取到该换行符。当读取到文件末尾时,结束对文件的读取,返回EOF。
(2)object.read(char* buffer, int count):
第一参数:指向存储有数据的内存,这块内存将要存储从文件读取的数据。
第二参数:声明将有count个字节会从文件中读取出来,保存到buffer指向的内存中。

说明:当声明读取文件为以二进制模式读取时,换行符会被解析成"\r\n"存储在指定内存中;当不是以二进制读取文件时,换行符会被解析为"\n"存储在指定内存中。

说明:如果想知道read从文件中成功读取了多少个字节,可以使用object.gcount()来获取read函数最近一次成功读取的字节数,来统计从文件读取字节总数。gcount()函数返回的是,read函数最近一次成功从文件读取的字节数。因为读到文件结尾,也未必是读取了count个字节。比如,当count>文件大小时。

该函数会受文件指针的影响,即是从文件指针位置开始读取。与其他c++读取文件函数一样。当读取到文件末尾时,结束对文件的读取。或读取了count个字符后结束对文件的读取

(3)object.get(ch):
从文件中读取一个字符,换行符会被解析成"\r\n"存储在指定内存中。当读取到文件末尾时,结束对文件的读取
(4)getline(char* buf, int bufSize):
第一参数:指向存储有数据的内存,这块内存将要存储从文件读取的数据。
第二参数:声明将有bufSize个字节会从文件中读取出来,保存到buffer指向的内存中。

(1)当遇到换行符时,读取文件结束。
(2)当读取了bufSize个字节后,读取文件结束。
(3)遇到文件结束符时,读取文件结束。

(5)getline(char* buf, int bufSize, char delim):
第一参数:指向存储有数据的内存,这块内存将要存储从文件读取的数据。
第二参数:声明将有bufSize个字节会从文件中读取出来,保存到buffer指向的内存中。
第三参数:声明读取到delim这个字符时,停止对文件的读取,并只保存这个字符之前的数据。

(1)当遇到换行符时,读取文件结束。
(2)当读取了bufSize个字节后,读取文件结束。
(3)遇到文件结束符时,读取文件结束。
(4)当遇到delim这个字符时,读取文件结束。并且把流中的delim字符移除,下一次读取时,不会在遇见当前delim字符。

获取文件大小的方法
方法一

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
    ifstream infile("test.txt",ios::in);
    //读 偏移量
    infile.seekg(0, ios::end);
    int len = infile.tellg();
    cout << "文件大小为:" << len << "字节" << endl;
    return 0;
}

test.txt文件内容如下

abc
1 2

控制台显示的内容如下

文件大小为:8字节

方法二
错误演示

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
    ofstream outfile("test.txt",ios::out);
    //读 偏移量
    infile.seekg(0, ios::end);
    int len = infile.tellg();
    cout << "文件大小为:" << len << "字节" << endl;
    return 0;
}

test.txt文件内容如下

abc
1 2

控制台显示的内容如下

文件大小为:0字节

为什么会是0呢?原因是,当仅仅以ios::out模式打开文件时,该文件的内容会被清除,所以偏移时,偏移了0个偏移量。

正确演示

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
    ofstream outfile("test.txt",ios::out|ios::in);
    //读 偏移量
    infile.seekg(0, ios::end);
    int len = infile.tellg();
    cout << "文件大小为:" << len << "字节" << endl;
    return 0;
}

test.txt文件内容如下

abc
1 2

控制台显示的内容如下

文件大小为:8字节

只需添加上ios::in只读即可.

方法三

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
    ifstream infile("test.txt",ios::in);
    //读数据的过程中统计读取数据的字节个数
    char ch;
    int len=0;
    while(infile.get(ch))
    cout << "文件大小为:" << len << "字节" << endl;
    return 0;
}

test.txt文件内容如下

abc
1 2

控制台显示的内容如下

文件大小为:7字节

二进制打开文件的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
    ifstream infile("test.txt",ios::in|ios::binary);
    //读数据的过程中统计读取数据的字节个数
    char ch;
    int len=0;
    while(infile.get(ch))
    cout << "文件大小为:" << len << "字节" << endl;
    return 0;
}

test.txt文件内容如下

abc
1 2

控制台显示的内容如下

文件大小为:8字节

为什么一个是7一个是8呢?因为以二进制打开文件读的时候,换行符会被解析成"\r\n",反之会被解析成"\n"。

总结

1、获取文件大小的方式有三种,一种是读偏移量计算,一种是写偏移量计算,还有一种读文件数据统计。
2、当文件打开方式为二进制的时候,换行符会被解析成"\r\n",否则会被解析成"\n"存储。

大总结

1、无论是c还是c++,当文件打开方式为二进制的时候,换行符会被解析成"\r\n",否则会被解析成"\n"存储。
2、偏移量计算文件大小的时候,换行符会被解析成"\r\n";不是二进制打开的文件,读取文件,并统计的方式计算文件大小,换行符会被解析成"\n";以二进制打开的文件,读取文件,并统计的方式计算文件大小,换行符会被解析成"\r\n"。
3、c语言中
(1)遇见EOF就停止读取文件的函数是:fgetc,fgets,fscanf
(2)遇见换行符(\r\n或\n)就停止读取文件的函数是:fgets
4、在c++语言中
(1)遇见EOF就停止读取文件的函数是:get,read,>>
(2)遇见换行符(\r\n或\n)就停止读取文件的函数是:>>

5、c语言写文件的函数:fputc,fputs,fwrite,fprintf
6、c语言读文件的函数:fgetc,fgets,fread,fscanf
7、c++语言写文件的方法:<<,put,write
8、c++语言读文件的方法:>>,get,getline,read