第五章-標準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沖洗了流,把緩衝區剩餘數據輸出到內核緩衝區
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章