Unix标准IO库相关函数总结之读写流(二)

读写流的三种方式

一旦一个流被打开,则可以选择三种方式对其进行读写。

  • 每次一个字符的IO,一次读或者写一个字符,如果流是带缓冲的,则标准IO函数处理所有的缓冲。
  • 每次一行的IO。每次读写一行数据,可以使用fgets和fputs函数,但是要说明最大行的长度。
  • 直接IO。通常使用fread和fwrite函数。

一个字符的IO

输入函数

通常使用以下三个函数进行一个字符的读。

1
2
3
4
#include <stdio.h>
int getc(FILE*);
int fgetc(FILE*);
int getchar();

区别与联系

这三个函数都用于一个字符的读取。其区别和联系如下:

  • getchar()相当于getc(stdin)。即每次从标准输入流读入一个字符。实质上getc是宏
  • getc和fgetc的区别是getc可以被实现为宏,而fgetc不能被实现为宏。这就意味着:
    • getc的参数不能是具有副作用的表达式,因为它的值可能被计算多次。
    • fgetc是一个函数,可以获得其地址, 这就允许将fgetc作为参数传递给另一个函数。
    • 调用fgetc时间比getc时间长,因为调用宏的时间更短。

有副作用的表达式是指:表达式的作用本质是用于计算的,原则上只返回一个计算结果,而不会改变表达式中的变量的值。这种不改变表达式中变量值的表达式叫做无副作用的表达式。如:x+y,y-z等。除此之外,若表达式中变量的值被改变则成为有副作用的表达式,如x++,y+=2;

由于在宏中宏可能出现在程序的很多位置,也就是表达式会被计算多次,这个时候若表达式有副作用就会GG。

返回值

这三个函数的返回值都是int类型,这三个函数在返回下一个字符的时候,将其unsigned char类型转换为int。说明无符号的理由是,如果最高位为1,也不会使返回值为负。返回整形的理由是这样既可以返回所有的字符,也可以返回出错或到达文件为的指示值

注意不管是到达文件为还是出错,这个时候三个函数的返回值都一样。为了区分这两种情况,常调用ferror或feof函数。

1
2
3
4
5
6
7
#include <stdio.h>
//用于判断流fp是否遇到读取错误,若读取错误,返回非零值,否则返回0。
int ferror(FILE* fp);
//用于判断流是否遇到文件结尾,若到达文件结尾,返回非零值,否则返回0.
int feof(FILE* fp);
void clearerr(FILE* fp);

在大多数的实现中,为每个流在FILE对象中维护了两个标志:

  • 出错标志
  • 文件结束标志

调用clearerr可以清除这两个标志。

压送字符到流中。

从流中读取字符以后,可以使用ungetc将字符押送回流中,压回的字符又可以从流中读出,读出的顺序与压送的顺序相反。压送的字符不会被写到流中。

1
2
#include <stdio.h>
int ungetc(int c, FILE* fp);

###输出函数
输出函数为以下三个,与输入对应,区别与联系和输入函数相同。

1
2
3
4
#include <stdio.h>
int putc(int c, FILE* fp);
int fputc(int c, FILE* fp);
int putchar(int c);

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE* test = fopen("test.txt", "rw");
char a;
while ((a =getc(test)) != EOF) {
putc(a, stdout);
putc('\n', stdout);
}
ungetc('1', test);
ungetc('2', test);
putc(getc(test), stdout);
putc(getc(test), stdout);
if (ferror(test)) {
printf("this is the read error\n");
}
if (feof(test)) {
printf("this is the eof\n");
}
fclose(test);
}

一行的IO

输入

标准IO中提供了一下两个函数进行一行的读取:

1
2
3
#include <stdio.h>
char* fgets(char* restrict buf, int n, FILE* fp);
char* gets(char* buf);

返回值

若读取成功返回buf,若读取失败或者读取到文件结尾返回NULL.

区别与联系

  • 对于fgets必须指定缓冲区的长度n.此函数一直读到下一个换行符为止,但是不能超过n-1个字符,读入的字符将被送入缓冲区,该缓冲区以null结尾。若改行包含换行符超过了n-1个字符,fgets只返回一个不完整的行。下一次读取的时候将继续从该行继续往下读。
  • gets函数用于从标准输入读取,但是gets不包含缓冲区的长度,所以在读取的时候可能会出现缓冲区溢出的情况。一般最好不要用gets函数.
  • gets函数不将换行符存入缓冲区,而fgets将换行符存入缓冲区

