标准I/O

标准I/O

C标准库中提供了标准I/O库(简称stdio),它实现了跨平台的用户缓冲解决方案。这个标准I/O库使用简单,功能强大

接下来主要讨论使用C标准库完成打开、关闭和读写操作。在应用中使用标准I/O还是直接使用系统调用,需要权衡应用的需求和行为

文件指针

标准I/O程序集并不是直接操作文件描述符。相反,他们通过唯一标识符,即文件指针来操作。在C标准库中,文件描述符和文件指针一一映射。文件指针是由指向类型定义FILE的指针表示

在标准I/O中,打开的文件成为“流”。流可以被打开用来读、写或者二者兼有

打开文件

文件通过fopen()打开以供读写操作:

1
2
3
#include <stdio.h>

FILE *fopen (const char *path, const char *mode);

该函数根据mode参数,按指定模式打开path所指向的文件,并给他关联上新的流

模式

参数mode描述如何打开指定文件,它可以是以下字串之一:

mode description
r 以只读模式打开文件。流指针指向文件开始
r+ 以读写模式打开文件。流指针指向文件开始
w 以只写模式打开文件。如果文件存在,文件会被清空,如不存在,就会被创建。流指针指向文件开始
w+ 以读写模式打开文件。如果文件存在,文件会被清空,如不存在,就会被创建。流指针指向文件开始
a 以追加写模式打开文件。如果文件不存在,就会被创建。流指针指向文件尾。所有的写入都是追加到文件的末尾
a+ 以追加读写模式打开文件。如果文件不存在,就会被创建。流指针指向文件尾。所有的写入都是追加到文件的末尾

举个例子:

1
2
3
4
5
6
7
FILE *stream;

stream = fopen ("test.txt","r");
if (!stream)
{
/* error */
}

通过文件描述符打开流

fdopen()会把一个已经打开的文件描述符转换成流:

1
2
3
#include <stdio.h>

FILE *fdopen (int fd, const char *mode);

fdopen()的可能模式和fopen()相同,而且必须和初始打开文件描述符的模式匹配,可以指定模式w和w+,但是它们不会清空文件

文件描述符并没有被复制,而只是关联了一个新的流。关闭流也会关闭相应的文件描述符

关闭流

fclose()函数会关闭给定的流:

1
2
3
#include <stdio.h>

int fclose (FILE *stream);

再关闭前,所有缓冲但还没有写出的数据都会被写出。成功返回0,失败返回EOF并设置相应的errno值

关闭所有流

fcloseall()函数会关闭和当前进程关联的所有流,包括标准输入、标准输出和标准错误:

1
2
3
4
#define _GNU_SOURCE
#include <stdio.h>

int fcloseall (void);

在关闭前,所有的流都会被写出。这个函数始终返回0,它是Linux所特有的

从流中读数据

C标准库实现了多种从流中读数据的方法。本次主要记录最常用的三种:每次读取一个字节;每次读取一行以及读取二进制数据

每次读取一个字节

通常情况下,理想的I/O模式是每次读取一个字符。函数fgetc()可以用来从流中读取单个字符:

1
2
3
#include <stdio.h>

int fgetc (FILE *stream);

该函数从stream中读取一个字符,并把该字符强制类型转换成unsigned int返回。强制类型转换是为了能够表示文件结束或错误:在这两种情况下都会返回EOF。fgetc()的返回值必须确保存成int类型。

1
2
3
4
5
6
7
8
9
10
int c;
c = fgetc (stream);
if (c == EOF)
{
/* error */
}
else
{
printf ("c = %c\n",(char) c);
}

stream指向的流必须以可读模式打开

把字符放回到流中

标准输入输出提供了一个函数可以把字符放回到流中。当流读取的最后一个字符,如果不需要该字符的话,可以把它放回流中。

1
2
3
#include <stdio.h>

int ungetc (int c, FILE *stream);

成功返回c,失败返回EOF

每次读取一行

函数fgets()会从指定流中读取一个字符串:

1
2
3
#include <stdio.h>

char *fgets (char *str, int size, FILE *stream);

