本文主要参考了C Primer Plus (5th & 6th Edition)

您可以选择本文的部分内容来读,有些内容对于不熟悉MS-DOS的读者可能过于晦涩难懂。

C语言文件基本知识

  文件通常是在磁盘或固态硬盘上的一段已命名的存储区。所有的文件内容都以二进制形式储存。文件分为文本文件和二进制文件。

文件格式 定义 保存内容 示例
文本文件 文本文件就是最初使用二进制编码字符(如ASCII或Unicode)表示文本的文件 文本内容 .TXT文件
二进制文件 二进制文件就是文件中的二进制值代表机器语言代码或数值数据的文件 二进制内容 图片文件,可执行文件

  C语言提供两种途径访问文件:文本模式二进制模式。文本模式,顾名思义就是以文本形式访问文件,该文件通常是文本文件。不同的系统,处理文本文件的方式不同。UNIX系统使用'\n'表示换行,而早期的MS-DOS使用'\r'和'\n'组合换行,用Ctrl + Z表示文件结尾。旧式的OS X Macintosh却使用'\r'表示新的一行。这给程序员在不同系统中操作使用文件带来了诸多不便。还好,C语言提供了转换机制。例如在早期的MS-DOS中以文本模式打开某个文件file1.txt,它会自动把\r\n组合转换成'\n',如果要往该文件写入内容时又会把'\n'转换成\r\n组合。当以二进制模式打开文件时,程序将访问到文件的每一个字节,程序员如果需要处理文本文件,就必须根据操作系统的不同而采取不同措施。

  文件的结尾标志着文件内容的结束,C语言用宏EOF(End of File)表示文件的结尾,其值通常为-1。

  C程序自动打开3个文件,它们是:

文件 通常使用的默认设备 C程序中的表示法
标准输入 键盘 stdin
标准输出 显示屏 stdout
标准错误输出 显示屏 stderr

  那么如何更改这些文件的默认设备呢?我们可以通过重定向的方法。

MS-DOS重定向

  使用带有MS-DOS6.22系统的计算机,假定才C:\user中有如下文件:

  

  文件defin.txt中有如下内容:

  

  insort.exe是一个插入排序的程序。

  重定向输入为defin.txt,然后执行程序:

  

  发现其实重定向输入就是把defin.txt的内容与sdtin流相关联。其中'<'是重定向运算符。这样的优势是,如果遇到大量数据输入时,可以先把数据输入到文件,再重定向输入运行程序,十分方便且易于检查和修改错误。可是我们看到,程序运行后死机,原因是程序结尾处有代码getch();,而stdin流已经与defin.txt相关联,本例中defin.txt的末尾处没有可以使getch();执行的内容,且键盘不再是stdin流的默认设备,所以,程序将永远无法退出(单任务纯DOS环境)。由此可见,重定向输入也是有风险的。(同时提醒各位读者,在设计单任务纯DOS下运行的程序时,如无必要,不需要在程序末尾添加暂停指令。必须要有相应的退出指令)

  同样,我们也可以重定向输出:

  

  这样,所有的输出都被发送到defout.txt中。其中'>'是重定向输出运算符,它把defout.txt和stdout相关联。打开defout.txt我们可以看到:

  

  这样,我们就把原本输出到stdin默认设备---显示屏上的内容都输入到文件defout.txt中了。但是,你看不到输出的内容,所以重定向输出只在特殊情况下使用,如UNIX服务器。

  我们还可以使用组合重定向

  

  其中,<defin.txt和>defout.txt可以调换位置。符号左右的空格如果系统允许可以省略。

  需要注意的是,我们不能同时重定向输入或输出多个文件或重定向输入输出与可执行文件关联,使用重定向与文件关联时要保证文件有结尾EOF(End Of File),使用>重定向输出时,输出的文件内的数据将被覆盖。

文件打开fopen()和关闭fclose()函数

  使用重定向输入输出来操作文件不仅十分繁琐而且还存在相应的危险。C语言为程序员提供了更加直观方便的文件访问机制,即程序员可以在程序中直接操作文件而不需要去做类似重定向这样的“低级”行为。

  我们如果要在程序中操作一个文件,这个文件同该程序在相对的同目录下(如果使用IDE就要到IDE默认路径创建),就可以像这样打开文件:

FILE * fp; //声明文件指针
fp = fopen("file1.txt", "r"); //以只读模式打开文件
if(fp == NULL)
exit(EXIT_FAILURE); //如果打开失败就退出

  fopen()函数在成功打开后返回指向该文件指针,否则返回NULL。接受两个参数,第一个参数表示文件的名称(包含文件名的字符串地址),第二个参数表示打开模式(这与“文本模式和二进制模式”是一样的,只是细分了这些模式。),常用的打开模式如下:

  此外C11还增加了x模式,为了不增加读者的负担,本文不详述。

  和动态内存分配一样,我们需要检测文件是否成功打开,而且在执行完相应操作后有必要及时关闭文件。关闭文件我们用fclose(),和free()一样简单,没有返回值:

