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结构体。
- typedef struct {
- short level; /* fill/empty level of buffer */
- unsigned flags; /* File status flags */
- char fd; /* File descriptor */
- unsigned char hold; /* Ungetc char if no buffer */
- short bsize; /* Buffer size */
- unsigned char *buffer; /* Data transfer buffer */
- unsigned char *curp; /* Current active pointer */
- unsigned istemp; /* Temporary file indicator */
- short token; /* Used for validity checking */
- } 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。
- #include <stdio.h>
- int ferror (FILE *fp);
- 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符之前是一个换行符,但并不要求总是如此。
下面举个例子:模拟文件的复制过程:
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <fcntl.h>
- #define maxsize 5
- int main(int argc, char *argv[])
- {
- FILE *fp1 ,*fp2;
- char buffer[maxsize];
- char *p,*q;
- if(argc < 3)
- {
- printf("Usage:%s <srcfile> <desfile>\n",argv[0]);
- return -1;
- }
- if((fp1 = fopen(argv[1],"r")) == NULL)
- {
- perror("fopen argv[1] fails");
- return -1;
- }
- if((fp2 = fopen(argv[2],"w+")) == NULL)
- {
- perror("fopen argv[2] fails");
- return -1;
- }
- while((p = fgets(buffer,maxsize,fp1)) != NULL)
- {
- fputs(buffer,fp2);
- }
- if(p == NULL)
- {
- if(ferror(fp1))
- perror("fgets failed");
- if(feof(fp1))
- printf("cp over!\n");
- }
- fclose(fp1);
- fclose(fp2);
- return 0;
- }
执行结果如下:
- fs@ubuntu:~/qiang/stdio/cp$ ls -l
- total 16
- -rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
- -rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
- -rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
- fs@ubuntu:~/qiang/stdio/cp$ ./cp time.c 1.c
- cp over!
- fs@ubuntu:~/qiang/stdio/cp$ ls -l
- total 20
- -rw-rw-r-- 1 fs fs 437 Jan 5 21:09 1.c
- -rwxrwxr-x 1 fs fs 7503 Jan 5 15:49 cp
- -rw-rw-r-- 1 fs fs 736 Jan 5 15:50 cp.c
- -rw-rw-r-- 1 fs fs 437 Jan 5 15:15 time.c
- fs@ubuntu:~/qiang/stdio/cp$
我们可以看到,这里将time.c拷贝给1.c ,1.c和time.c大小一样,都是437个字节;
3、以指定大小为单位读写文件
三、流的定位
四、格式化输入输出
这里举个相关应用例子:循环记录系统时间
实验内容:程序每秒一次读取依次系统时间并写入文件
- #include <stdio.h>
- #include <unistd.h>
- #include <time.h>
- #define N 64
- int main(int argc, char *argv[])
- {
- int n;
- char buf[N];
- FILE *fp;
- time_t t;
- if(argc < 2)
- {
- printf("Usage : %s <file >\n",argv[0]);
- return -1;
- }
- if((fp = fopen(argv[1],"a+")) == NULL)
- {
- perror("open fails");
- return -1;
- }
- while(1)
- {
- time(&t);
- fprintf(fp,"%s",ctime(&t));
- fflush(fp);
- sleep(1);
- }
- fclose(fp);
- return 0;
- }
执行结果如下:
- fs@ubuntu:~/qiang/stdio/timepri$ ls -l
- total 12
- -rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
- -rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
- fs@ubuntu:~/qiang/stdio/timepri$ ./time 1.txt
- ^C
- fs@ubuntu:~/qiang/stdio/timepri$ ls -l
- total 16
- -rw-rw-r-- 1 fs fs 175 Jan 5 21:14 1.txt
- -rwxrwxr-x 1 fs fs 7468 Jan 5 16:06 time
- -rw-rw-r-- 1 fs fs 451 Jan 5 17:40 time.c
- fs@ubuntu:~/qiang/stdio/timepri$ cat 1.txt
- Tue Jan 5 21:14:11 2016
- Tue Jan 5 21:14:12 2016
- Tue Jan 5 21:14:13 2016
- Tue Jan 5 21:14:14 2016
- Tue Jan 5 21:14:15 2016
- Tue Jan 5 21:14:16 2016
- Tue Jan 5 21:14:17 2016
- fs@ubuntu:~/qiang/stdio/timepri$
Linux 系统应用编程——标准I/O的更多相关文章
- Linux 系统应用编程——进程基础
一.Linux下多任务机制的介绍 Linux有一特性是多任务,多任务处理是指用户可以在同一时间内运行多个应用程序,每个正在执行的应用程序被称为一个任务. 多任务操作系统使用某种调度(shedule)策 ...
- linux系统串口编程实例
在嵌入式开发中一些设备如WiFi.蓝牙......都会通过串口进行主机与从机间通信,串口一般以每次1bit位进行传输,效率相对慢. 在linux系统下串口的编程有如下几个步骤,最主要的是串口初始化! ...
- Linux系统shell编程自学_第一章基础
第一章 基础shell的优势在于处理操作系统底层的业务,Python,php的优势在于开发运维工具,web界面的管理工具以及web业务开发.处理一键安装.优化.报警脚本shell又叫命令解释器,它能识 ...
- 《linux系统及其编程》实验课记录(五)
实验 5:权限的设置和更改 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统.有另外一个无特权用户 student,密码 student 的 ...
- 《linux系统及其编程》实验课记录(一)
实验 1:登录和使用基本的 Linux 命令 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统. 有另外一个无特权用户 student,密码 ...
- Linux系统层级结构标准
Linux Foundation有一套标准规范: FHS: Filesystem Hierarchy[‘haɪərɑːkɪ] Standard(文件系统层级标准)目前最新的标准是2.3版本:http: ...
- 《linux系统及其编程》实验课记录(六)
实验 6:Linux 文件系统 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统.有另外一个无特权用户 student,密码 student ...
- Linux 系统应用编程——线程基础
传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程.每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程.每个进程的全部系统资源是私有的,如虚拟地址空间,文 ...
- 《linux系统及其编程》实验课记录(二)
实验 2:获取对使用命令的帮助 实验环境: 安装了 Red Hat Enterprise Linux 6.0 可运行系统,并且是成功验证系统.有另外一个无特权用户 student,密码 student ...
随机推荐
- 今天我点亮了CSDN博客专家殊荣
很久以前,看着csdn博客学习第一篇博客时,我依旧记得,是一个名叫蒋老夫子的博客专家,文章写的非常认真.内心很崇拜.在想,若干年后,在哪个地方?以什么样的一种状态,也得到此殊荣,华为有句口号,叫勇敢做 ...
- React Native开发工具Nuclide使用
之前写RN的时候首选webstorm,这是之前做前端已经习惯的工具,其实RN开发官网推荐的是Nuclide工具, Nuclide是Fackbook专门为React开发IDE,今天也来尝试下,如果对we ...
- Android回调事件传播-android学习之旅(四十五)
概念简介 代码演示 package peng.liu.test; import android.app.ActionBar; import android.app.Activity; import a ...
- (NO.00004)iOS实现打砖块游戏(二):实现游戏主界面动画
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 一个内容不错的游戏也要一个好的包装.玩家进入游戏时第一眼看到的是 ...
- 使用HTML5拍照
原文连接地址: Camera and Video Control with HTML5 演示地址: HTML5拍照演示 翻译日期: 2013年8月6日 首先,我们看看HTML代码结构,当然,这部分的D ...
- java设计模式---构建者模式
创建者模式和工厂模式有点类似,不过关注点不同.工厂模式往往只关心你要的是什么,二不关心这个东西的具体细节是什么.而创建模式则关心的是这个东西的具体细节的创建.拿创建人物来说,我们关心的不仅是创建一个人 ...
- UNIX环境高级编程——进程基本概述
一.什么是进程 从用户的角度来看进程是程序的一次执行过程.从操作系统的核心来看,进程是操作系统分配的内存.CPU时间片等资源的基本单位.进程是资源分配的最小单位.每一个进程都有自己独立的地址空间与执行 ...
- 工作中常用的Linux命令
1.从其他机器拷贝文件夹 格式: scp -r 文件夹名 用户名@机器名:/路径 范例: scp -rsearch work@zjm-testing-ps23.zjm.baidu.com:/home/ ...
- vs2010 单文档MFC 通过加载位图文件作为客户区背景
实现效果: 这个其实是一个非常常见的功能,大家都会考虑给自己简单的工程做一个背景界面.其实只要在view类中重载OnEraseBkgnd()这个函数就好了. 代码如下: BOOL CdddView:: ...
- MySQL学习笔记_4_MySQL创建数据表(下)
MySQL创建数据表(下) 五.数据表类型及存储位置 1.MySQL与大多数数据库不同,MySQL有一个存储引擎概念.MySQL可以针对不同的存储需求选择不同的存储引擎. 2. showengines ...