该函数从stream中读取size-1个字节的数据,并把结果保存到str中。读完最后一个字节后,缓冲区会写入空字符(\0)。当读到EOF或换行符时,会结束读。如果读到换行符,会把\n写入str中。

fgets()成功时,返回str,失败时,返回NULL

1
2
3
4
5
6
char buf[LINE_MAX];

if (!fgets (buf, LINE_MAX, stream))
{
/* error */
}

读取任意字符串

通常,基于行的读取fgets()函数是很有用的。但是很多时候,它又会带来很多麻烦。比如不想要分隔符,或者想要自己设置分隔符,这时就需要用fgetc()来实现fgets()的功能了:

1
2
3
4
5
6
7
8
9
10
11
//读取n-1个字节到str中,然后追加一个\0字符
char *s;
int c;

s = str;
while (--n > 0 && (c = fgetc (stream)) != EOF)
{
*s++ = c;
}

*s = '\0';

可以优化代码支持在任意分隔符d处停止读数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char *s;
int c;

s = str;
while (--n > 0 && (c = fgetc (stream)) != EOF && (*s++ = c) != d)
{
;
}

if (c == d)
{
*--s = '\0';
}
else
{
*s = '\0';
}

读二进制文件

很多时候,读取一个字符或一行是不够的,为了解决这个问题,标准I/O库提供了fread()函数:

1
2
3
#include <stdio.h>

size_t fread(void *buf, size_t size, size_t nr, FILE *stream);

调用fread()会从stream中读取nr项数据,每项size个字节,并将数据保存到buf所指向的缓冲区中。文件指针向前移动读出数据的字节数

返回读到的数据项的个数(注意:不是读入字节数!!!)。如果读取失败或文件结束,返回一个比nr小的数,不幸的是必须使用ferror()或feof()函数,才能判断是失败还是文件结束

向流中写数据

和读相同,本次也介绍三个最常用的写数据的方法:每次写一个字节,每次写一个字符串,和写二进制数据

写入单个字符

和fgetc()函数对应的是fputc():

1
2
3
#include <stdio.h>

int fputc (int c, FILE *stream);

fputc()函数将参数c所表示的字节(强制类型转换成unsigned char)写到指针stream所指向的流。成功返回c,否则,返回EOF,并设置相应的errno值

1
2
3
4
if (fputc ('p', stream) == EOF)
{
/* error */
}

写入字符串

函数fputs()用于向指定流写入整个字符串:

1
2
#include <stdio.h>
int fputs (const char *str, FILE *stream);

fputs()函数会把str所指向的所有字符串都写入stream指向的流中,不会写入结束标记符。成功时返回非负整数;失败时,返回EOF

写入二进制数据

和fread()函数对应,标准I/O提供了fwrite()函数:

1
2
3
#include <stdio.h>

size_t fwrite (void *buf, size_t size, size_t nr, FILE *stream);

调用fwrite()会把buff所指向的nr个数据写入stream中,每个数据长为size

成功时返回写入的数据项个数,出错时,返回值小于nr

标准I/O例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>

int main(void)
{
FILE *in, *out;
struct pirate
{
char name[100];
unsigned long booty;
unsigned int beard_len;
}p, blackbeard = { "Edward Teach", 950, 48};

out = fopen ("./data", "w");
if (!out)
{
perror ("fopen");
return 1;
}

if (!fwrite (&blackbeard, sizeof(struct pirate), 1, out))
{
perror ("fwrite");
fclose (out);
return 1;
}

fclose (out);

in = fopen ("./data", "r");
if (!in)
{
perror ("fopen");
return 1;
}

if (!fread (&p, sizeof(struct pirate), 1, in))
{
perror ("read");
fclose (in);
return 1;
}

fclose (in);

printf ("name = \"%s\" booty = %lu beard_len = %u\n",
p.name,p.booty,p.beard_len);
return 0;
}

标准I/O
https://carl-5535.github.io/2021/02/01/Linux系统编程/标准I-O/
作者
Carl Chen
发布于
2021年2月1日
许可协议