标准I/O的由来

        标准I/O指的是ANSI C 中定义的用于I/O操作的一系列函数。

只要操作系统安装了C库,标准I/O函数就可以调用。换句话说,如果程序中使用的是标准I/O函数,那么源代码不需要任何修改就可以在其他操作系统下编译运行,具有更好的可移植性。

        除此之外,使用标准I/O可以减少系统调用的次数,提高系统效率。标准I/O函数在执行时也会用到系统调用。在执行系统调用时,Linux必须从用户态切换到内核态,处理相应的请求,然后再返回到用户态。如果频繁的执行系统调用会增加系统的开销。为避免这种情况,标准I/O在使用时为用户控件创建缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际的文件,从而减少了使用系统调用的次数。

流的含义

标准I/O的核心对象就是流。当用标准I/O打开一个文件时,就会创建一个FILE结构体描述该文件(或者理解为创建一个FILE结构体和实际打开的文件关联起来)。我们把这个FILE结构体形象的称为流,我们在stdio.h里可以看到这个FILE结构体。

[cpp] view
plain
 copy

  1. typedef struct  {
  2. short           level;          /* fill/empty level of buffer */
  3. unsigned        flags;          /* File status flags    */
  4. char            fd;             /* File descriptor      */
  5. unsigned char   hold;           /* Ungetc char if no buffer */
  6. short           bsize;          /* Buffer size          */
  7. unsigned char   *buffer;        /* Data transfer buffer */
  8. unsigned char   *curp;          /* Current active pointer */
  9. unsigned        istemp;         /* Temporary file indicator */
  10. short           token;          /* Used for validity checking */
  11. }       FILE;                           /* This is the FILE object */

这个结构体:1)对 fd 进行了封装;2)对缓存进行了封装 unsigned char *buffer; 这而指向了buffer 的地址,实际这块buffer是cache,我们要将其与用户控件的buffer分开。

标准I/O函数都是基于流的各种操作,标准I/O中的流的缓冲类型有下面三种:

1)、全缓冲。 

在这种情况下,实际的I/O操作只有在缓冲区被填满了之后才会进行。对驻留在磁盘上的文件的操作一般是有标准I/O库提供全缓冲。缓冲区一般是在第一次对流进行I/O操作时,由标准I/O函数调用malloc函数分配得到的。

术语flush描述了标准I/O缓冲的写操作。缓冲区可以由标准I/O函数自动flush(例如缓冲区满的时候);或者我们对流调用fflush函数。

2)、行缓冲

在这种情况下,只有在输入/输出中遇到换行符的时候,才会执行实际的I/O操作。这允许我们一次写一个字符,但是只有在写完一行之后才做I/O操作。一般的,涉及到终端的流--例如标注输入(stdin)和标准输出(stdout)--是行缓冲的。

3)、无缓冲

标准I/O库不缓存字符。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。

标准I/O函数时库函数,是对系统调用的封装,所以我们的标准I/O函数其实都是基于文件I/O函数的,是对文件I/O函数的封装,下面具体介绍·标准I/O最常用的函数:

一、流的打开与关闭

使用标准I/O打开文件的函数有fopen() 、fdopen() 、freopen()。他们可以以不同的模式打开文件,都返回一个指向FILE的指针,该指针指向对应的I/O流。此后,对文件的读写都是通过这个FILE指针来进行。

fopen函数描述如下:

所需头文件 #include <stdio.h>
函数原型 FILE *fopen(const char *path, const char *mode);
函数参数

path: 包含要打开的文件路径及文件名

mode:文件打开方式

函数返回值

成功:指向FILE的指针

失败:NULL

mode用于指定打开文件的方式。

关闭流的函数为fclose(),该函数将流的缓冲区内的数据全部写入文件中,并释放相关资源。

fclose()函数描述如下:

所需头文件 #include <stdio.h>
函数原型 int fclose(FILE *stram);
函数参数

stream:已打开的流指针

函数返回值

成功:0

失败:EOF

二、流的读写

1、按字符(字节)输入/输出

字符输入/输出函数一次仅读写一个字符。