fclose(fp);//关闭文件
if(fp != )
exit(EXIT_FAILURE);//如果关闭失败就退出

  但是,有时候,文件不能正常关闭,例如程序运行时硬盘有故障或被拔出。因此,我们十分有必要在关闭之后检查一下状态。

文本模式文件I/O

操作单字符---getc()和putc()函数

  C Primer Plus(Sixth Edition)对这两个函数的解释非常清楚:{

  getc()和putc()函数与getchar()和putchar()函数类似,不同的是,要告诉getc()和putc()函数使用哪一个文件。下面这条语句的意思是“从标准输入中获取一个字符”:

ch = getchar();

  而下面这条语句的意思是“从fp指定的文件中获取一个字符”:

ch = getc(fp);

  与此类似,下面语句的意思是“把字符ch放入FILE指针fpout指定的文件中”:

putc(ch, fpout);

  }

  这里我们需要注意getc()的返回值是int类型的,这样是为了返回EOF,虽然有些系统把char类型默认定义signed char,但为了保证程序最大的可移植性,上面代码中ch字符实际上是int类型的。

  C程序只有在读到超过文件末尾之后才会发现文件结尾(EOF),所以为了避免空文件被错误读取,我们应在执行相应操作之前尝试读取文件,再根据是否读到EOF 来执行相应操作。

  这里插一段:有些读者朋友会问getc()和fgetc()以及putc()和fputc()之间有什么关系?其实在查阅相关文档之后,两组函数几乎是一模一样的,只不过或许getc()和putc()是fgetc()和fputc()的宏实现,原话如下:

  

  

  所以在调用getc()和putc()时,其参数不能是具有副作用的表达式。例如get(fp++)这样的,这是不允许的!

格式化输出输入到文件---fprintf()和fscanf()函数

  fprintf(),fscanf()和printf(),scanf()类似,只不过在原有基础上加了一个参数来确定输出或输入的目标文件。例如,如果有一个FILE指针fp已经指定一个有效的文件,并以"w"模式打开它,我们这样做可以将Hello World输出到这个文件上:

fprintf(fp, "Hello World");

  同样,如果有一个字符串"string"被保存在FILE指针fp指定的有效文件的最开始,并以"r"模式打开它,我们这样做将"string"输入到数组st上:

fscanf(fp, "%s", st);

  请留意这里的输入输出,它们并不指从标准输入输出设备获得输入输出,而是一种传递的关系:

  我们通过C Primer Plus(Fifth Edition)上的一个相关示例来演示这两个函数以及rewind()的具体应用:

字符串输入输出到文件---fgets()和fputs()函数

  fgets()函数和fputs()函数之前已在《字符串的输入输出》这篇博文介绍过,只不过现在将它的用途扩展到全体文件。我们假定fp是一个有效的FILE指针,那么从fp指定的文件中读取一段长度为SIZE - 1的字符串(不包括'\0')到数组st,我们可以这样:

fgets(st, SIZE, stdin);
//fgets()函数读取SIZE-1个字符或遇到'\n'结束,并把最后一个字符或'\n'之后的字符赋值为'\0',也就是说在某些情况下它保存了'\n'。

  同理,fputs()将字符串"Hello World\n"输出到fp所指定的文件:

fputs(fp, "Hello World\n");
//因为fgets()保存了'\n'所以fput()不会自动在字符串末尾加上'\n'

二进制模式文件I/O

文件定位---fseek()和ftell()函数

  如果部分读者读到这里可能会吃不消,因为我们发现,与文件操作这块内容相关的函数非常多,很难记忆,不过没有关系,你不必去死记硬背,只要记住函数的大致功能,用到时再去查C标准库参考,久而久之就慢慢会记住了。

  ftell()函数返回一个long类型的值,该值表示文件当前位置距离文件开始处的字节数目,以此来确定文件指针当前指向的位置。它接受一个参数,该参数表示一个有效的文件指针。如下代码所示,我们假定fp是一个有效的FILE指针:

