第五章-标准I/O库

流和FILE对象

NAME
       fwide - set and determine the orientation of a FILE stream

SYNOPSIS
       #include <wchar.h>

       int fwide(FILE *stream, int mode);

缓冲

标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。
标准I/O提供了3种类型的缓冲

  1. 全缓冲 ,在填满标准I/O缓冲区后才进行实际I/O操作,需要用的缓冲区可调用函数malloc获得。
NAME
       fflush - flush a stream
SYNOPSIS
       #include <stdio.h>
       int fflush(FILE *stream);

flush有两种意思:将缓冲区内容写到磁盘上;丢弃已经存储在缓冲区中的数据(终端驱动程序tcflush函数)。
2. 行缓冲 在写了一行(就是在输入和输出中遇到换行符时)后才进行实际I/O操作。
3. 不带缓冲 stderr通常是不带缓冲的;
4. 更改缓冲类型

NAME
       setbuf, setbuffer, setlinebuf, setvbuf - stream buffering operations
SYNOPSIS
       #include <stdio.h>
       void setbuf(FILE *stream, char *buf); //如果想要对打开的流用全缓冲,那么你必须设置buf指向一个长度为BUFSIZ的缓冲区;如果想要对打开的流关闭缓冲,将buf设置为NULL。
       int setvbuf(FILE *stream, char *buf, int mode, size_t size);//可精确的说明缓冲区的类型,mode参数实现

如下所述列出了以上两个函数的在不同的输入参数下的行为特征
在这里插入图片描述

打开流

系统默认,流被打开时 是全缓冲的,若流引用终端设备,则该流是行缓冲的。在对该流执行任何操作之前,可考虑用setbuf或setvbuf改变缓冲类型。
用fclose()函数关闭一个打开的流之前,请冲洗缓冲中的输出数据,

NAME
       fopen, fdopen, freopen - stream open functions
