Linux 系统应用编程——标准I/O

标准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.   
  3. int ferror (FILE *fp);  
  4. 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.   
  7. int main(int argc, char *argv[])  
  8. {  
  9.     FILE *fp1 ,*fp2;  
  10.     char buffer[maxsize];  
  11.     char *p,*q;  
  12.   
  13.     if(argc < 3)  
  14.     {  
  15.         printf("Usage:%s <srcfile> <desfile>\n",argv[0]);  
  16.         return -1;  
  17.     }  
  18.   
  19.     if((fp1 = fopen(argv[1],"r")) == NULL)  
  20.     {  
  21.         perror("fopen argv[1] fails");  
  22.         return -1;  
  23.     }  
  24.   
  25.     if((fp2 = fopen(argv[2],"w+")) == NULL)  
  26.     {  
  27.         perror("fopen argv[2] fails");  
  28.         return -1;  
  29.     }  
  30.   
  31.   
  32.     while((p = fgets(buffer,maxsize,fp1)) != NULL)  
  33.     {  
  34.          fputs(buffer,fp2);  
  35.     }  
  36.   
  37.     if(p == NULL)  
  38.     {  
  39.         if(ferror(fp1))  
  40.             perror("fgets failed");  
  41.         if(feof(fp1))  
  42.             printf("cp over!\n");  
  43.     }  
  44.   
  45.     fclose(fp1);  
  46.     fclose(fp2);  
  47.           
  48.     return 0;  
  49. }  

执行结果如下:

[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.   
  6. int main(int argc, char *argv[])  
  7. {  
  8.     int n;  
  9.     char buf[N];  
  10.     FILE *fp;  
  11.     time_t t;  
  12.   
  13.     if(argc < 2)  
  14.     {  
  15.         printf("Usage : %s <file >\n",argv[0]);  
  16.         return -1;  
  17.     }  
  18.   
  19.     if((fp = fopen(argv[1],"a+")) == NULL)  
  20.     {  
  21.         perror("open fails");  
  22.         return -1;  
  23.     }  
  24.   
  25.     while(1)  
  26.     {  
  27.         time(&t);  
  28.         fprintf(fp,"%s",ctime(&t));  
  29.         fflush(fp);  
  30.         sleep(1);  
  31.     }  
  32.   
  33.     fclose(fp);  
  34.   
  35.     return 0;  
  36. }  

执行结果如下:

[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$  
时间: 2024-09-16 14:05:18

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

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

一.Linux下多任务机制的介绍          Linux有一特性是多任务,多任务处理是指用户可以在同一时间内运行多个应用程序,每个正在执行的应用程序被称为一个任务.          多任务操作系统使用某种调度(shedule)策略(由内核来执行)支持多个任务并发执行.事实上,(单核)处理器在某一时刻只能执行一个任务.每个任务创建时被分配时间片(几十到上百毫秒),任务执行(占用CPU)时,时间片递减.操作系统会在当前任务的时间片用完时调度执行其他任务.由于任务会频繁地切换执行,因此给用户多

gcc用于linux系统下编程的编译器选项说明

GC++(GNU Compiler Collection,GNU编译器套装),是一套由 GNU 开发的编程语言编译器.它是一套&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp; GNU编译器套装以 GPL 及 LGPL 许可证所发行的自由软件,也是 GNU计划的关键部分,亦是自由的类Unix及苹果电脑 Mac OS X 操作系统的标准编译器. GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言.GCC

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

 传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程.每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程.每个进程的全部系统资源是私有的,如虚拟地址空间,文件描述符和信号处理等等.使用多进程实现多任务应用时存在如下问题: 1)任务切换,即进程间上下文切换,系统开销比较大.(虚拟地址空间以及task_struct 都需要切换) 2)多任务之间的协作比较麻烦,涉及进程间通讯.(因为不同的进程工作在不同的地址空间) 所以,为了提高系统的性能,许多操作系统规

linux下Shell编程--标准的守护进程的启动脚本

一个标准的守护进程的启动脚本: #! /bin/sh WHOAMI=`whoami` PID=`ps -u $WHOAMI | gerp mydaemond | awk '{print $1}'` if (test "$1" = "") then echo "mydaemond [start][stop][version]" exit 0 fi if ( test "$1" = "status") then

GCC 4.6.1发布 linux系统下的编译器

GCC是一个用于linux系统下编程的编译器.是一套由 GNU 开发的编程语言编译器.它是一套  GNU编译器套装以 GPL 及 LGPL 许可证所发行的自由软件,也是 GNU计划的关键部分,亦是自由的类Unix及苹果电脑 Mac OS X 操作系统的标准编译器. GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言.GCC 很快地扩展,变得可处理 C++.之后也变得可处理 Fortran.Pascal.Objective-C.Java, 以及 Ada与其他语言. GCC 4.6.

《Linux系统编程(第2版)》——1.3 标准

1.3 标准 UNIX系统编程是门古老的艺术.UNIX编程的基础理念在几十年来一直根深蒂固.但是,对于UNIX系统,变化却是无处不在.各种行为不断变化,特性不断增加.为了使UNIX世界变得有序,标准化组织为系统接口定义了很多套官方标准.虽然存在很多这样的官方标准,但是Linux没有遵循任何一个标准.相反地,Linux致力于和两大主流标准兼容:POSIX和单一UNIX规范(Single UNIX Specification,SUS). 除了其他内容,POSIX和SUS为类UNIX操作系统定义了一套

《Linux系统编程(第2版)》——第1章 入门和基本概念 1.1 系统编程

第1章 入门和基本概念 摆在你面前的是一本关于系统编程的书,你将在本书中学习到编写系统软件的相关技术和技巧.系统软件运行在系统的底层,与内核和系统核心库进行交互.常见的系统软件包括Shell.文本编辑器.编译器.调试器.核心工具(GNU Core Utilities)以及系统守护进程.此外,网络服务.Web服务和数据库也属于系统软件的范畴.这些程序都是基于内核和C库实现的,可以称为"纯"系统软件.相对地,其他软件(如高级GUI应用),很少和底层直接交互.有些程序员一直在编写系统软件,而

《Linux系统编程(第2版)》——导读

前言 这本书是关于Linux上的系统编程."系统编程"是指编写系统软件,其代码在底层运行,直接跟内核和核心系统库对话.换句话说,本书的主题是Linux系统调用和底层函数说明,如C库定义的函数. 虽然已经有很多书探讨UNIX上的系统编程,却很少有专注于探讨Linux方面的书籍,而探讨最新版本的Linux以及Linux特有的高级接口的书籍更是凤毛麟角.此外,本书还有一个优势:我为Linux贡献了很多代码,包括内核及其上面的系统软件.实际上,本书中提到的一些系统调用和系统软件就是我实现的.因

《Linux系统编程(第2版)》——2.12 结束语

2.12 结束语 本章讨论了Linux系统编程的基础:文件I/O.在Linux这样遵循一切皆文件的操作系统中,了解如何打开.读.写和关闭文件是非常重要的.所有这些操作都是传统的UNIX方式,很多标准都涵盖它们.