1.5.输入和输出(Input and Output)
文件描述符(File Descriptors)
正常地,文件描述符是非负的小整数(small non-negative integers:0,1,2...)。文件描述符出现的原因是为了kernel能够识别一个进程正在存取的所有文件。无论何时kernel打开一个存在的文件或者创建一个新的文件,它都返回一个文件描述符,以便我们随后能够使用文件描述符来读或写文件。
标准输入,标准输出和标准出错(Standard Input, Standard Output, and Standard Error)
按惯例,无论何时一个新程序运行,任一种shell都会打开3个文件描述符:标准输入,标准输出和标准出错。If nothing special is done,这3个文件描述符都联系到终端(terminal)。大多数的shell都提供了一种方法,能将这3个文件描述符重新定向到任一个文件。例如,
ls > file.list
执行命令ls with它的标准输出重新定向到文件file.list。
-----------------------------
如果文件file.list不存在,会先创建文件file.list,然后再执行ls。
这样,就会出现这样的情况:
[lizl@hydra03 ~]$ ls
betweenExt3AndHfs
[lizl@hydra03 ~]$ ls > file.list
[lizl@hydra03 ~]$ more file.list
betweenExt3AndHfs
file.list
[lizl@hydra03 ~]$
-----------------------------
不用缓存的输入输出(Unbuffered I/O)
函数open, read, write, lseek以及close提供了不用缓存的I/O。这些函数都用文件描述符进行工作。
Example
如果我们愿意从标准输入读,并向标准输出写, 那么可以看程序figure1.4,该程序也可用于复制任一UNIX文件。
Figure 1.4. Copy standard input to standard output, using Unbuffered I/O
#include <unistd.h>
#include <iostream>
using namespace std;
#define BUFFSIZE 4096
int
main(void)
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
cout<<"write error!"<<endl;
if (n < 0)
cout<<"read error!"<<endl;
exit(0);
}
(第3章将更详细地说明不用缓存的I/O函数。)
头文件<unistd.h>和常量STDIN_FILENO,STDOUT_FILENO是POSIX标准的一部分(在下一章中我们会对POSIX进行更多的说明)。在头文件<unistd.h>中,有许多UNIX系统服务的函数原型(function prototypes for many of the UNIX system services),比如在figure1.4中使用的read和write。
常量STDIN_FILENO和STDOUT_FILENO定义在头文件<unistd.h>中,它们为标准输入和标准输出指定了文件描述符,它们的典型值是0和1,但是为了可移植性(portability),我们将使用这些新名字。
--------------------------------------
/usr/include/unistd.h
/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
---------------------------------------
section3.9将详细讨论BUFFSIZE常数,说明其各种不同的值将如何影响程序1.4的效率。但是不管该常数的值如何,此程序总能copies any regular file。
read函数返回读得的字节数,此值用作要写的字节数。当到达输入文件的end时,read返回0,程序停止执行(成功执行完毕)。如果发生了一个读错误,read返回-1。大多数系统函数在出错时返回-1。
If we compile the program(figure1.4) into the standard name (a.out) and execute it as
./a.out > data
那么,标准输入是终端,标准输出则重新定向至文件data,标准出错也是终端。如果文件data不存在,则shell默认地会创建它。程序1.4拷贝我们输入的行,然后送向标准输出。直到我们键入文件末尾字符(end-of-file character),通常是Control-D,才会跳出while循环。
如果我们这样执行:
./a.out < infile > outfile
那么文件infile将会被拷贝到文件outfile。(标准输入重定向到文件infile,标准输出重定向到文件outfile,标准出错仍是终端,这就是前面提到的能够copy any regular file。)
标准输入输出(Standard I/O)
标准I/O函数提供了一个面向不用缓存的I/O函数的带缓存的接口(a buffered interface to the unbuffered I/O functions,也就是在上面提到的unbuffered I/O之上建立的接口函数,那么standard I/O背后仍然是调用unbuffered I/O?)。使用标准I/O可无需担心如何选取最佳的缓存长度,例如如何确定figure1.4中的BUFFSIZE常量大小。另一个使用标准I/O函数的优点与处理行输入有关(行输入常常发生在UNIX的应用中)。例如,fgets函数读一完整的行,而另一方面,read函数则是读指定数量的字节。我们将在section5.4中看到,标准I/O库提供了一些函数that let us control the style of buffering used by the library.
我们最熟悉的标准I/O函数是printf。在调用printf的程序中,总是包含头文件<stdio.h>,因为此头文件包括了所有标准I/O函数的原型。
Example
Figure1.5的功能类似于调用read和write的前一个程序figure1.4,5.8节将对Figure1.5作更详细的说明。它将标准输入复制到标准输出,于是也就能复制任一UNIX文件(can copy any regular file)。
Figure 1.5. Copy standard input to standard output, using standard I/O
#include <stdio.h>
#include <iostream>
using namespace std;
int
main(void)
{
int c;
while ((c = getc(stdin)) != EOF)
if (putc(c, stdout) == EOF)
cout<<"output error"<<endl;
if (ferror(stdin))
cout<<"input error"<<endl;
exit(0);
}
函数getc一次读1个字符,然后putc将此字符写到标准输出。读完输入的最后1个字节后,getc返回常量EOF(定义在头文件<stdio.h>)。标准I/O常量stdin和stdout也定义在头文件< stdio.h>中,它们分别表示标准输入和标准输出。
-------------------------------------------
/* End of file character.
Some things throughout the library rely on this being -1. */
#ifndef EOF
# define EOF (-1)
#endif
/* Standard streams. */
extern struct _IO_FILE *stdin; /* Standard input stream. */
extern struct _IO_FILE *stdout; /* Standard output stream. */
extern struct _IO_FILE *stderr; /* Standard error output stream. */
#ifdef __STDC__
/* C89/C99 say they're macros. Make them happy. */
#define stdin stdin
#define stdout stdout
#define stderr stderr
#endif
--------------------------------------------
--------------------------------------------
1.也就是说,可以在任何一个程序里面直接使用文件描述符0,1,2(当然还是直接用STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO 好些,但前提是要#include <unistd.h>,而直接用0,1,2则不用#include <unistd.h>,但是有时候程序仍需要#include <unistd.h>却是由于使用了如read和write这样包含在该头文件中的函数,而不是由于使用0,1,2),而不需要去open来获得;事实上目前我也不知道open哪个文件。
2.标准输入和标准输出的重定向用<和>即可,那么标准出错用什么符号实现重定位?
3.不用缓存的I/O函数才需要指定缓存,标准I/O函数则由函数内部替程序员实现了缓存,再去调用不用缓存的I/O。
4.一些标准I/0函数:fgets,getc,putc,printf,ferror
5.unbuffered I/O functions:<unistd.h>
STDIN_FILENO
STDOUT_FILENO
STDERR_FILENO
standard I/O functions :<stdio.h>
stdin
stdout
stderr
--------------------------------------------