虽然ISO要求提供gets函数,但一般使用fgets不要使用gets函数。

输出

对应的,标准IO提供了以下连个函数进行输出:

1
2
3
#include <stdio.h>
int fputs(const char* restrict buf, FILE* restrict fp);
int puts(const char* buf);

返回值

成功返回非负值,失败返回EOF。

区别

  • fputs将一个以null结尾的字符串写到指定的流,但是null不写出,注意这不是每次输出一行,而取决于缓冲区中的内容。
  • puts将一个以null结尾的字符串写到指定的流,null不写出,但是在最后又添加了一个换行符
  • 一般情况下使用fgets和fputs,不适用gets和puts函数。

实例

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
FILE* fp = fopen("test.txt", "rw");
char buf[5];
while (fgets(buf, 5, fp)) {
printf("%s", buf);
}
return 0;
}

直接IO

直接IO通常用于对二进制文件的读写,除此之外也可以对文本文件进行读写。其中对二进制文件的读写只能用直接IO的方式,因为二进制文件中可能含有null字符,会导致使用行或者字符获取时出现错误。直接IO进行读写的两个函数如下:

1
2
3
4
#include <stdio.h>
size_t fread(void* restrict ptr, size_t size, size_t nobj, FILE* fp);
size_t fwrite(const void* restrict ptr, size_t size, size_t nobj, FILE* fp);

参数说明

  • ptr:为缓冲区指针
  • size:为一个结构体(类型)的大小
  • nobj:结构体的个数,若在fread中说明将要写入nobj个大小为size的结构体(类型),若为fwrite,则为要从流fp中读取nobj个大小为size的数据
  • fp:文件指针

返回值

返回值为实际读取或写入的对象的个数。对于fread,若文件出错或者读到文件结尾处都可以少于nobj,对于fwrite,若返回值小于nobj,则写入出错。

注意

使用这两个函数存在一个问题就是,他们只能用于读写在同一个系统上已写的数据。若是通过网络挂载的文件则不可行。
fread,fwrie可移植,而read,write不可移植。

格式化输出

格式化输出有以下几个函数:

1
2
3
4
5
6
7
#include <stdio.h>
int printf(const char* restrict format, ...);
int fprintf(FILE* restrict fp, const char* restrict format, ...);
int dprintf(int fd, const char* restrict format, ...);
int sprintf(char* restrict buf, const char* restrict format, ...);
int snprintf(char* restrict buf, size_t n, const char* restrict format, ...);

返回值

前三个函数若输出成功,则返回输出字符的个数;若输出出错,则返回负值。

sprintf和snprintf若执行成功,则返回存入buf中的字符串的长度,否则返回负值。

函数说明

  • printf:向标准输出输出字符串。
  • fprintf:向标准文件流输fp出字符串。
  • dprintf:向文件描述符所指向的文件输出字符串。
  • sprintf:向缓冲区buf写入字符串。
  • snprintf:安全的向缓冲区buf写入字符串并指定缓冲区的最大长度.一般用此函数代替sprintf

格式化字符串format的格式

1
2
//其中[]表示可选部分,converter不可选
%[flags][fldwidth][precision][lenmodifier]converter
  • flags : 是该输出的标志,其包含如下几个值:
    • ‘ : 将整数按千位分组字符。
      • : 在字段内左对齐输出。
      • : 总是显示正负号
    • (空格) : 如果第一个字符不是正负号,则在其前面加上一个空格
    • : 指定另一种转换形式,如对于16进制,在前面加0x

    • 0 : 添加前导0进行填充
  • fldwidth : 说明最小字段宽度,若参数字段小于此宽度,多余位置用空格填充。
  • precision : 浮点数精度。前导为.

注意:fldwidth可以用*作为占位符,然后在后面对其进行指定。

1
2
3
4
5
6
7
8
#include <stdio.h>
int main () {
// 10.10
printf("%10.2lf", 10.1);
//与上面写法等效。
printf("%*.*lf", 10, 2, 10.1);
}

