c语言字符流缓冲区,深入理解C语言的IO及缓冲操作

http://www.shangxueba.com/jingyan/1837313.html

2014

与文件I/O围绕文件描述符操作不同,标准I/O的操作是围绕流进行的。

流:

对于流,《C和指针》里有一段解释得很好:

ANSI C进一步对I/O的概念进行了抽象。就C程序而言,所有的I/O操作只是简单地从程序移进或移出字节的事情。因此,毫不惊奇的是,这种字节流便被称为流(stream)。程序只需要关心创建正确的输出字节数据,以及正确地解释从输入读取的字节数据。特定I/O设备的细节对程序员是隐藏的。

TCPL Appendix B.1中这么解释:

A stream is a source or destination of data that may be associated with a disk or other peripheral.(流是一个可能与硬盘或者其他设备关联的数据的源或者目的地)

简单地说,流是对信息的一种抽象。C系统在处理文件(文本文件和二进制文件)时,并不区分类型,都看成是字符流,按字节进行处理。

输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。

流有最小的信息单元就是二进制位,含有最小的信息包就是字节,C标准库提供两种类型的流:二进制流(binary stream)和文本流(text stream)。二进制流是有未经处理的字节构成的序列;文本流是由文本行(每行有0个或多个字符,并以'\n'结束)组成的序列。注意在UNIX中,并不区别两种流。

当一个程序启动时,,标准输入、输出、出错三个流就已经被自动打开,并对应到默认的物理终端。这三个标准I/O流通过预定义(stdio.h)文件指针stdin,stdout,stderr加以引用当一个进程正常终止时(直接调用exit(),或从main返回)所有打开的标准I/O流都会被关闭,所有带未写缓冲数据的I/O流都会被冲洗。

PS:在main()中return(expr)等价于exit(expr),而exit则调用fclose()关闭每个文件描述符并刷洗对应缓存。

在Linux的应用程序中,通常用文件描述符0,1,2与标准输入,标准输出,标准出错输出相关联。为符合POSIX规范,在中,0,1,2分别被替换成常量符号STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。

缓冲区:

(详细内容可以参考APUE3.9和5.4,本段纯属摘抄)

标准I/O提供缓存的目的是尽可能减少使用read和write调用的数量(系统调用比普通函数调用开销大)。它也对每个I/O流自动地进行缓存管理,避免了应用程序需要考虑这一点所带来的麻烦。

标准I/O提供了三种类型的缓存:

(1) 全缓存。在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。对于驻在磁盘上的文件通常是由标准I/O库实施全缓存的。

(2) 行缓存。在这种情况下,当在输入和输出中遇到新行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(用标准I/O fputc函数),但只有在写了一行之后才进行实际I/O操作。

(3) 不带缓存。标准I/O库不对字符进行缓存。如果用标准I/O函数写若干字符到不带缓存的流中,则相当于用write系统调用函数将这些字符写至相关联的打开文件上。

标准出错流stderr通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个新行字符。

ANSI C要求下列缓存特征:

(1) 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。

(2) 标准出错决不会是全缓存的

但是,这并没有告诉我们如果标准输入和输出涉及交互作用设备时,它们是不带缓存的还是行缓存的,以及标准输出是不带缓存的,还是行缓存的。

SVR4和4.3 + BSD的系统默认使用下列类型的缓存:

标准出错是不带缓存的。

如若是涉及终端设备的其他流,则它们是行缓存的;否则是全缓存的。

可以通过下面的函数改变缓存类型(APUE5.4):void setbuf(FILE *restrict fp, char *restrict buf);int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);这些函数必须在流打开之后、但是未对流做任何操作之前被调用

参数buf通常指向一个长度为BUFSIZ的缓冲区,BUFSIZ在stdio.h中定义,可自行输出查看stdio.h:#ifndef BUFSIZ# define BUFSIZ _IO_BUFSIZlibio.h:#define _IO_BUFSIZ _G_BUFSIZ_G_config.h:#define _G_BUFSIZ 8192如要关闭缓冲,将buf置为NULL

Liunx上的默认情况是,当标准输入输出连接终端时是行缓冲的,缓冲区大小1024字节,重定向到普通文件时,他们变为全缓冲(APUE 5.12 程序5.3提供查看I/O相关信息的方法)

强制冲洗一个流

int fflush(FILE *fp)

使该流所有未写数据传送至内核。如fp为NULL,将使所有输出流被清洗。

