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

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China