复撸C系列(3)-文件基础IO

1.FILE结构体

从前面我们知道printf内部输出到stdout,而stdout本质上只是个FILE,那”Hello,World”我们完全可以写成


int _tmain(int argc, _TCHAR* argv[])
{
	fprintf(stdout,"Hello, world!");
	return 0;
}

事实证明效果是一样的:


那我们重点就变成了了解FILE,一起来看看定义:

typedef struct   _iobuf  { 
         char   *_ptr;             //缓冲区下一个位置 
         int       _cnt;           //剩余未读的字节数 
         char   *_base;         // 缓冲区首地址
         int       _flag;            //文件的标志 
         int       _file;             // 用于获取文件描述,可以使用fileno函数获得此文件的句柄。
         int       _charbuf;    //单字节的缓冲,即缓冲大小仅为1个字节,如果为单字节缓冲,_base将无效 
         int       _bufsiz;        //缓冲区的大小 
         char   *_tmpfname;     //临时文件名 
} FILE;

这样看起来不是很清晰,我们实际打开一个来看一下:


Test.txt 里面是有内容的:



那为何里面那些都是空指针,是因为还没有开始读取,里面都是一些初始值。但可以看到pFile本身这个文件指针是有效的,也就是我们的文件打开成功了。
那我们来读一个字符看一下:


调用了fread读取一个字节之后,可以看见_ptr指针向前走了一步,现在指向’e’这个字符,_cnt还剩余27个字符没有读,_base是缓冲区的首地址,_flag是文件标识符表示打开状态之类,_bufsiz是默认的缓冲区大小,这里系统默认4096,也就是4KB.

 

为什么需要缓冲区?大家都知道,硬盘的读取速度是很慢的,从内存里取最快,所以文件打开时按需要一次性读一大块进内存,再来从内存取东西就比较快捷了。


2.fopen

FILE * fopen ( const char * filename, const char * mode );

第一个参数是文件名,第二个是打开的模式,这里有详细的说明:


( http://www.cplusplus.com/reference/cstdio/fopen/)

流程:

Fopen请求系统分配资源-> 系统查看是否有空闲的文件句柄资源->分配资源,设置打开模式相关参数->返还一个有效的File*指针(有效地址)回来


3.fread

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

ptr  读入的buf首地址

size 每个元素的大小

count 读取的个数

stream 文件流

返回值 : 读取到的字节数目

 

上面的 fread(buf, 1, 1, pFile); 表示 读取size*count(1*1)也就是一个字节到buf中,文件流为打开的”test.txt”文件指针。系统最终还是要调用win32的接口函数ReadFile来完成读取工作:


其中还涉及到很多对缓冲区当前的判断之类,是出于效率的考虑所以这样设计。


4.fseek

int fseek ( FILE * stream, long int offset, int origin );

stream 文件指针

offset 偏移量

origin 起始位置 : SEEK_SET (文件开始),SEEK_CUR(文件当前读到的位置),SEEK_END(文件末尾)

返回值: 0代表成功  非零代表失败

 

下面给一个一次读取文件所有字符的例子:

int _tmain(int argc, _TCHAR* argv[])
{
    FILE *pFile =fopen("test.txt","r");
    fseek(pFile,0,SEEK_END);   //定位到文件末尾
    size_t len =ftell(pFile);  // 当前偏移即为文件长度
    rewind(pFile);  // 把文件_ptr指针重新指向开头处
    char buf[100] = {};
    fread(buf, 1, len,pFile);   //一次性读取文件到buf里
fclose(pFile);
    return 0;
}


 

其中的ftell 是返回当前的偏移量 rewind相当于 fseek(pFile,0,SEEK_SET),比较简单,这里就不做过多讲解。

 

5.fwrite

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

ptr 内存中准备写到缓冲区的字符串首地址

size 每个元素的大小

count 总共多少歌元素

stream 待写入的文件指针

 

返回值: 写入了多少字符

这里需要注意的是,这里的ptr是往缓冲区里写,之前我也误会认为直接写入文件里了。这里如果不手动的调用fflush的话,是要等到fclose才实际写入文件存到硬盘。

 

int _tmain(int argc, _TCHAR* argv[])
{
       FILE*pFile = fopen("writefile.txt","w");
       charbufForWrite[] = {"some string for writing\nthis is a test"};
       size_twriteCnt = fwrite(bufForWrite, 1, sizeof(bufForWrite), pFile);  //这里返回39,写入缓冲区的数目,但是现在打开writefile.txt里面还是没有任何东西的,因为字符还在缓冲区里,没有存入硬盘
       fclose(pFile);//这里存入硬盘,这时打开文件看就有了
       return0;
}

这里给个不等待fclose就写入的例子:

int _tmain(int argc, _TCHAR* argv[])
{
       FILE*pFile = fopen("writefile.txt","w");
       charfirstWrite[] = {"first string before fflush\n"};
       size_tfirstCnt = fwrite(firstWrite, 1, sizeof(firstWrite), pFile);  //此刻缓冲区有
字符串"first string before fflush\n"
       fflush(pFile);//刷新缓冲区,缓冲区的字符送入文件,缓冲区清空,此刻没任何东西了,文
件已有了内容
       charsecondWrite[] = {"second string after fflush\n"};
       size_tsecondCnt = fwrite(secondWrite, 1, sizeof(secondWrite), pFile);  //第二次往
缓冲区里写"second string after fflush\n"
       fclose(pFile);  //文件关闭,缓冲区所有内容送入文件
       return0;
}

调用fflush之后,文件马上有了内容:


6.小结

通过本篇文章的介绍,我们了解了文件结构体各个成员的意义,以及基础的文件读写操作,尤其是加深了对“文件缓冲区”的印象。数据操作的本质都是文件的操作,这也是LINUX把所有一切当做文件来对待的意思,只是在WINDOWS区分了文件和其他的概念,但究其本质仍然只是文件而已。我们程序员要清楚认识到在编程中我们拥有的主要原料是:文件,函数,寄存器,逻辑运算单元(CPU)。不要被所谓的“面向XX”迷惑,也不要被“XX语言”、“XX平台”迷惑。要抓住最本质的东西,基础才牢靠。

更详细更多的文件函数解析,待后面慢慢道来。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值