应当注意的是:fflush(NULL)并不能有效地清空输入缓存。后面详细讨论

常用I/O函数:

流打开:

FILE * fopen ( const char * filename, const char * mode );FILE * freopen ( const char * filename, const char * mode, FILE * stream );常用freopen进行输入输出重定向。

单字符读写:

int getc(FILE *fp)int fgetc(FILE *fp)int getchar(void)getchar等价于getc(stdin)。前两个函数区别在于,getc可被实现为宏,意味着fgetc调用时间略长。

不管是出错还是到达文件尾端,三个函数都返回-1.

在大多数实现中,FILE维护了两个标志:出错标志和文件结束标志。可用下面三个函数判断流是出错还是结束,最后一个函数是清除两标志:int ferror(FILE *fp)int foef(FILE *fp)void clearerr(FILE *fp)

还有一个神奇的函数,可以把字符压送回流中:int ungetc(int c, FILE *fp)注意不能回送EOF

类似地,输出函数:int putc(int c, FILE *fp)int fputc(int c, FILE *fp)int putchar(int c)putchar(c)等价于putc(c,stdout),putc可被实现为宏。

通常为了避免过多的函数调用开销,putchar和getchar都被实现为宏。

行读写函数:

char * fgets ( char * str, int num, FILE * stream );它会读取不超过num-1个字符,然后在末尾加上结束符 '\0' ,或者遇到换行符结束输入,同时换行符也被传入。

另一个函数:

char * gets(char* buf);

由于存在缓冲区溢出漏洞,不推荐使用。

相应地,输出int fputs(const char * str, FILE * fp);int puts(const char *char);fputs()将一个以NULL终止的字符串写到指定的流,终止符NULL不写出。

puts()虽然安全,但是他的不方便在于,每次将换行符写到输出。

因此,我们贯彻这样的方针,坚持使用fgets和fputs,并自己处理换行符。各I/O函数的效率对比可参看APUE5.9

格式化I/O:

格式化输出:int printf ( const char * format, ... );int fprintf (FILE *fp, const char * format, ... );int snprintf (char *buf, size_t n, const char * format, ... );

注意参数转换说明中 %[flags][fldwidth][lenmodifier]convtype,宽度和精度字段可被置为*,而后用一个整形参数指定其值。

printf的实现:

printf是C中为数不多的变参函数之一,主要通过stdarg.h中的一系列宏来对参数列表进行处理。其源码实现可以参看:点击打开链接和点击打开链接

使用变参函数机制,简单模拟printf#include#include#include#includevoid simon_printf(char *fmt, ...){char buf[10];char *p = fmt;char c_tmp, *s_tmp;int i_tmp;double f_tmp;va_list ap;va_start(ap, fmt);while (*p){if (*p != '%'){putchar(*p++);continue;}else {switch (*++p){case 'd':{i_tmp = va_arg(ap, int);//sprintf(buf, "%d", i_tmp);//write(STDOUT_FILENO, buf, strlen(buf));printf("%d", i_tmp);break;}case 'f'://float在内部被提升为double{f_tmp = va_arg(ap, double);printf("%f", f_tmp);break;}case 'c'://char在内部被提升为int{i_tmp = va_arg(ap, int);printf("%c", i_tmp);break;}case 's':{for(s_tmp = va_arg(ap, char*); *s_tmp; s_tmp++)printf("%c", *s_tmp);break;}}p++;}}va_end(ap);}int main(){int a = 1;float b = 2.0;char c = 'a';char *str = {"test"};simon_printf("This is a test Message:\n int:%d\n float:%f\n string: %s\n char:%c\n ", a, b, str, c);return 0;}

格式化输入:int scanf(const char *format, ...);int fscanf(FILE *fp, const char *format, ...);int sscanf(const char *buf, const char *format, ...);

scanf中*表示抑制,不把该输入赋值给对应变量,即跳过。

scanf()还有一些正则用法:[]表示输入字符集,可以使用连字符表示范围,scanf() 连续吃进集合中的字符并放入对应的字符数组,直到发现不在集合中的字符为止。用字符 ^ 可以说明补集。把 ^ 字符放为扫描集的第一字符时,构成其它字符组成的命令的补集合。

通常并不推荐使用scanf()的正则用法,用法复杂, 容易出错。编译器作语法分析时会很困难, 从而影响目标代码的质量和执行效率。

关于输入缓冲区清空:

1)fflush(NULL)

