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);
}