字符输入函数原型如下:

所需头文件 #include <stdio.h>
函数原型

int  getc(FILE *stream);

int  fgetc(FILE *stream);

int  getchar (void);

函数参数

stream:要输入的文件流

函数返回值

成功:读取的字符

失败:EOF

函数getchar等价于get(stdin)。前两个函数的区别在于getc可被实现为宏,而fgetc则不能实现为宏。这意味着:

1)getc 的参数不应当是具有副作用的表达式。

2)因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传给另一个参数;

3)调用fgetc所需时间很可能长于调用getc,因为调用函数通常所需的时间长于调用宏。

这三个函数在返回下一个字符时,会将其unsigned char 类型转换为int类型。说明为什么不带符号的理由是,如果是最高位为1也不会使返回值为负。要求整数返回值的理由是,这样就可以返回所有可能的字符值再加上一个已出错或已达到文件尾端的指示值。在<stdio.h>中的常量EOF被要求是一个负值,其值经常是-1。这就意味着不能将这三个函数的返回值存放在一个字符变量中,以后还要将这些函数的返回值与常量EOF相比较。

注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof。

[cpp] view
plain
 copy

  1. #include <stdio.h>
  2. int ferror (FILE *fp);
  3. int feof (FILE *fp);

两个函数返回值;若条件为真则返回非0值(真),否则返回0(假);

在大多数实现中,为每个流在FILE对象中维持了两个标志:

出错标志。

文件结束标志。

字符输出-函数原型如下:

所需头文件 #include <stdio.h>
函数原型

int putc (int c ,FILE *stream);

int fputc (int c, FILE *stream);

int putchar(int c);

函数返回值

成功:输出的字符c

失败:EOF

putc()和fputc()向指定的流输出一个字符(节),putchar()向stdout输出一个字符(节)。

2、按行输入、输出

      行输入/输出函数一次操作一行。

行输入函数原型如下:

所需头文件 #include <stdio.h>
函数原型

char *gets(char *s);

char  *fgets(char *s,int size,FILE *stream);

函数参数

s:存放输入字符串的缓冲区首地址;

size:输入的字符串长度

stream:对应的流

函数返回值

成功:s

失败或到达文件末尾:NULL

这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。

gets函数容易造成缓冲区溢出,不推荐使用;

fgets从指定的流中读取一个字符串,当遇到 \n 或读取了 size - 1个字符串后返回。注意,fgets不能保证每次都能读出一行。 如若该行(包括最后一个换行符)的字符数超过size -1 ,则fgets只返回一个不完整的行,但是,缓冲区总是以null字符结尾。对fgets的下一次调用会继续执行。

行输出函数原型如下:

所需头文件 #include <stdio.h>
函数原型

int puts(const char *s);

int fgets(const char *s,FILE *stream);

函数参数

s:存放输入字符串的缓冲区首地址;

stream:对应的流

函数返回值

成功:非负值

失败或到达文件末尾:NULL

函数fputs将一个以null符终止的字符串写到指定的流,尾端的终止符null不写出。注意,这并不一定是每次输出一行,因为它并不要求在null符之前一定是换行符。通常,在null符之前是一个换行符,但并不要求总是如此。

下面举个例子:模拟文件的复制过程:

[cpp] view
plain
 copy

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <unistd.h>
  4. #include <fcntl.h>
  5. #define maxsize 5
  6. int main(int argc, char *argv[])
  7. {
  8. FILE *fp1 ,*fp2;
  9. char buffer[maxsize];
  10. char *p,*q;
  11. if(argc < 3)
  12. {
  13. printf("Usage:%s <srcfile> <desfile>\n",argv[0]);
  14. return -1;
  15. }
  16. if((fp1 = fopen(argv[1],"r")) == NULL)
  17. {
  18. perror("fopen argv[1] fails");
  19. return -1;
  20. }
  21. if((fp2 = fopen(argv[2],"w+")) == NULL)
  22. {
  23. perror("fopen argv[2] fails");
  24. return -1;
  25. }
  26. while((p = fgets(buffer,maxsize,fp1)) != NULL)
  27. {
  28. fputs(buffer,fp2);
  29. }
  30. if(p == NULL)
  31. {
  32. if(ferror(fp1))
  33. perror("fgets failed");
  34. if(feof(fp1))
  35. printf("cp over!\n");
  36. }
  37. fclose(fp1);
  38. fclose(fp2);
  39. return 0;
  40. }