fflush的定义说得很清楚了,这种用法导致的结果不确定

If the given stream was open for writing (or if it was open for updating and the last i/o operation was an output operation) any unwritten data in its output buffer is written to the file.

If stream is a null pointer, all such streams are flushed.

In all other cases, the behavior. depends on the specific library implementation. In some implementations, flushing a stream open for reading causes its input buffer to be cleared (but this is not portable expected behavior).

The stream remains open after this call.

When a file is closed, either because of a call to fclose or because the program terminates, all the buffers associated with it are automatically flushed.

如果stream指向输出流或者更新流(update stream),并且这个更新流最近执行的操作不是输入,那么fflush函数将把任何未被写入的数据写入stream指向的文件(如标准输出文件stdout)。

fflush(NULL)清空所有输出流和上面提到的更新流。

否则,fflush函数的行为是不确定的。取决于编译器,某些编译器(如VC6)支持用 fflush(stdin) 来清空输入缓冲,而gcc就不支持。

2)setbuf(stdin, NULL);

setbuf(stdin, NULL);是使stdin输入流由默认缓冲区转为无缓冲区,在没有特殊要求的情况下还是适用的

3)int c;

while((c = getchar()) != '\n' && c != EOF);

由代码知,不停地使用getchar()获取缓冲区中字符,直到获取的字符c是换行符’\n’或者是文件结尾符EOF为止。这个方法可以完美清除输入缓冲区,并且具备可移植性。

4)scanf("%[^\n]%*c");

这里用到了scanf格式化符中的“*”,即赋值屏蔽;“%[^集合]”,匹配不在集合中的任意字符序列。这也带来个问题,缓冲区中的换行符’\n’会留下来,需要额外操作来单独丢弃换行符。

声明:该文章系网友上传分享,此内容仅代表网友个人经验或观点,不代表本网站立场和观点;若未进行原创声明,则表明该文章系转载自互联网;若该文章内容涉嫌侵权,请及时向上学吧网站投诉>>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/349199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

maven插件依赖_当Maven依赖插件位于

maven插件依赖问题: 我们进行了一个集成测试,该测试创建了一个Spring ClassPathXmlApplicationContext ,同时这样做导致NoSuchMethodError爆炸。 事实证明,我们对Spring构件的依赖版本存在冲突。 这本身不是一个不寻常的问题-使用…

华为编程规范_华为 Java 编程规范出炉,究竟和官方文档有何不同?

来源:blog.csdn.net/chenleixing/article/details/441739851、引言这个标准是衡量代码本身的缺陷,也是衡量一个研发人员本身的价值。华为作为一家全球化的 IT 公司,十几万员工,无论是人事管理,还是代码管理&#xff0c…

变量是否在数组内c语言,函数内创建的数组在调用后,是否就不存在