long int addr;
addr = ftell(fp);

  addr就返回文件的当前位置,如下图所示:

  由此可见,ftell()将文件当成数组来处理,我们就可以用处理数组的方法来处理文件。但是,这有一个重要的前提,就是文件必须用二进制模式打开,否则将会出现毫无意义的结果。

  fseek()函数可以改变文件的当前位置,它实现的前提也应是以二进制模式打开文件。它接受三个参数,第一个参数指明要进行操作的文件指针,第二个参数我们称作偏移量,这是一个long型的参数,如果往文件开始处偏移就是负值,否则为正。第三个参数是模式,用来表示偏移的起点。模式有三种:

  我们通过书中的示例来解释这两个函数的应用(因为是以二进制模式打开,所以针对不同的系统,读取文本文件就要有不同的实现代码):

二进制文件I/O---fread()和fwrite()函数

  我们先前了解到,如果C程序以二进制模式访问一个文件,那么它将可以访问该文件的所有字节。文件定位的示例中演示了这个特性。那么,为什么需要以二进制模式对文件进行操作呢?假设我们要在文件中存储1/3这个值,我们选择文本模式,那么存储的精度就会大大降低,也更加地麻烦(因为要将数字转换成字符串就必须指定一个精度,读取时也必须指定同样的精度,如果将1/3以0.33存入文件,下次读取就不能恢复它原来的精度)。所以,我们最好的选择就是用相同的位格式来存储值,我们可以使用sizeof(double)个字节的空间来存储1/3,这和程序在内存中存储double变量的位格式相同,相当于拷贝了一个值为1/3的double变量到文件中,读取时按照原本写入时的精度去读取,就可以恢复最大的精度。为了完成上述操作,我们可以使用fread()和fwrite()函数。

  fwrite()的原形是:

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

  其中,ptr指定要写入的数据块的地址(原地址),size表示写入数据块的大小,nmemb表示写入数据块的个数(这能很方便地写入数组),stream指定要写入的文件(目标地址)。例如我们要把1/3这个数以最大精度写入文件pro.dat,我们需要这样做(这一节只给出核心代码):

FILE * fp;
double num = 1.0 / 3.0; fp = fopen("pro.dat", "wb"); //以二进制写模式打开文件
if( !fp )
exit(EXIT_FAILURE); fwrite(&num, sizeof(double), , fp); //写入二进制数据 if( fclose(fp) != ) //关闭文件
exit(EXIT_FAILURE);

  然后,我们查看相对与程序同目录的文件夹发现pro.dat创建成功,并且大小为8字节,正是当前系统double类型变量的大小。

  现在,如果我们要读取pro.dat文件里的内容,我们就要使用fread()函数了,因为以文本模式打开这个文件会出现乱码。fread()函数的原形如下:

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

  同样是四个参数,第一个参数指名要读取到的数据块地址(目标地址),第二三个参数与fwrite()的中对应参数相同,表示读取的大小,最后一个参数指明要读取的文件。

  下面的代码演示了如何使用pro.dat文件内容:

FILE * fp;
double get; fp = fopen("pro.dat", "rb"); //以二进制读模式打开文件
if( !fp )
exit(EXIT_FAILURE); fread(&get, sizeof(double), , fp); //读取文件内容到get if( fclose(fp) != ) //及时关闭文件
exit(EXIT_FAILURE); printf("get * 3.0 = %lf",get * 3.0); //输出

  如无意外,代码运行之后会显示:

  

结语

  这篇文章我概括地写出了有关C语言文件的基本操作,如果您手头上正好有一本C Primer Plus(第5或6版),请最好结合着书看本文。

  通过本文,我们知道了:

  〉〉什么是文件?

  〉〉MS-DOS系统下重定向的有关操作和反映问题

  〉〉文本的打开和关闭fopen()和fclose()

  〉〉文本模式I/O:getc()和putc(),fprintf()和fscanf(),fgets()和fputs()

  〉〉文件定位:fseek()和ftell()

  〉〉二进制模式I/O:fread()和fwrite()

  在本文的编撰过程中,由于本文的篇幅较大,图片较多,部分内容偏难,我耗费了很长时间才完成。文中的错误也是不可避免的,如果您在读完之后发现有什么错误,或是有什么建议,欢迎批评指出!

附录:其他标准I/O函数(选自C Primer Plus 5)

