最近,我读了《计算机系统:体系结构和程序设计》一书。程序员的样子。” 在Unix I / O系统这一章中,作者提到文件末尾没有特殊字符EOF
。
如果您阅读或尝试过Unix / Linux I / O系统,或者编写了从文件中读取数据的C程序,那么对您来说,这句话似乎很明显。但是,让我们仔细看一下以下两个与我在书中发现的内容有关的陈述:
EOF
-这不是符号。- 文件末尾没有特殊字符。
这是EOF
什么EOF不是符号
为什么有人说或认为EOF
这是一个符号?我想这可能是因为某些用C编写的程序可以找到使用显式检查EOF
使用函数getchar()
和的代码getc()
。它可能看起来像这样: #include <stdio.h>
...
while ((c = getchar()) != EOF)
putchar(c);
或者: FILE *fp;
int c;
...
while ((c = getc(fp)) != EOF)
putc(c, stdout);
如果查看getchar()
或的帮助getc()
,您会发现这两个函数都从输入流读取下一个字符。可能-这正是导致对自然的误解的原因EOF
。但这只是我的假设。让我们回到一个想法EOF
-这不是一个符号。一般而言,什么是符号?符号是文本的最小组成部分。“ A”,“ a”,“ B”,“ b”-所有这些都是不同的符号。字符具有数字代码,在Unicode标准中,该代码称为代码点。例如,拉丁字母“ A”以十进制表示的代码为65。可以使用Python解释器的命令行快速检查该字母:$python
>>> ord('A')
65
>>> chr(65)
'A'
或者,您可以查看Unix / Linux上的ASCII表:$ man ascii
EOF
通过使用C编写一个小程序,
我们将找出对应的代码。在ANSI C中,EOF
定义了一个常量stdio.h
,它是标准库的一部分。通常写入此常量-1
。您可以将以下代码保存在文件中printeof.c
,进行编译并运行:#include <stdio.h>
int main(int argc, char *argv[])
{
printf("EOF value on my system: %d\n", EOF);
return 0;
}
编译并运行程序:$ gcc -o printeof printeof.c
$ ./printeof
EOF value on my system: -1
我有这个程序,在Mac OS和Ubuntu上,报道称,测试EOF
等于-1
。此代码有字符吗?同样,在这里,您可以检查ASCII表中的字符代码,可以查看Unicode表并找出字符代码的范围。我们将采取不同的行动:我们将启动Python解释器并使用标准函数chr()
为我们提供与代码相对应的符号-1
:$ python
>>> chr(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: chr() arg not in range(0x110000)
不出所料,带有代码的字符-1
不存在。因此,最后,EOF
事实并非象征。现在我们来考虑第二条陈述。文件末尾没有特殊字符。
也许EOF
-这是一个特殊字符,可以在文件末尾找到?我想你已经知道答案了。但是,让我们仔细检查一下我们的假设。取得一个简单的文本文件helloworld.txt,并以十六进制表示形式显示其内容。为此,您可以使用以下命令xxd
:$ cat helloworld.txt
Hello world!
$ xxd helloworld.txt
00000000: 4865 6c6c 6f20 776f 726c 6421 0a Hello world!.
如您所见,文件的最后一个字符具有一个code 0a
。从ASCII表中,您可以发现此代码对应于一个字符nl
,即换行符。您可以使用Python找出答案:$ python
>>> chr(0x0a)
'\n'
所以。EOF
-这不是符号,文件末尾没有特殊符号。这是EOF
什么什么是EOF?
EOF
(文件结束)是在文件读取操作到达末尾的情况下应用程序可以检测到的状态。让我们看看EOF
使用这些语言提供的高级输入输出工具读取文本文件时,如何以不同的编程语言检测状态。为此,我们将编写一个非常简单的版本cat
,称为mcat
。它读取ASCII文本字节(字符)并显式检查EOF
。我们将使用以下语言编写程序:- ANSI C
- Python 3
- 走
- JavaScript(Node.js)
这是带有示例代码的存储库。我们继续对其进行分析。ANSI C
让我们从古老的C开始。这里介绍的程序是cat
《 C编程语言》一书的修改版。
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
int c;
if ((fp = fopen(*++argv, "r")) == NULL) {
printf("mcat: can't open %s\n", *argv);
return 1;
}
while ((c = getc(fp)) != EOF)
putc(c, stdout);
fclose(fp);
return 0;
}
汇编:$ gcc -o mcat mcat.c
发射:$ ./mcat helloworld.txt
Hello world!
以下是有关上述代码的一些解释:- 程序打开作为命令行参数传递给它的文件。
- 循环将
while
数据从文件复制到标准输出流。数据逐字节复制,直到到达文件末尾为止。 - 程序到达时
EOF
,它将关闭文件并退出。
Python 3
在Python中,没有EOF
类似于ANSI C中可用的显式检查机制。但是,如果按字符读取文件,EOF
则如果保存下一个字符的变量为空,则可以显示状态:# mcat.py
import sys
with open(sys.argv[1]) as fin:
while True:
c = fin.read(1) # 1
if c == '': # EOF
break
print(c, end='')
运行程序并查看返回给它的结果:$ python mcat.py helloworld.txt
Hello world!
这是用Python 3.8+编写的同一示例的简短版本。这里使用了运算符:=(它被称为“海象运算符”或“海象运算符”):# mcat38.py
import sys
with open(sys.argv[1]) as fin:
while (c := fin.read(1)) != '': # 1 EOF
print(c, end='')
运行此代码:$ python3.8 mcat38.py helloworld.txt
Hello world!
走
在Go中,您可以显式检查Read()返回的错误,以查看它是否表明我们已到达文件末尾:
package main
import (
"fmt"
"os"
"io"
)
func main() {
file, err := os.Open(os.Args[1])
if err != nil {
fmt.Fprintf(os.Stderr, "mcat: %v\n", err)
os.Exit(1)
}
buffer := make([]byte, 1)
for {
bytesread, err := file.Read(buffer)
if err == io.EOF {
break
}
fmt.Print(string(buffer[:bytesread]))
}
file.Close()
}
运行程序:$ go run mcat.go helloworld.txt
Hello world!
JavaScript(Node.js)
Node.js没有用于显式检查的机制EOF
。但是,当到达文件末尾时,如果尝试读取其他内容,则会引发end stream事件。
const fs = require('fs');
const process = require('process');
const fileName = process.argv[2];
var readable = fs.createReadStream(fileName, {
encoding: 'utf8',
fd: null,
});
readable.on('readable', function() {
var chunk;
while ((chunk = readable.read(1)) !== null) {
process.stdout.write(chunk);
}
});
readable.on('end', () => {
console.log('\nEOF: There will be no more data.');
});
运行程序:$ node mcat.js helloworld.txt
Hello world!
EOF: There will be no more data.
低级系统机制
上面的示例中使用的高级I / O机制如何确定文件的结尾?在Linux上,这些机制直接或间接使用内核提供的read()系统调用。getc()
例如,来自C的函数(或宏)使用系统调用,read()
并EOF
在read()
指示到达文件末尾的状态时返回。在这种情况下,read()
返回0
。如果以图表形式描述所有这些内容,则会得到以下信息:事实证明,该功能getc()
基于read()
。我们将只使用Unix系统调用编写一个cat
命名版本syscat
。我们这样做不仅是出于兴趣,而且还因为它很可能为我们带来一些好处。这是用C语言编写的程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char c;
fd = open(argv[1], O_RDONLY, 0);
while (read(fd, &c, 1) != 0)
write(STDOUT_FILENO, &c, 1);
return 0;
}
运行:$ gcc -o syscat syscat.c
$ ./syscat helloworld.txt
Hello world!
此代码使用以下事实:read()
表明文件末尾的函数返回0
。这是用Python 3编写的相同程序:# syscat.py
import sys
import os
fd = os.open(sys.argv[1], os.O_RDONLY)
while True:
c = os.read(fd, 1)
if not c: # EOF
break
os.write(sys.stdout.fileno(), c)
运行:$ python syscat.py helloworld.txt
Hello world!
这是用Python 3.8+编写的同一件事:# syscat38.py
import sys
import os
fd = os.open(sys.argv[1], os.O_RDONLY)
while c := os.read(fd, 1):
os.write(sys.stdout.fileno(), c)
也运行以下代码:$ python3.8 syscat38.py helloworld.txt
Hello world!
摘要
EOF
-这不是符号。- 文件末尾没有特殊字符。
EOF
-这是内核报告的状态,当数据读取操作到达文件末尾时,应用程序可以检测到该状态。- 在ANSI C中
EOF
,这也不是字符。这是定义的常量stdio.h
,通常在其中写入值-1。 EOF
在ASCII表或Unicode中找不到“字符” 。
亲爱的读者们!您是否了解计算机界或多或少的普遍误解?