Comment and share

Unix标准IO库相关函数总结之打开关闭流(一)

Unix标准IO类型FILE

在Unix相关的文件IO中几乎所有的函数都用到了文件描述符,文件描述符是打开一个文件时返回的一个可用的最小的文件描述标识。相应的在Unix标准IO相关的函数中,几乎每个函数都用到了FILE数据类型。本小结简单介绍一下FILE结构体的内容。

FILE实际上是一个struct的typedef,可以在/usr/include/stdio.h中找到它的定义为:

1
typedef _IO_FILE FILE;

_IO_FILE_的定义在文件/usr/include/libio.h中,我们可以看到它的具体定义为:

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
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

从上面的struct中可以观察到FILE中是有文件描述符标志的,即为fileno参数。

打开标准IO流

打开标准文件IO流的方法有如下三个:

1
2
3
4
5
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 fd, const char* type);

上述三个函数区别以及使用场景说明:

  • fopen的作用是打开一个指定文件路径的文件流。
  • freopen的作用是在一个指定的留上打开一个指定文件。如果当前流已经打开,则先关闭该流;若该流被重定向,则清除重定向。该函数常用于将一个指定的文件打开为一个默认的流,如若想使用printf函数将打印出来的内容输出到一个指定文件中,此时可以讲stdout重定向到指定的文件路径并指定打开模式。
  • fdopen的作用是将一个已经打开的文件描述符(该文件描述符可能是open,dup,socket等获取的)绑定到一个标准IO流上。此函数常用于由管道和网络通信通道函数返回的文件描述符,因为这些无法显式的指定文件。

参数说明:

  • pathname: 文件路径,相对或绝对
  • type: 打开模式(r,w,a,r+,w+,a+以及所有后面加b)

打开模式说明:

  • r : 读模式
  • w : 写模式,在写之前将原有文件内容全部清楚
  • a : 追加模式,offset为文件结尾
  • r+ : 读写模式(不删除文件原有内容,offset初始为文件开头)。如原有文件内容为”123456”,若以此模式打开文件并写入”abc”,此时写入之后的结果是”abc456”。
  • w+ : 读写模式(删除文件原有内容,offset为文件开头).如原有文件内容为”123456”,若以此模式打开文件并写入”abc”,此时写入之后的结果是”abc”。
  • a+ : 读写模式(offset为文件结尾).如原有文件内容为”123456”,若以此模式打开文件并写入”abc”,此时写入之后的结果是”123456abc”。

上述所有的模式后面都加上b表示对二进制文件的操作(rb,wb,ab,rb+,wb+,ab+)。

注意:对于fdopen函数由于文件已经由文件描述符打开,此时w模式时将不清除文件内容,追加模式不常见文件。

当以读写模式打开文件时候,将有一些限制。如果中间没有fflush,fseek,fsetpos以及rewind函数,标注输出之后不能直接进行输入;如果没有fseek,fsetpos或者rewind,或者一个输入没有到达文件尾,则输入操作之后不能跟输出操作。

关闭标准文件流

一般情况下在关闭文件流之前通常先使用fflush刷新缓冲区防止数据丢失,关闭标准文件流使用的函数为:

1
2
#include <stdio.h>
int fclose(FILE* fp);

Comment and share

Unix标准IO文件流及缓冲类型

Unix标准IO文件流

文件IO相关函数的一节中,我们所有的I/O函数都是围绕着文件描述符来操作的,当打开一个文件的时候,即返回一个文件描述符,然后该文件描述符用于后续的文件操作。而对于标准IO库,对于文件的操作都是围绕这 文件流 file stream进行的。当我们使用标准IO库打开或创建一个文件的时候,我们已经使一个流和一个文件进行关联。

文件流

由于历史原因,C语言中原来表示流的数据结构是FILE,而不是叫做流。由于大多数的库函数使用到了FILE类型,有的时候在使用FILE指针的时候也叫其为流,这导致后来很多数据把FILE和流搞得十分混乱。实际上流就是标准IO库中程序与文件交互的一种方式。

标准IO函数fopen打开一个文件时返回一个指向FILE对象的指针,该对象通常是一个结构,它包含了标准IO库为管理该流所需要的所有信息,包括该文件的文件描述符,用于指向该流缓冲区的指针,缓冲区的长度,当前缓冲区中的字符数以及出错标志等等