函数内创建的数组在调用后,是不是就不存在本帖最后由 test_lockxxx 于 2014-04-23 09:31:06 编辑第1种写法:char *getABC(){char str[10];strcpy(str,"12345");return str;}void main(){char *p getABC();printf("%s\n",p); //这…

惯用并发:flatMap()与parallel()– RxJava常见问题解答

简单,有效和安全的并发是RxJava的设计原则之一。 然而,具有讽刺意味的是,它可能是该库中最容易被误解的方面之一。 让我们举一个简单的例子:假设我们有一堆UUID并且对于每个UUID ,我们必须执行一组任务。 第一个问题是…

python%20开发工具_20招让你的Python飞起来!

今天分享的这篇文章,文字不多,代码为主。绝对干货,童叟无欺,主要分享了提升 Python 性能的 20 个技巧,教你如何告别慢Python。原文作者 开元,全栈程序员,使用 Python, Java, PHP和C。1. 优化算法…

c语言万能预编译,Objective-C学习笔记

import 指令(将文件的内容在预编译的时候拷贝到写指令的地方)import Foundation 框架NSLog 函数 NSLog("Hello, World!");NSString语法: NSString *str "jack";字符串占位符: %数据类型C 语言支持的数据类型基本数据类型int double float char构造类型数组…

Java命令行界面(第19部分):jClap

本系列中第19篇文章的重点是从Java代码解析命令行参数是jClap ( Java命令行参数解析器 ),不应将它与称为JCLAP的库相混淆,而JCLAP库是我本系列先前文章的重点。 在以前的帖子覆盖JCLAP 1.4加尔斯吉尔温斯坦利( snaq.ne…

使用Arquillian和LocalStack脱机测试AWS云堆栈

在AWS云堆栈 (例如DynamoDB,S3等)上构建应用程序时,需要针对这些组件编写测试。 您可能首先想到的是拥有一个用于生产的环境和一个用于测试的环境,然后针对该环境运行测试。 这对于集成测试,部署测试&…

python取文本中间_Python读取两个字符串之间的特定文本行

我无法让python读取特定的行。我正在做的事情是这样的:lines of data not neededlines of data not neededlines of data not needed--------------------------------------***** REPORT 1 *****--------------------------------------[key] lines of interest ar…

c语言7.5return的值是,这个真心搞不懂了。求助

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼回复 15楼. 假如你每天签到拿4经验,300000/475000天,如果从1岁开始签到,那100年36500天,你差不多要活200年保持每天签到(谁知道200年后还有没有签到这玩意),如果你每天再水…

Java命令行界面(第22部分):argparser

John Lloyd的argparser是本系列的第二十二篇有关基于Java的命令行参数解析的文章中介绍的库。 该库的主页除了提供单个源代码示例外,还提供了指向基于Javadoc的API文档 ,JAR文件,ZIP文件和TAR文件的链接。 本帖子中使用的示例与本系列的前二十…

python中布尔类型的值包括0和1_Python中布尔型变量的值为0和1。( )

【单选题】以下哪个不能作为字典的键。【多选题】以下哪些元素是可变序列。【单选题】已知 x[1,2,3,4,5,6,7],执行语句x.pop()的结果是()。【多选题】以下哪几个可以作为字典的键。【单选题】以下哪个属于列表的定界符。【多选题】以下哪几个选项类型属于元组。【单选题】下面代…

如何粗暴地下载huggingface_hub指定数据文件

参考这里: https://huggingface.co/docs/huggingface_hub/guides/download 可见下载单个文件,下载整个仓库文件都是可行的。 这是使用snapshot_download下载的一个例子: https://qq742971636.blog.csdn.net/article/details/135150482 sn…

顺序表输入栈元素c语言,C语言数据结构之栈简单操作

C语言数据结构之栈简单操作实验:编写一个程序实现顺序栈的各种基本运算,并在此基础上设计一个主程序,完成如下功能:(1)初始化顺序栈(2)插入元素(3)删除栈顶元素(4)取栈顶元素(5)遍历顺序栈(6)置空顺序栈分析:栈的顺序存储结构简称…

rete_Rete之外的生活– RIP Rete 2013 :)

rete我只是对我的新算法做最后的修改。 它融合了Leaps , 面向集合的Match和Left / Right取消链接的概念 ,以及我自己的一些想法。 该代码已提交,但我正在积累工作并编写更多测试。 我将在一周左右的时间内写一个完整的博客,详细介…

25q64存储多个数据_一篇文章看懂,存储虚拟化在不同用例中的实践与优势

存储虚拟化是一种对物理存储资源进行抽象的技术,使其看起来像是一个集中的资源。虚拟化掩盖了管理内存、网络、服务器和存储中资源的复杂性。存储虚拟化运行在多个存储设备上,使它们看起来就像一个单一的存储池。这些池化的存储设备可以来自不同的供应商…

android代码画出波浪球,Android绘制波浪曲线,效果很赞的。

github地址:https://github.com/sddyljsx/Android-SurfView-WaveViewpackage neal.canvas;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import and…

Java命令行界面(第14部分):google-options

google-options的GitHub页面指出google-options是“来自Google(java)的人们的命令行参数解析库。” 该页面继续说:“这是Bazel Project中的命令行参数解析器。 com.google.devtools.common.options程序包已拆分为一个单独的jar,用…

python自动化工具哪个好用_10款好用的自动化测试工具推荐

当我们功能测试干的时间比较久了,或者想要学习更多的技术,提升自己的时候,基本上第一时间就会想到的是自动化测试。而在自动化测试领域,自动化工具的核心地位毋庸置疑,下面为大家推荐10款常见常用的自动化测试工具:1、SeleniumWEB自动化测试S…

android 输入法文本选择功能,Android的文本和输入---创建输入法(一)

输入法编辑器(IME)是让用户输入文本的控件。Android提供了一个可扩展的的输入法的框架,它允许应用程序给用户提供另外的输入法,如软键盘或语音输入。这些输入法一旦安装,用户就可以从系统的设置中选择他们想要使用的IME,并且这个设…