5、打开和关闭流
三种打开流的方式: fopen
, freopen
, fdopen
其原型如下:
#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type,FILE *restrict fp);
FILE *fdopen(int filedes, const char *type);
三者返回:如果成功返回文件指针,如果错误返回 NULL
。
这里:
fopen
打开路径名由 pathname
指定的文件。
fdopen
可以接受一个已经打开的文件描述符号,将这个文件描述符和标准输入输出流关联。这样可以实现对除了磁盘 normal
文件之外的其他特殊文件进行流的操作(例如,有些特殊文件不能用 fopen
打开,那么可以先通过 open
或者 pipe
等获取其文件描述符浩,再用这个函数将其与标准流关联)。
freopen
在一个特定的流上(由 fp
指示)打开一个指定的文件(其路径名由 pathname
指示)如若该流已经打开,则先关闭该流。此函数常用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。
ISO C
为 type
参数指定了如下15种值:
打开一个标准输入输出流的类型参数
+------------------------------------------------------------------------------------+
| type | Description |
|------------------+-----------------------------------------------------------------|
| r or rb | open for reading |
|------------------+-----------------------------------------------------------------|
| w or wb | truncate to 0 length or create for writing |
|------------------+-----------------------------------------------------------------|
| a or ab | append; open for writing at end of file, or create for writing |
|------------------+-----------------------------------------------------------------|
| r+ or r+b or rb+ | open for reading and writing |
|------------------+-----------------------------------------------------------------|
| w+ or w+b or wb+ | truncate to 0 length or create for reading and writing |
|------------------+-----------------------------------------------------------------|
| a+ or a+b or ab+ | open or create for reading and writing at end of file |
+------------------------------------------------------------------------------------+
使用 b
作为 type
的一部分,使标准 I/O
系统可以区分文本文件和二进制文件。但是因为 UNIX
内核并不对这两种文件进行区分,所以在 UNIX
系统环境下指定字符b作为 type
的一部分实际上并没有什么作用。
对于 fdopen
, type
参数的意义稍有不同。因为该描述符已被打开,所以 fdopen
以写方式打开时并不截短该文件。(例如,若该描述符原来是由 open
函数打开的,该文件那时已经存在,则其 O_TRUNC
标志将决定是否截短该文件。 fdopen
函数不能截短它为写而打开的任一文件。)另外,标准 I/O
以追加方式操作,也不能用于创建该文件(因为如若一个描述符引用一个文件,则该文件一定已经存在) 。
当用追加方式打开一文件后,则每次写都将数据写到文件的当前尾端处。如若有多个进程用标准 I/O
添加方式打开了同一文件,那么来自每个进程的数据都将正确地写到文件中。
当以读和写类型打开一文件时( type
中 +
号),具有下列限制:
a)如果中间没有 fflush
、 fseek
、 fsetpos
或 rewind
,则在输出的后面不能直接跟随输入。
b)如果中间没有 fseek
、 fsetpos
或 =rewind = ,或者一个输出操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
根据上表,下表列出了打开一个流的六种不同方式。
六种打开标准输入输出流的方法
+-----------------------------------------------------------------+
| Restriction | r | w | a | r+ | w+ | a+ |
|--------------------------------------+---+---+---+----+----+----|
| file must already exist | • | | | • | | |
|--------------------------------------+---+---+---+----+----+----|
| previous contents of file discarded | | • | | | • | |
|--------------------------------------+---+---+---+----+----+----|
| stream can be read | • | | | • | • | • |
|--------------------------------------+---+---+---+----+----+----|
| stream can be written | | • | • | • | • | • |
|--------------------------------------+---+---+---+----+----+----|
| stream can be written only at end | | | • | | | • |
+-----------------------------------------------------------------+
注意,在指定 w
或 a
类型创建一个新文件时,我们无法说明该文件的存取许可权位( open
函数和 creat
函数则能)。如果流引用的不是终端设备,那么系统默认,它被打开时是全缓存的。如果流引用终端设备,则该流是行缓存的。打开流之后,在对该流执行任何操作之前,可使用前面所述的 setbuf
和 setvbuf
改变缓存的类型。
可以调用 fclose
关闭一个打开的流。声明如下:
#include <stdio.h>
int fclose(FILE *fp);
返回:如果成功返回0,如果错误返回 EOF
。
在文件被关闭之前,会将任何缓存中的输出数据刷新,而缓存中的输入数据被丢弃。如果标准 I/O
库已经为该流自动分配了一个缓存,则释放此缓存。当一个进程正常终止时(直接调用 exit
函数,或从 main
函数返回),则所有带未写缓存数据的标准 I/O
流都被刷新,所有打开的标准 I/O
流都被关闭。
译者注
原文参考
6、关于流的读写
当打开一个 stream
的时候,我们可以有三种方式读写其中的内容.例如: fgetc
, fputc
.
- 字符方式。一次读写一个字符,如果
stream
有缓存那么由标准输入输出函数自己管理缓存。 - 行方式。一次读写一行。例如
fputs
,fgets
. - 直接方式。在
ISO C
中的直接方式,也有时候被称作binary I/O
(二进制方式),object-at-a-time I/O
(一次一个对象),record-oriented I/O
(面向记录),structure-oriented I/O
(面向结构)。每次读写指定的大小。例如fread
,fwrite
.
(1)输入函数
有三个函数,可以每次读取输入的一个字符,声明如下:
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
三者返回:如果成功返回下一个字符;否则到达文件结尾返回 EOF
,或者错误。
三个函数都以单个字符的方式操作流。
getchar
和 getc
( stdin
)一样。 getc
和 fgetc
的区别是 getc
可能是一个宏定义而 fgetc
一定是一个函数的实现。三者都返回一个 unsigned char
并转化成为 int
.这样返回值非负。不能用其返回值和 EOF
比较。判断文件结束有另外的方法。
当读取文件错误或者到达文件结尾的时候,返回值一样,这时候,需要通过 ferror
和 feof
才能加以区别。其声明如下:
#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
void clearerr(FILE *fp);
两者返回:如果条件符合则返回 true
(非0),否则 false
。
在 FILE
对象中为每一个 stream
维护一个 error flag
和 end of file flag
。就是通过这两个函数进行判断的。使用 clearerr
可以清除这两个标记。
从流中读取之后,我们也可以通过 ungetc
函数将字符推送到流中,声明如下:
#include <stdio.h>
int ungetc(int c, FILE *fp);
返回:如果成功返回c,如果错误返回 EOF
。
这个函数可以把一个字符推送回到流中,下次读取的时候就会读到那个字符了。
注意这个函数只是把一个字符推送到流里面了,并没有写这个流对应的实际文件。不能把 EOF
推送到 stream
中。当推送一个字符到 stream
中成功的时候,会自动清除 end of file flag
。
(2)输出函数
我们也可看到相应于前面讨论的输入函数的输出函数,声明如下:
#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
三者返回:如果成功返回c,如果错误返回 EOF
。
这三个函数和 getc
, fgetc
, getchar
类似,不过是写而不是读取了。 putchar
(c)等价于 putc
(c, stdout
), putc
可以用宏的方式实现而 fputc
不能用宏实现。
译者注
原文参考
7、行输入输出
下面两个函数提供行输入功能:
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
返回:如果成功返回 buf
,如果失败或者到达文件结尾返回 NULL
。
两者以行的方式操作流。 fgets
需要指定缓存的大小,然后读取一行,读取的结果包含 new line
和一个 null
结束。如果行长超过了n,那么就读取到n,最后也包含一个 null
字符(需要仔细调查???),反正 fgets
最多读取 n-1
个字符。 gets
已经逐渐被淘汰了,因为没有指定缓存大小,不安全,另外它并不把 newline
存放到 buffer
中。
下面是一个例子:
/*程序功能:
使用fgets读取文件的一行,并且打印出来。
*/
#include <stdio.h>
int main(int argc, char *argv[])
{
char chars[256];
FILE *f = fopen("./test","r");
while(!feof(f))
{
fgets(chars,256,f);//注意回车也都读取了
printf("%s",chars,ftell(f));
//printf("%s:%ld",chars,ftell(f));
}
fclose(f);
return 0;
}
下面两个函数提供行输出功能:
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
两者返回:如果成功返回非负,否则返回 EOF
。
fputs
会把一个 null
结束的字符串写到一个指定的 stream
中。结尾的 null
字节并不会被写入。需要注意的是,这个函数并不一定是一次一行的输出,因为输出的字符串并不需要把 newline
作为最后的非空字符。一般来说,最后一个非空字符会是 newline
字符,但是这不是必须的。
puts
函数会把 null
字符串写入到标准输出,但是不会写入空字符。另外, puts
还会把一个 newline
字符输出到标准输出。
puts
函数是安全的,而不像相应的 gets
。然而,我们也要避免使用它,否则还要记住它要在输出的最后追加一个 newline
字符。如果我们始终使用 fgets
和 fputs
,我们会知道我们怎样处理每一行最后的一个 newline
字符。