unix高级环境编程——标准IO

本期主题:

unix环境高级编程——标准IO


往期链接:

unix环境高级编程——文件IO



0.引言

这篇文章讲述标准IO库,不仅是unix,很多其他操作系统都实现了标准IO库,这个库由ISO C标准说明。

1.流与FILE对象

在另外一篇文章的文件IO部分,我们了解到文件IO函数(open、close、write、read等)都是围绕着文件描述符,即当打开一个文件时,就会返回一个文件描述符,然后利用该文件描述符来进行后续的操作。
而对于标准IO库而言,它们的操作是围绕 流(stream) 来进行的,当使用标准IO库打开或创建一个文件时,该文件已经和一个流相关联起来了。
当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针。

// 在/usr/include/libio.h中
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
};

2.标准输入、标准输出和标准错误

对一个进程预设了3个流,并且这3个流可以自动地被进程使用,分别是标准输入、标准输出和标准错误,这3个标准IO流通过预定义头文件指针stdin、stdout和stderr加以引用,定义在头文件<stdio.h>中。

3.缓冲

标准IO库提供缓冲的目的是为了尽可能减少read和write的调用次数。
标准IO提供了以下3种类型的缓冲:

(1)全缓冲。在这种情况下,在填满标准IO缓冲区之后才进行实际的IO操作。
(2)行缓冲,这种情况下,当输入输出遇到换行符时,这个时候标准IO库才允许输出。
(3)不带缓冲。标准IO库不对字符进行缓冲存储,立即输出,像前面讲过的write函数一样。

4.读和写流

一旦打开了流,则可在3种不同类型的非格式化IO中进行选择,对其进行读、写操作。
(1)每次一个字符的IO,一次读或写一个字符。
(2)每次一行的IO,每行都以一个换行符终止。
(3)直接IO,每次IO操作读或者写某种数量的对象,每个对象具有指定的长度。

4.1 一次字符的IO,getc和putc

getc和putc对应的是一个字符的IO的输入和输出。

#include <stdio.h>
int getc(FILE *stream);
int putc(int c, FILE *stream);

代码如下:

#include <stdio.h>
#include <stdint.h>
int main(void)
{
    //一个字符的IO,只能能用于读取一个字符,当输入多个时也只能读取一个
    char c;
    printf("get the input char : ");
    c = getc(stdin);
    printf("the input char : ");
    putc(c, stdout);
    printf("\n");
    return 0;
}

//运行结果:
gary@ubuntu:~/workspaces/0.Server_Workspace/2.Unix_Advanced_Program/1.Unix_IO/2.Stand_IO$ ./a.out 
get the input char : fas
the input char : f

4.2 每次一行的IO, gets和fgets

#include <stdio.h>
char *gets(char *s);
char *fgets(char *s, int size, FILE *stream);

这两个函数都指定了缓冲区的地址,读入的行送入其中。
gets从标准输入读,fgets从指定的流读取。
gets是一个不推荐使用的函数,问题在于调用者使用gets时能否指定缓冲区的长度,不然就会造成崩溃,例如

//gets风险
int main(void)
{
    char buf[2];
    gets(buf); 
    printf("输出: %s \n", buf);
    return 0;
}

gary@ubuntu:~/workspaces/0.Server_Workspace/2.Unix_Advanced_Program/1.Unix_IO/2.Stand_IO$ ./a.out 
helloworld
输出: helloworld 
*** stack smashing detected ***: ./a.out terminated
Aborted (core dumped)

当输入"helloworld"时,gets并没有检查输入的长度,这样存在很大的风险。
fgets函数则不存在这个问题,看下面的例子

int main(void)
{
    char buf[2];
    fgets(buf, sizeof(buf), stdin); 
    printf("输出: %s \n", buf);
    return 0;
}

//同样输入helloworld,fgets则会去检查
gary@ubuntu:~/workspaces/0.Server_Workspace/2.Unix_Advanced_Program/1.Unix_IO/2.Stand_IO$ ./a.out 
helloworld
输出: h 

4.3 二级制IO,fread和fwrite

前两小节讲得是一次以一个字符或者一行来进行操作的,如果在进行二进制IO操作,我们更愿意一次读或者写一个完整的结构,提供了以下两个函数来进行二进制IO操作。

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

这两个函数有这两种常用的用法:

  1. 读或者写一个二进制数组,例如将一个数组的第2~5个元素写至一个文件上:
int data[10];
fwrite(&data[2], sizeof(int), 4, fp);
//其中size为每个数组元素的长度,nmemb为欲写的元素个数
  1. 读写一个结构
struct {
	int a;
	int b;
} item;

fwrite(&item, sizeof(item), 1, fp);

看看下面的代码示例:

int main(void)
{
    FILE *pf;
    pf = fopen("./test.txt", "w");
    if (pf != NULL)
        printf("open file ok!\n");
    else
        printf("open file failed!\n");
    
    struct _test_str
    {
        uint8_t     a;
        uint8_t     b;
        uint16_t    c;
    };
    struct  _test_str str1;
    str1.a = 1;
    str1.b = 2;
    str1.c = 3;
    printf("sizeof str1 is %d\n", sizeof(str1));
    if (fwrite(&str1, sizeof(str1), 1, pf) != 1)
        printf("fwrite error!");
    
    fclose(pf);
    return 0;
}

gary@ubuntu:~/workspaces/0.Server_Workspace/2.Unix_Advanced_Program/1.Unix_IO/2.Stand_IO$ ./a.out 
open file ok!
sizeof str1 is 4
gary@ubuntu:~/workspaces/0.Server_Workspace/2.Unix_Advanced_Program/1.Unix_IO/2.Stand_IO$ od -c test.txt 
0000000 001 002 003  \0
0000004

5.格式化IO

前面讲的都是非格式化IO,现在我们来分析一下格式化IO。

1.格式化输出

格式化输出是由5个Printf函数来处理的。

NAME
       printf, fprintf, dprintf, sprintf, snprintf, vprintf, vfprintf, vdprintf, vsprintf, vsnprintf - formatted output conversion

SYNOPSIS
       #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将格式化数据写到标准输出;
fprintf写至指定的流;
dprintf写至指定的文件描述符;
sprintf将格式化的字符送入数组buf中;
sprintf在该数组的尾端自动添加一个Null字节,但该字符不包含在返回值中;

2.格式化输入

格式化输入是3个scanf函数。

NAME
       scanf, fscanf, sscanf, vscanf, vsscanf, vfscanf - input format conversion

SYNOPSIS
       #include <stdio.h>

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

       #include <stdarg.h>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值