C文件I/O超详细教程的更多相关文章

  1. Github上传代码菜鸟超详细教程【转】

    最近需要将课设代码上传到Github上,之前只是用来fork别人的代码. 这篇文章写得是windows下的使用方法. 第一步:创建Github新账户 第二步:新建仓库 第三部:填写名称,简介(可选), ...

  2. WebRTC VideoEngine超详细教程(三)——集成X264编码和ffmpeg解码

    转自:http://blog.csdn.net/nonmarking/article/details/47958395 本系列目前共三篇文章,后续还会更新 WebRTC VideoEngine超详细教 ...

  3. Struts2+Spring4+Hibernate4整合超详细教程

    Struts2.Spring4.Hibernate4整合 超详细教程 Struts2.Spring4.Hibernate4整合实例-下载 项目目的: 整合使用最新版本的三大框架(即Struts2.Sp ...

  4. 安装64位Oracle 10g超详细教程

    安装64位Oracle 10g超详细教程 1. 安装准备阶段 1.1 安装Oracle环境 经过上一篇博文的过程,已经完成了对Linux系统的安装,本例使用X-Manager来实现与Linux系统的连 ...

  5. 数学规划求解器lp_solve超详细教程

    前言 最近小编学了运筹学中的单纯形法.于是,很快便按奈不住跳动的心.这不得不让我拿起纸和笔思考着,一个至关重要的问题:如何用单纯形法装一个完备的13? 恰巧,在我坐在图书馆陷入沉思的时候,一位漂亮的小 ...

  6. c++ 网络编程(九)LINUX/windows-IOCP模型 多线程超详细教程及多线程实现服务端

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9661012.html 先讲Linux下(windows下在后面可以直接跳到后面看): 一.线程 ...

  7. Emmet超详细教程

    Emmet超详细教程 一.总结 一句话总结:用的时候照着用,能提高效率. 1.快捷键如何使用? 需要敲代码的时候把快捷键放到旁边即可.照着敲. 二.Emmet超详细教程 Emmet的前身是大名鼎鼎的Z ...

  8. 命令创建.net core3.0 web应用详解(超详细教程)

    原文:命令创建.net core3.0 web应用详解(超详细教程) 你是不是曾经膜拜那些敲几行代码就可以创建项目的大神,学习了命令创建项目你也可以成为大神,其实命令创建项目很简单. 1.cmd命令行 ...

  9. NumPy 超详细教程(3):ndarray 的内部机理及高级迭代

    系列文章地址 NumPy 最详细教程(1):NumPy 数组 NumPy 超详细教程(2):数据类型 NumPy 超详细教程(3):ndarray 的内部机理及高级迭代 ndarray 对象的内部机理 ...

随机推荐

  1. ASP 读取Word文档内容简单示例

    以下通过Word.Application对象来读取Doc文档内容并显示示例. 下面进行注册Word组件:1.将以下代码存档命名为:AxWord.wsc XML code复制代码 <?xml ve ...

  2. Linux系统下python代码运行shell命令的方法

    方法一:os.popen #!/usr/bin/python # -*- coding: UTF-8 -*- import os, sys # 使用 mkdir 命令 a = 'ls' b = os. ...

  3. 《你又怎么了我错了行了吧》【Alpha】Scrum meeting 3

    第三天 日期:2019/6/16 前言: 第3次会议在女生宿舍召开 讨论了项目功能改进问题,继续代码完善和安排 1.1 今日完成任务情况以及明天任务安排 姓名 当前阶段任务 下一阶段任务 刘 佳 对已 ...

  4. vue-cli快速搭建

    vue-cli是用于开发大型vue项目的脚手架工具,使用vue-cli搭建好平台后,只需要关注程序的开发,不用过多的花时间去思考文件配置的问题 当然,还是可以任意进行自定义配置,官方地址:vue-cl ...

  5. 做支付遇到的HttpClient大坑

    前言 HTTPClient大家应该都很熟悉,一个很好的抓网页,刷投票或者刷浏览量的工具.但是还有一项非常重要的功能就是外部接口调用,比如说发起微信支付,支付宝退款接口调用等:最近我们在这个工具上栽了一 ...

  6. 洛谷 U6254 最低费用

    U6254 最低费用 题目背景 小明暑假去国外游玩,到了最后一天,却发现自己的钱还不一定够去机场,于是他开始对国外特殊的交通方式进行研究,但是他发现路段的错综复杂使他头脑昏花,于是他打开电脑,希望你去 ...

  7. [Linux]第二部分-linux文件磁盘格式

    账户信息在/etc/passwd中,密码在/etc/shadow中,组信息在etc/group中 (d/-)rwxrwxrwx 1 root 293 Oct 19 21:24 test 文件属性 连接 ...

  8. pythonWeb -- Django开发- Admin

    [第一次使用Admin 要创建超级用户账号] 1.\ python manage.py createsuperuser You have 1 unapplied migration(s). Your ...

  9. Java中Array、List、Set、Map

    一.Java中数组 数组用来存放固定数量的同类元素,声明方法: T[] ref,T ref[],如int[] intAry; int intAry[].推荐用T[]的方式,后一种方式为兼容C++习惯写 ...

  10. category的概念

    category 的意思应该是为基类添加一个子类的声明方法 可以在创建基类对象的时候访问到子类的对象方法 category 可以说是 类的扩展 也可以说是 将类分成了几个模块 需要注意的是 在cate ...