SYNOPSIS
       #include <stdio.h>
       FILE *fopen(const char *path, const char *mode); //打开路径名为path的一个指定的文件
       FILE *fdopen(int fd, const char *mode); //常用于由创建管道和网络通信函数返回的描述符
       FILE *freopen(const char *path, const char *mode, FILE *stream); //在一个指定的流上打开一个指定的文件
   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
       fdopen(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

mode参数指定对该I/O流的读,写方式,ISO C总共规定了15种不同的值,如下:
在这里插入图片描述
以下列出了打开一个流的6中不同方式
在这里插入图片描述

读和写流

3种选择

  1. 每次一个字符的I/O
    输入函数
NAME
       fgetc, fgets, getc, getchar, ungetc - input of characters and strings
SYNOPSIS
       #include <stdio.h>
       int fgetc(FILE *stream);
       char *fgets(char *s, int size, FILE *stream);
       int getc(FILE *stream); //getc可实现为宏,fgetc不能实现为宏。
       int getchar(void); //它等同于getc(stdin)
       int ungetc(int c, FILE *stream); //将字符再压送回流中。

要注意以上函数返回值是表示下一个字符(unsigned char类型转换为int类型)或文件尾端或出错。

NAME
       clearerr, feof, ferror, fileno - check and reset stream status
SYNOPSIS
       #include <stdio.h>
       void clearerr(FILE *stream); //清除FILE对象中维护的两个标志(出错标志,文件结束标志)
       int feof(FILE *stream);
       int ferror(FILE *stream);  //区分是出错还是到达文件尾端

输出函数

NAME
       fputc, fputs, putc, putchar, puts - output of characters and strings

SYNOPSIS
       #include <stdio.h>
       int fputc(int c, FILE *stream);
       int fputs(const char *s, FILE *stream);
       int putc(int c, FILE *stream);
       int putchar(int c);
       int puts(const char *s);
DESCRIPTION
       fputc() writes the character c, cast to an unsigned char, to stream.
       fputs() writes the string s to stream, without its terminating null byte ('\0').
       putc() is equivalent to fputc() except that it may be implemented as a macro which evaluates stream more than once.
       putchar(c) is equivalent to putc(c, stdout).
       puts() writes the string s and a trailing newline to stdout.
       Calls  to  the functions described here can be mixed with each other and with calls to other output functions from the stdio library for the same
       output stream.
       For nonlocking counterparts, see unlocked_stdio(3).
  1. 每次一行的I/O
    每次输入一行的功能
       #include <stdio.h>
  char *gets(char *s);
  char *fgets(char *s, int size, FILE *stream);

每次输出一行的功能

       #include <stdio.h>
       int fputs(const char *s, FILE *stream);
       int puts(const char *s);
  1. 直接I/O

标准I/O的效率

标准I/O系统的效率表现在如下几个方面:

  1. 无需考虑缓冲及最佳I/O长度的选择。
  2. 每次一行I/O的速度比每次一个字符的速度更快,因为更少的系统调用,而系统调用需要花费更多的时间。
    以下是两个示例程序,仅供参考
    Ex1:标准输入复制到标准输出
#include "apue.h"

int
main(void)
{
        int             c;

        while ((c = getc(stdin)) != EOF)
                if (putc(c, stdout) == EOF)
                        err_sys("output error");

        if (ferror(stdin))
                err_sys("input error");

        exit(0);
}

Ex2: 按行从标准输入复制到标准输出

#include "apue.h"

int
main(void)
{
        char    buf[MAXLINE];

        while (fgets(buf, MAXLINE, stdin) != NULL)
                if (fputs(buf, stdout) == EOF)
                        err_sys("output error");

        if (ferror(stdin))
                err_sys("input error");

        exit(0);
}

二进制I/O

实现读/写一个结构数组

NAME
       fread, fwrite - binary stream input/output
SYNOPSIS
       #include <stdio.h>
       size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
       size_t fwrite(const void *ptr, size_t size, size_t nmemb,
                     FILE *stream);

size为结构的长度,nmemb为对象个数,ptr指向数组首元素。

定位流

有三种方法定位标准I/O流.
一种,ftell和fseek函数,假定了文件位置用长整型存储

       #include <stdio.h>

       int fseek(FILE *stream, long offset, int whence);

       long ftell(FILE *stream);

       void rewind(FILE *stream);

第二种,fseeko和ftello函数,off_t 数据类型代替了长整型

    #include <stdio.h>

       int fseeko(FILE *stream, off_t offset, int whence);

       off_t ftello(FILE *stream);

第三种,fgetpos和fsetpos函数,用一个抽象数据类型fpos_t记录文件位置

   #include <stdio.h>
       int fgetpos(FILE *stream, fpos_t *pos);
       int fsetpos(FILE *stream, const fpos_t *pos);

格式化I/O

格式化输出

      #include <stdio.h>

       int printf(const char *format, ...);//格式化数据写到标准输出
       int fprintf(FILE *stream, const char *format, ...);//格式化数据写到指定流
       int dprintf(int fd, const char *format, ...);//格式化数据写到指定的文件描述符
       int sprintf(char *str, const char *format, ...);//格式化字符写到指定的缓冲区,有缓冲区溢出的安全隐患
       int snprintf(char *str, size_t size, const char *format, ...);//通过指定写入缓冲区的字符数来避免缓冲区溢出隐患

格式说明控制
转换说明的结构如下(以%开始),除转化说明外,格式字符串中的其他字符将按原样输出
在这里插入图片描述
下面是该结构的各个字段的介绍

  • 标志字段
    在这里插入图片描述
  • 最小字段宽度,非负十进制数或一个星号(*),不足时用空格填充。
  • 精度字段,说明整形转换后最少输出数字位数、浮点数转化后小数点后的最少位数、字符串转化后最大字节数。“一个点(.)后跟一个非负十进制整数或星号(*)”
  • 参数长度字段
    在这里插入图片描述
  • 转换类型字段,是不可选的,它控制如何解释参数。
    在这里插入图片描述
    以下是printf族的变体,可变参数表替换成了ap,这里不介绍可变长度参数表。
    iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
       #include <stdio.h>
       #include <stdarg.h>

       int vprintf(const char *format, va_list ap);
       int vfprintf(FILE *stream, const char *format, va_list ap);
       int vdprintf(int fd, const char *format, va_list ap);
       int vsprintf(char *str, const char *format, va_list ap);
       int vsnprintf(char *str, size_t size, const char *format, va_list ap);

格式化输入

       #include <stdio.h>

       int scanf(const char *format, ...);
       int fscanf(FILE *stream, const char *format, ...);
       int sscanf(const char *str, const char *format, ...);

scanf族函数分析输入字符串,并将字符序列转换成指定类型的变量。
格式说明控制如何转换参数,其结构如下
在这里插入图片描述

  • 可选择的星号(*)用于抑制转换
  • fldwidth说明最大宽度(即最大字符数)
  • lenmodifier要用转换说明赋值的参数大小
  • convtype字段类似于printf族的转换类型字段
    在这里插入图片描述
    相应的,也有可变长度参数表的变体,如下
      #include <stdarg.h>

       int vscanf(const char *format, va_list ap);
       int vsscanf(const char *str, const char *format, va_list ap);
       int vfscanf(FILE *stream, const char *format, va_list ap);

实现细节

  • Plauger[1992]的第12章提供了标准I/O库一种实现的全部源代码。GNU标准I/O库的实现也是公开可用的。
  • 你可以从头文件<stdio.h>了解到FILE对象是如何定义的
    以下示例说明标准错误如它所应该的那样是不带缓冲的,而普通文件按系统默认是全缓冲的。
#include "apue.h"

void	pr_stdio(const char *, FILE *);
int		is_unbuffered(FILE *);
int		is_linebuffered(FILE *);
int		buffer_size(FILE *);

int
main(void)
{
	FILE	*fp;

	fputs("enter any character\n", stdout);
	if (getchar() == EOF)
		err_sys("getchar error");
	fputs("one line to standard error\n", stderr);

	pr_stdio("stdin",  stdin);
	pr_stdio("stdout", stdout);
	pr_stdio("stderr", stderr);

	if ((fp = fopen("/etc/passwd", "r")) == NULL)
		err_sys("fopen error");
	if (getc(fp) == EOF)
		err_sys("getc error");
	pr_stdio("/etc/passwd", fp);
	exit(0);
}

void
pr_stdio(const char *name, FILE *fp)
{
	printf("stream = %s, ", name);
	if (is_unbuffered(fp))
		printf("unbuffered");
	else if (is_linebuffered(fp))
		printf("line buffered");
	else /* if neither of above */
		printf("fully buffered");
	printf(", buffer size = %d\n", buffer_size(fp));
}

/*
 * The following is nonportable.
 */

#if defined(_IO_UNBUFFERED)

int
is_unbuffered(FILE *fp)
{
	return(fp->_flags & _IO_UNBUFFERED);
}

int
is_linebuffered(FILE *fp)
{
	return(fp->_flags & _IO_LINE_BUF);
}

int
buffer_size(FILE *fp)
{
	return(fp->_IO_buf_end - fp->_IO_buf_base);
}

#elif defined(__SNBF)

int
is_unbuffered(FILE *fp)
{
	return(fp->_flags & __SNBF);
}

int
is_linebuffered(FILE *fp)
{
	return(fp->_flags & __SLBF);
}

int
buffer_size(FILE *fp)
{
	return(fp->_bf._size);
}

#elif defined(_IONBF)

#ifdef _LP64
#define _flag __pad[4]
#define _ptr __pad[1]
#define _base __pad[2]
#endif

int
is_unbuffered(FILE *fp)
{
	return(fp->_flag & _IONBF);
}

int
is_linebuffered(FILE *fp)
{
	return(fp->_flag & _IOLBF);
}

int
buffer_size(FILE *fp)
{
#ifdef _LP64
	return(fp->_base - fp->_ptr);
#else
	return(BUFSIZ);	/* just a guess */
#endif
}

#else

#error unknown stdio implementation!

#endif

运行该示例程序,使三个标准流与终端相连接,结果如下
在这里插入图片描述
运行该示例程序,使他们重定向到普通文件,结果如下
在这里插入图片描述

临时文件

创建临时文件

       #include <stdio.h>

       char *tmpnam(char *s);    //如果s = NULL,创建的路径名存放在一个静态区中,如果s != NULL,它应该是指向长度至少为L_tmpnam个字符的数组,L_tmpnam在头文件<stdio.h>定义。
       FILE *tmpfile(void);

以下是应用实例

#include "apue.h"

int
main(void)
{
	char	name[L_tmpnam], line[MAXLINE];
	FILE	*fp;

	printf("%s\n", tmpnam(NULL));		/* first temp name */

	tmpnam(name);						/* second temp name */
	printf("%s\n", name);

	if ((fp = tmpfile()) == NULL)		/* create temp file */
		err_sys("tmpfile error");
	fputs("one line of output\n", fp);	/* write to temp file */
	rewind(fp);							/* then read it back */
	//rewind(fp) is equal to (void) fseek(fp, 0L, SEEK_SET)
	if (fgets(line, sizeof(line), fp) == NULL)
		err_sys("fgets error");
	fputs(line, stdout);				/* print the line we wrote */

	exit(0);
}

运行该示例程序,其结果如下
在这里插入图片描述
从运行结果来看,这个静态区在每次调用该函数时就会被重写。

另外的处理临时文件函数

  #include <stdlib.h>

       char *mkdtemp(char *template); //创建有一个唯一名字的目录,名字是通过template字符串进行选择的。这个字符串是后六位设置为xxxxxx的路径名
       int mkstemp(char *template); //以唯一的名字创建一个普通文件并且打开该文件

以下是应用实例

#include "apue.h"
#include <errno.h>

void make_temp(char *template);

int
main()
{
	char	good_template[] = "/tmp/dirXXXXXX";	/* right way */
	char	*bad_template = "/tmp/dirXXXXXX";	/* wrong way*/

	printf("trying to create first temp file...\n");
	make_temp(good_template);
	printf("trying to create second temp file...\n");
	make_temp(bad_template);
	exit(0);
}

void
make_temp(char *template)
{
	int			fd;
	struct stat	sbuf;

	if ((fd = mkstemp(template)) < 0)
		err_sys("can't create temp file");
	printf("temp name = %s\n", template);
	close(fd);
	if (stat(template, &sbuf) < 0) {
		if (errno == ENOENT)
			printf("file doesn't exist\n");
		else
			err_sys("stat failed");
	} else {
		printf("file exists\n");
		unlink(template);
	}
}

运行该示例程序,其结果如下:
在这里插入图片描述
段错误的原因是 函数通过指针试图修改只读段。

内存流

标准I/O库把数据缓存在内存中,这就是标准I/O流,虽然仍使用FILE指针进行访问,但其实并没有底层文件。以下函数用于创建内存流

    #include <stdio.h>

       FILE *fmemopen(void *buf, size_t size, const char *mode);//buf参数指向缓冲区的开始位置,size是缓冲区大小的字节数,mode控制如何使用流

以下是mode可能的取值
在这里插入图片描述
下面分析一个具体实例来看看用已知模式填充缓冲区时流写入是如何操作的。
实例代码如下

#include "apue.h"

#define BSZ 48

int
main()
{
	FILE *fp;
	char buf[BSZ];

	memset(buf, 'a', BSZ-2);
	buf[BSZ-2] = '\0';
	buf[BSZ-1] = 'X';
	if ((fp = fmemopen(buf, BSZ, "w+")) == NULL)
		err_sys("fmemopen failed");
	printf("initial buffer contents: %s\n", buf);
	fprintf(fp, "hello, world");
	printf("before flush: %s\n", buf);
	fflush(fp);
	printf("after fflush: %s\n", buf);
	printf("len of string in buf = %ld\n", (long)strlen(buf));

	memset(buf, 'b', BSZ-2);
	buf[BSZ-2] = '\0';
	buf[BSZ-1] = 'X';
	fprintf(fp, "hello, world");
	fseek(fp, 0, SEEK_SET);
	printf("after  fseek: %s\n", buf);
	printf("len of string in buf = %ld\n", (long)strlen(buf));

	memset(buf, 'c', BSZ-2);
	buf[BSZ-2] = '\0';
	buf[BSZ-1] = 'X';
	fprintf(fp, "hello, world");
	fclose(fp);
	printf("after fclose: %s\n", buf);
	printf("len of string in buf = %ld\n", (long)strlen(buf));

	return(0);
}

这是在linux系统下的运行结果
在这里插入图片描述
可知:

  1. 可知冲洗内存流后,格式化数据才得以输出
  2. fseek引起缓冲区冲洗
  3. fclose冲洗了流,把缓冲区剩余数据输出到内核缓冲区
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值