执行结果如下:

[cpp] view
plain
 copy

  1. fs@ubuntu:~/qiang/stdio/cp$ ls -l
  2. total 16
  3. -rwxrwxr-x 1 fs fs 7503 Jan  5 15:49 cp
  4. -rw-rw-r-- 1 fs fs  736 Jan  5 15:50 cp.c
  5. -rw-rw-r-- 1 fs fs  437 Jan  5 15:15 time.c
  6. fs@ubuntu:~/qiang/stdio/cp$ ./cp time.c 1.c
  7. cp over!
  8. fs@ubuntu:~/qiang/stdio/cp$ ls -l
  9. total 20
  10. -rw-rw-r-- 1 fs fs  437 Jan  5 21:09 1.c
  11. -rwxrwxr-x 1 fs fs 7503 Jan  5 15:49 cp
  12. -rw-rw-r-- 1 fs fs  736 Jan  5 15:50 cp.c
  13. -rw-rw-r-- 1 fs fs  437 Jan  5 15:15 time.c
  14. fs@ubuntu:~/qiang/stdio/cp$

我们可以看到,这里将time.c拷贝给1.c ,1.c和time.c大小一样,都是437个字节;

3、以指定大小为单位读写文件



三、流的定位



四、格式化输入输出



这里举个相关应用例子:循环记录系统时间

实验内容:程序每秒一次读取依次系统时间并写入文件

[cpp] view
plain
 copy

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <time.h>
  4. #define N 64
  5. int main(int argc, char *argv[])
  6. {
  7. int n;
  8. char buf[N];
  9. FILE *fp;
  10. time_t t;
  11. if(argc < 2)
  12. {
  13. printf("Usage : %s <file >\n",argv[0]);
  14. return -1;
  15. }
  16. if((fp = fopen(argv[1],"a+")) == NULL)
  17. {
  18. perror("open fails");
  19. return -1;
  20. }
  21. while(1)
  22. {
  23. time(&t);
  24. fprintf(fp,"%s",ctime(&t));
  25. fflush(fp);
  26. sleep(1);
  27. }
  28. fclose(fp);
  29. return 0;
  30. }

执行结果如下:

[cpp] view
plain
 copy

  1. fs@ubuntu:~/qiang/stdio/timepri$ ls -l
  2. total 12
  3. -rwxrwxr-x 1 fs fs 7468 Jan  5 16:06 time
  4. -rw-rw-r-- 1 fs fs  451 Jan  5 17:40 time.c
  5. fs@ubuntu:~/qiang/stdio/timepri$ ./time 1.txt
  6. ^C
  7. fs@ubuntu:~/qiang/stdio/timepri$ ls -l
  8. total 16
  9. -rw-rw-r-- 1 fs fs  175 Jan  5 21:14 1.txt
  10. -rwxrwxr-x 1 fs fs 7468 Jan  5 16:06 time
  11. -rw-rw-r-- 1 fs fs  451 Jan  5 17:40 time.c
  12. fs@ubuntu:~/qiang/stdio/timepri$ cat 1.txt
  13. Tue Jan  5 21:14:11 2016
  14. Tue Jan  5 21:14:12 2016
  15. Tue Jan  5 21:14:13 2016
  16. Tue Jan  5 21:14:14 2016
  17. Tue Jan  5 21:14:15 2016
  18. Tue Jan  5 21:14:16 2016
  19. Tue Jan  5 21:14:17 2016
  20. fs@ubuntu:~/qiang/stdio/timepri$

