走进C标准库(4)——"stdio.h"中的putc
花了点时间把园子弄得好看了点,现在继续。
函数名: putc
功 能: 输出一字符到指定流中
用 法: int putc(int ch, FILE *stream);
#define _putc_lk(_c,_stream) (--(_stream)->_cnt >= 0 ? 0xff & (*(_stream)->_ptr++ = (char)(_c)) : _flsbuf((_c),(_stream)))
看到这个宏是否觉得很熟悉,很像getc的宏吧。
那么,很容易产生一个问题,同样是改变IO控制块中的信息,同时使用putc和getc能否正常地进行读写操作呢?
使用一个简单的样例测试了一下:
#include <stdio.h> int main(void)
{
char a;
int i = ;
FILE *fp = fopen("input.txt","r+");
a = getc(fp);
putc('b',fp);
putc('c',fp);
putc('d',fp);
putc('e',fp);
fclose(fp);
return ;
}
以上这段代码未能正常在input.txt文件中写入内容。
通过单步调试可以看到,getc是成功的,putc也是改变了缓冲区的,但是在写入文件这一步出了问题。
更进一步,比较了在fopen后,先进行getc和先进行putc,fp的各成员的值的异同。可以看到,当先进行getc时,fp->_flag=137,而当先进行putc时,fp->_flag=138。那么,显然在第一次使用函数读写fp时,就会为fp定下不同的读写标记。该标记会阻止其他类型的操作。
于是,在上述代码的第8行下加入代码:fp->_flag = 138后,putc成功。
从缓冲区的意义来说,不会说不允许同时支持读写。原因可能是同时读写文件在是实际意义上会造成读信息或写信息的混乱,亦或者可能是实现上的原因。猜测。
言归正传:
那么putc只有在FILE中为写信息的时候才生效。
当stream->_cnt大于0(缓冲区还有写空位),就继续写,如果没有空位了就调用_flsbuf((_c),(_stream)),通过该函数调用WriteFile,将缓冲区中的内容写入到文件中,然后重置_ptr的位置和_cnt的大小。
简单的逻辑。
完
后附《C陷阱与缺陷》中关于上述现象的描述。
5.2更新顺序文件
许多系统中的标准输入/输出库都允许程序打开一个文件,同时进行写入和读出的操作:
FILE *fp;
fp=fopen(file, "r+");
上面的例子代码打开了文件名由变量 file 指定的文件,对于存取权限的设定表明程序希望对这个文件进行输入和输出操作。
编程者也许认为,程序一旦执行上述操作完毕,就可以自由地交错进行读出和写入的操 作。遗憾的是,事实总难遂人所愿,为了保持与过去不能同时进行读写操作的程序的向下兼性,一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果 同时进行输入和输 出操作,必须在其中插入 fseek 函数的调用。
下面的程序片段似乎更新了一个顺序文件中选定的记录:
FILE *fp;
struct record rec;
...
while(fread((char*)&rec, sizeof(rec), , fp) == ){
/*对 rec 执行某些操作*/
if(/*rec 必须被重新写入*/){
fseek(fp, -(long)sizeof(rec), );
fwrite((char*)&rec, sizeof(rec), , fp);
}
}
这段代码乍看上去毫无问题:&rec 在传入 fread 和 fwrite 函数时被小心翼翼地转换为字 符指针类型,sizeof(rec)被转换为长整型(fseek 函数 求第二个参数是 long 类型,因为 int 类 型的整数可能无法包含一个文件的大小:sizeof 返回一个 unsigned 值,因此首先必须将其转 换为有符号类型才有可能将其反号)。但是这段代码仍然可能运行失败,而且出错的方式非 常难于察觉。
问题出在:如果一个记录需 被重新写入文件,也就是说,fwrite 函数得到执行,对这 个文件执行的下一个操作将是循环开始的 fread 函数。因为在 fwrite 函数调用与 fread 函数 调用之间缺少了一个 fseek 函数调用,所以无法进行上述操作。解决的办法是把这段代码改 写为:
while(fread((char*)&rec, sizeof(rec), , fp) == ){
/*对 rec 执行某些操作*/
if(/*rec 必须被重新写入*/){
fseek(fp, -(long)sizeof(rec), );
fwrite((char*)&rec, sizeof(rec), , fp);
fseek(fp, 0L, );
}
}
第二个 fseek 函数虽然看上去什么也没做,但它改变了文件的状态,使得文件现在可以 正常地进行读取了。
附录测试文件:
#include <stdio.h> int main()
{
FILE *fp;
struct record{
char a;
char b;
}; struct record rec; if((fp=fopen("test", "rb+"))==NULL)
{
printf("open error!\n");
}
while(fread((char*)&rec, sizeof(rec), , fp) == ){
/*对 rec 执行某些操作*/
printf("record.a=%d\n", rec.a);
printf("record.b=%c\n", rec.b);
if(){
fseek(fp, -(long)sizeof(rec), );
rec.a=rec.b;
fwrite((char*)&rec, sizeof(rec), , fp);
fseek(fp, 0L, ); /*此句关系是否能写入*/
}
}
fclose(fp);
}
走进C标准库(4)——"stdio.h"中的putc的更多相关文章
- 走进C标准库(3)——"stdio.h"中的getc和ungetc
接前文. 再来看看getc和ungetc的实现.在看这两个函数的实现之前,我们先来想一想这两个函数分别需要做的工作. int getc(FILE *stream) 说明:函数getc从stream指向 ...
- 走进C标准库(2)——"stdio.h"中的fopen函数
其他的库文件看起来没有什么实现层面的知识可以探究的,所以,直接来看stdio.h. 1.茶余饭后的杂谈,有趣的历史 在过去的几十年中,独立于设备的输入输出模型得到了飞速的发展,标准C从这个改善的模型中 ...
- 走进C标准库(5)——"stdio.h"中的其他部分函数
函数介绍来自:http://ganquan.info/standard-c/ 函数名: freopen 功 能: 替换一个流 用 法: FILE *freopen(char *filename, ...
- 走进C标准库(8)——"string.h"中函数的实现相关字符串操作函数
我的strcat: char *strcat(char *dest,char *src) { char * reval = dest; while(*dest) dest++; while(*src) ...
- 走进C标准库(6)——"string.h"中函数的实现memchr
我写的memchr: void *memchr(const void *buf, char ch, unsigned count){ unsigned ; while(*(buf++) != ch & ...
- 走进C标准库(7)——"string.h"中函数的实现memcmp,memcpy,memmove,memset
我的memcmp: int memcmp(void *buf1, void *buf2, unsigned int count){ int reval; while(count && ...
- 走进C标准库(1)——assert.h,ctype.h
默默觉得原来的阅读笔记的名字太土了,改了个名字,叫做走进C标准库. 自己就是菜鸟一只,第一次具体看C标准库,文章参杂了对<the standard C library>的阅读和对源码的一些 ...
- C语言中头文件<stdio.h>中的#ifndef _STDIO_H_
先了解这里的相关知识:http://www.cnblogs.com/stemon/p/4000468.html 头文件的中的#ifndef,这是一个很关键的东西.比如你有两个C文件,这两个C文件都in ...
- C标准头文件<stdio.h>
是很多人学C语言接触的第一个头文件,顾名思义,stdio就是"标准输入输出",其中声明了一组关于输入输出的类型,宏和函数,其中就包括了打印著名的"hello,world! ...
随机推荐
- iOS学习心得——UITableViewCell的复用
UITableView是在iOS开发中最常用的控件之一.我的第一篇学习心得献给它了 UITableView是由一行一行的UITableViewCell构成的. 首先想这 ...
- 前端开发的常用js库
验证: jQuery formValidator,Validform; 提示框: artDialog, lhgDialog,jBox,jQuery textbox plugin 文件批量上传:uplo ...
- 疯狂Java讲义 第一章控制台五子棋(代码分析)
package com.test; public class Chessboard { //定义二维数组作为棋盘 private String[][] board; //定义棋盘大小 public s ...
- JSP 适配手机屏幕
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scal ...
- 《C++ Primer Plus 6th》读书笔记 - 第十章 对象和类
1. 过程性编程和面向对象编程 2. 抽象和类 1. 使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员 2. 可以在类声明之外定义成员函数,并使其成为内 ...
- Flink Program Guide (5) -- 预定义的Timestamp Extractor / Watermark Emitter (DataStream API编程指导 -- For Java)
本文翻译自Pre-defined Timestamp Extractors / Watermark Emitter ------------------------------------------ ...
- 使用file_get_content系列函数和使用curl系列函数采集图片的性能对比
由于公司的一个汽车网站的后台的汽车内容都是主要是来自与汽车之家的,编辑的同事们必须天天手动去对着汽车之家来添加汽车,实在是太蛋疼了.于是乎,为了改变这种状况,作为一个开发码农,我的任务就来了...那就 ...
- 修改默认的undo_retention参数设置
昨天,一个朋友的数据库数据被误操作删除掉了,请求我帮忙进行恢复. 数据库版本是Oracle10g Release 2的,我首先想到的是使用Flashback Query进行闪回恢复,不幸的是ORA-0 ...
- PSAM SAM
第一个问题: 为什么要用SAM? 究竟谁最开始使用SAM这个词,已经无从考证,能够确认的是:这个世界上先有了PSAM,然后才有了SAM.由于网络状况的原因,或者是应用环境的要求,使用IC卡作为支付介质 ...
- 利用netstat和tasklist查看PC的端口占用情况
经常,我们在启动应用的时候发现系统需要的端口被别的程序占用,如何知道谁占有了我们需要的端口? 1.Windows平台在windows命令行窗口下执行: E:\oracle\ora92\bin>n ...