标准输入,标准输出以及标准错误

标准库中对于每一个进程都预定义了三个流,分别是stdin,stdout以及stderr,他们分别对应与Linux文件IO中的STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO。它们的定义在stdio.h中

I/O文件流的缓冲类型

标准IO提供缓冲的目的是为了通过减少使用read和write调用的次数来提高IO读写的效率,它对每个IO流自动的进行缓冲处理,从而避免了用户程序在使用read和write需要考虑的这一点。

标准IO流提供了三种缓冲。分别是全缓冲(fully buffering),行缓冲(line Buffering)以及无缓冲(nonBuffering)。

全缓冲

在使用全缓冲的情况下,当数据填满整个缓冲区之后才进行实际的IO操作。对于驻留在磁盘上的文件的读写通常是使用全缓冲。通常如果不给文件流指定缓冲区的情况下,标准IO函数会首先调用malloc函数获取所需要的缓冲区。

行缓冲

在使用行缓冲的情况下,每当输入输出遇到换行或者缓冲区满了的情况下才会进行实际的IO操作,当涉及到终端输入输出的时候通常使用行缓冲。

对于行缓冲有两个限制。1.由于接收行缓冲的缓冲区的长度是固定的,所以只要填满了缓冲区,即使还没有遇到换行符,也会进行IO操作。2.任何时候,只要通过IO库要求从一个不带缓冲的流或者一个行缓冲的流得到输入数据,那么就会冲洗所有缓冲输出流。

###不带缓冲
此时标准IO库不对字符进行缓冲存储。这就使得输入流要求IO立即进行,如标准错误流,若果出现错误,会立马输出。

flush一个流即刷新缓冲区有两个含义。

  • 在IO库方面,flush意味着将缓冲区中的内容写到磁盘上,该缓冲区可能还没有满
  • 在终端驱动方面表示丢弃已经存储在缓冲区中的内容。

##标准文件流与缓冲类型之间的关系

  • 当标准输入输出指向的是交互式设备(如终端)的时候,它们是行缓冲的,若不是则是全缓冲的。
  • 标准错误永远是无缓冲的。

与缓冲相关的函数

我们可以通过一下两个函数对将缓冲关闭或者改变缓冲的类型。其中这些函数应该在流被打开之后调用,而且也应该在对流进行一切操作之前调用。

1
2
3
#include <stdio.h>
void setbuf(FILE* restrict fd, char* restrict buf);
int setvbuf(FILE* restrict fd, char* restrict buf, int mode, size_t size);

使用setbuf函数打开或者关闭缓冲,当buf是一个有效缓冲区时,此时缓冲打开,若流指向的是终端设备,则此时该流是行缓冲的,否则该流是全缓冲的;当buf为NULL的时候,表示关闭该缓冲。

使用setvbuf可以精确的说明缓冲的类型,这里是使用mode来说明的,mode的值包括以下几个:

  • _IOFBF 全缓冲
  • _IOLBUF 行缓冲
  • _IONBUF 无缓冲

如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定缓冲,则buf和size分别指定一个缓冲区域和缓冲区域的长度。若此时buf为NULL,则标准IO库将自动制定一个适合长度的缓冲区。

上述函数与缓冲之间的关系

函数 mode buf 缓冲区及长度 缓冲类型
setbuf 非空 长度为size的缓冲区buf 全缓冲或行缓冲
setbuf NULL 无缓冲区 不带缓冲
setvbuf _IOFBF 非空 长度为size的缓冲区buf 全缓冲
setvbuf _IOFBF NULL 合适长度的缓冲区buf 全缓冲
setvbuf _IOLBF 非空 长度为size的缓冲区buf 行缓冲
setvbuf _IOLBF NULL 合适长度的缓冲区buf 行缓冲
setvbuf _IONBF 忽略 无缓冲区 不带缓冲

我们还可以通过fflush强制冲洗一个流,此函数使该流所有未写的数据都被传送到内核。作为一种特殊的情况,当流的NULL时,所有的流将被冲洗:

1
2
#include <stdio.h>
int fflush(FILE* fd);

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China