Linux 系统应用编程——标准I/O的更多相关文章

  1. Linux 系统应用编程——进程基础

    一.Linux下多任务机制的介绍 Linux有一特性是多任务,多任务处理是指用户可以在同一时间内运行多个应用程序,每个正在执行的应用程序被称为一个任务. 多任务操作系统使用某种调度(shedule)策 ...

  2. linux系统串口编程实例

    在嵌入式开发中一些设备如WiFi.蓝牙......都会通过串口进行主机与从机间通信,串口一般以每次1bit位进行传输,效率相对慢. 在linux系统下串口的编程有如下几个步骤,最主要的是串口初始化! ...

  3. Linux系统shell编程自学_第一章基础

    第一章 基础shell的优势在于处理操作系统底层的业务,Python,php的优势在于开发运维工具,web界面的管理工具以及web业务开发.处理一键安装.优化.报警脚本shell又叫命令解释器,它能识 ...

  4. 《linux系统及其编程》实验课记录(五)

    实验 5:权限的设置和更改 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统.有另外一个无特权用户 student,密码 student 的 ...

  5. 《linux系统及其编程》实验课记录(一)

    实验 1:登录和使用基本的 Linux 命令 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统. 有另外一个无特权用户 student,密码 ...

  6. Linux系统层级结构标准

    Linux Foundation有一套标准规范: FHS: Filesystem Hierarchy[‘haɪərɑːkɪ] Standard(文件系统层级标准)目前最新的标准是2.3版本:http: ...

  7. 《linux系统及其编程》实验课记录(六)

    实验 6:Linux 文件系统 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统.有另外一个无特权用户 student,密码 student ...

  8. Linux 系统应用编程——线程基础

    传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程.每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程.每个进程的全部系统资源是私有的,如虚拟地址空间,文 ...

  9. 《linux系统及其编程》实验课记录(二)

    实验 2:获取对使用命令的帮助 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统.有另外一个无特权用户 student,密码 student ...

随机推荐

  1. Linux命令—文件目录

     (1) shell的使用 <1>检查系统当前运行的shell版本: [root@lab root]# echo $SHELL <2>从当前shell下切换到csh: [r ...

  2. Swift基础之对FMDB第三方的使用方法

    相信大家都熟悉OC使用FMDB第三方库,进行数据库操作,增.删.改.查,现在我就来利用代码展示一下Swift对此库的使用方法,我是通过Pods添加的第三方库,如果手动添加记得创建桥接文件,在文件中调用 ...

  3. 深入理解CoordinatorLayout.Behavior

    要研究的几个问题 一.Behavior是什么?为什么要用Behavior? 二.怎么使用Behavior? 三.从源码角度看为什么要这么使用Behavior? 一.Behavior是什么?为什么要用B ...

  4. 【iOS 开发】基本 UI 控件详解 (UIButton | UITextField | UITextView | UISwitch)

    博客地址 : http://blog.csdn.net/shulianghan/article/details/50051499 ; 一. UI 控件简介 1. UI 控件分类 UI 控件分类 : 活 ...

  5. android获取设备唯一标示

    概述 有时需要对用户设备进行标识,所以希望能够得到一个稳定可靠并且唯一的识别码.虽然Android系统中提供了这样设备识别码,但是由于Android系统版本.厂商定制系统中的Bug等限制,稳定性和唯一 ...

  6. 使用jquery获取radio的值

     使用jquery获取radio的值,最重要的是掌握jquery选择器的使用,在一个表单中我们通常是要获取被选中的那个radio项的值,所以要加checked来筛选,比如有以下的一些radio项: ...

  7. 【Unity Shaders】Transparency —— 透明的cutoff shader

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  8. Java 8新特性探究(四)深入解析日期和时间-JSR310

    众所周知,日期是商业逻辑计算一个关键的部分,任何企业应用程序都需要处理时间问题.应用程序需要知道当前的时间点和下一个时间点,有时它们还必须计算这两个时间点之间的路径.但java之前的日期做法太令人恶心 ...

  9. 40个比较重要的Android面试题

    1. Android的四大组件是哪些,它们的作用? 答:Activity:Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持 ...

  10. C语言通讯录管理系统

    本文转载自:http://blog.csdn.net/hackbuteer1/article/details/6573488 实现了通讯录的录入信息.保存信息.插入.删除.排序.查找.单个显示等功能. ...