1.C语言中的文件是什么?
所谓文件(file)一般指存储在外部介质上数据的集合,比如我们经常使用的txt、bmp、jpg、exe、rmvb等等。这些文件各有各的用途,我们通常将它们存放在磁盘或者可移动盘等介质中。
文件无非就是一段数据的集合,这些数据可以是有规则的集合,也可以是无序的集合。操作系统也就是以文件为单位对数据进行管理的。也就是说,要访问外部介质上的数据,必须先按照文件名进行查找,然后从该文件中读取数据。要想写数据到外部介质,必须得建立一个文件,然后再写入。因此,你眼前的文件只是数据的集合。
1.1文件一般包括三要素:
文件路径、文件名、后缀。
由于在C语言中''一般是转义字符的起始标志,故在路径中需要用两个''表示路径中目录层次的间隔,也可以使用'/'作为路径中的分隔符。
例如:
"D:\\123\\test.c"或者"D:/123/test.c",表示文件test.c保存在D盘123目录下。
"tu.txt"表示当前目录下的文件tu.txt。
文件路径:可以显式指出其绝对路径,如上面的"D:\\"或者"D:/"等。如果没有显式指出其路径,默认认为当前路径。也就相对路径。
数据的输入和输出几乎伴随着每个C语言程序,所谓输入就是从"源端"获取数据,所谓输出可以理解为向"终端"写入数据。这里的源端可以是键盘、鼠标、硬盘、光盘、扫描仪等输入设备,终端可以是显示器、硬盘、打印机等输出设备。在C语言中,把这些输入和输出设备也看作"文件"。
1.2 C语言文件系统中的类型
FILE:对象类型,足以保有控制 C I/O 流所需的全部信息
fpos_t:非数组完整对象类型,足以唯一指定文件的位置和多字节剖析状态
每个 FILE 对象直接或间接保有下列信息:
● (C95)字符宽度:未设置、窄或宽。
● (C95)多字节与宽字符间转换的分析状态(mbstate_t类型对象)
● 缓冲状态:无缓冲、行缓冲、全缓冲。
● 缓冲区,可为外部的用户提供缓冲区所替换。
● I/O 模式:输入、输出或更新(兼具输入与输出)。
● 二进制/文本模式指示器。
● 文件尾指示器。
● 错误状态指示器。
● 文件位置指示器,可作为fpos_t类型对象访问,对于宽流包含剖析状态。
● (C11)在多个线程读、写、寻位或查询流时避免数据竞争的再入锁。
1.3 预定义标准流
● stdin 与标准输入流关联的 FILE* 类型表达式
● stdout 与标准输出流关联的 FILE* 类型表达式
● stderr 与标准错误输出流关联的 FILE* 类型表达式
1.4 宏常量
2.流的概念及分类
I/O设备的多样性及复杂性,给程序设计者访问这些设备带来了很大的难度和不便。为此,ANSIC的I/O系统即标准I/O系统,把任意输入的源端或任意输出的终端,都抽象转换成了概念上的“标准I/O设备”或称“标准逻辑设备”。程序绕过具体设备,直接与该“标准逻辑设备”进行交互,这样就为程序设计者提供了一个不依赖于任何具体I/O设备的统一操作接口,通常把抽象出来的“标准逻辑设备”或“标准文件”称作“流”。
把任意I/O设备,转换成逻辑意义上的标准I/O设备或标准文件的过程,并不需要程序设计者感知和处理,是由标准I/O系统自动转换完成的。故从这个意义上,可以认为任意输入的源端和任意输出的终端均对应一个“流”。
●流按方向分为:输入流和输出流。从文件获取数据的流称为输入流,向文件输出数据称为输出流。
●流按数据形式分为:文本流和二进制流。文本流是ASCII码字符序列,而二进制流是字节序列。
●流是一种抽象的概念,负责在数据的产生者和数据的使用者之间建立联系,并管理数据的流动。
3.文本文件和二进制文件到底有什么区别:
根据文件中数据的组织形式的不同,可以把文件分为:文本文件和二进制文件。
● 文本文件:把要存储的数据当成一系列字符组成,把每个字符的 ASCII 码值存入文件中。每个 ASCII 码值占一个字节,每个字节表示一个字符。故文本文件也称作字符文件或 ASCII 文件,是字符序列文件。
● 二进制文件:把数据对应的二进制形式存储到文件中,是字节序列文件。
4.C语言与文件读写
文件库函数stdio.h链接:http://www.cplusplus.com/reference/cstdio/
C语言操作文件分为三步,1)打开文件,2)读写文件,3)关闭文件。
4.1 打开文件函数原型:FILE * fopen ( const char * filename, const char * mode );
函数参数:
filename:文件名,包括路径,如果不显式含有路径,则表示当前路径。例如,"D:\\text.txt"表示D盘根目录下的文件text.txt文件。
mode:文件打开模式,指出对该文件可进行的操作。常见的打开模式如"r"表示只读,"w"表示只写,"rw"表示读写,"a"表示追加写入。
返回值:
打开成功,返回该文件对应的 FILE 类型的指针;打开失败,返回 NULL。故需定义 FILE 类型的指针变量,保存该函数的返回值。可根据该函数的返回值判断文件打开是否成功。
4.2 关闭函数fclose的原型:int fclose(FILE* stream);
函数参数
stream:指向要关闭流对象的指针。
返回值:
如果流被成功关闭,返回0值。失败时,返回EOF(-1)。即使调用失败,作为参数传递的流将不再与文件或其缓冲区关联。
4.3 字符串格式化函数原型int sprintf(char* str, const char* format, ...);
函数参数
str:指向缓冲区指针,缓冲区足够大。
format:格式化字符串,该字符串遵循与printf中的格式相同的规范。
...:附加参数(根据格式化字符串的不同,函数可能需要一系列附加参数,每个参数都包含一个值,用于替换格式字符串中的格式说明符)。
返回值:
如果成功,将返回写入的字符总数。此计数不包括自动附加在字符串末尾的额外空字符。失败返回负数。
注意:Windows OS 上的 C 流在输出时将'\n'转换为'\r\n',输入时将'\r\n'转换为'\n'。
4.4 格式化写入函数int fprintf ( FILE * stream, const char * format, ... );
输出函数
int printf ( const char * format, ... );
示例:
4.5 从流中读取格式化数据函数int fscanf(FILE* stream, const char* format, ... );
函数参数
stream:指向文件对象的指针,该对象标识要从中读取数据的输入流。
format:格式化字符串,该字符串遵循与scanf中的格式相同的规范。
返回值
成功赋值的接收参数的数量(可以为零,在首个接收用参数赋值前匹配失败的情况下),或者若输入在首个接收用参数赋值前发生失败,则为EOF。
#include <stdlib.h>
#include <stdio.h>
int main()
{const int n = 10;int ar[n]={0};int i = 0;FILE *fpr = fopen("Test.txt","r");if(NULL == fpr){printf("open file failter \n");exit(EXIT_FAILURE);}for(i = 0;i<n;++i){fscanf(fpr,"%d ",&ar[i]);}fclose(fpr);fpr = NULL;return 0;
}
4.6 二进制文件的读写
块数据写入函数:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
函数参数:
ptr:这是指向要被写入的元素数组的指针。
size:这是要被写入的每个元素的大小,以字节为单位。
count:这是元素的个数,每个元素的大小为 size 字节。
stream:这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
返回值:
返回成功写入元素的个数,若出现错误或到达文件末尾,则可能小于 count。如果返回的数值与 count 参数值不同,则写入错误将阻止函数完成。在这种情况下,将为流设置错误指示器(ferror)。
块数据读出函数:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
函数参数:
ptr:指向大小至少为(size*count)字节的内存块的指针,从流中读出的数据存储到ptr指向的内存。
size:读取元素的大小,unsigned int。
count:读取取元素的个数。
stream:是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
返回值:
返回成功读取的对象个数,若出现错误或到达文件末尾,则可能小于 count。若 size 或 count 为零,则 fread 返回零且不进行其他动作。fread 不区分文件尾和错误,因此调用者必须用 feof 和 ferror 才能判断发生了什么。
#include <stdlib.h>
#include <stdio.h>
#define ARSIZE 10void Save_Ar(int *ar, int n)
{FILE *fpw = NULL;int len = 0;if(NULL == ar || n < 1) return ;fpw = fopen("Test2.txt", "wb");if(NULL == fpw){printf("open file failure \n");exit(EXIT_FAILURE);}len = fwrite(ar, sizeof(int), n, fpw);fclose(fpw);fpw = NULL;
}void Load_Ar(int *br, int n)
{FILE *fpr = NULL;int len = 0;if(NULL == br || n < 1) return;fpr = fopen("Test2.txt", "rb");if(NULL == fpr){printf("open file failure \n");exit(EXIT_FAILURE);}len = fread(br, sizeof(int), n, fpr);fclose(fpr);fpr = NULL;
}int main()
{int ar[ARSIZE] = {12, 23, 34, 45, 56, 67, 78, 89, 90, 100};int br[ARSIZE] = {0};int n = ARSIZE;Save_Ar(ar, n);Load_Ar(br, n);return 0;
}
5.缓冲和非缓冲文件系统
在ANSI C标准中,使用的是“缓冲文件系统”。所谓缓冲文件系统指系统自动地在内存为每一个正在使用的文件名开辟一个缓冲区,从内存向磁盘输出数据必须先送到内存中的缓冲区,装满后再一起送到磁盘去。反向也是如此。vs2012 stdio.h中的FILE结构体:
int fflush( FILE *stream );
功能:清除读写缓冲区,在需要立即把输出缓冲区的数据进行物理写入时
函数说明
如果指针指向一个输出流或者是一个最近的一次操作不是输入的更新流,输出刷新将会创造任意未写入的数据给将要被写入文件的流和最近的数据被修改流,并且最后的文件状态改变应该被标记为更新的基础文件的时间戳。
对于打开以使用基础文件描述进行读取的流,如果文件尚未处于EOF,并且该文件是能够搜索的文件,则基础打开文件描述的文件偏移量应设置为流的文件位置,并且任何未被从流中读取的 ungetc () 或 ungetwc () 推回到流上的字符都将被丢弃(不再进一步改变文件偏移量)。
如果 stream 是空指针,则 fflush () 将对上面定义了行为的所有流执行此刷新操作。
返回值
如果成功刷新, fflush 返回 0。指定的流没有缓冲区或者只读打开时也返回 0 值。返回 EOF 指出一个错误。
注意:如果 fflush 返回 EOF,数据可能由于写错误已经丢失。当设置一个重要错误处理器时,最安全的是用 setvbuf 函数关闭缓冲或者使用低级 I/O 例程,如 open、close 和 write 来代替流 I/O 函数。
其他用法
fflush(stdin)刷新标准输入缓冲区,把输入缓冲区里的东西丢弃[非标准]
fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上
注意事项
C 和 C++ 的标准里从来没有定义过 fflush(stdin)。也许有人会说:"可是我用 fflush(stdin) 解决了这个问题,你怎么能说是错的呢?" 的确,某些编译器(如 VC6)支持用 fflush(stdin) 来清空输入缓冲,但是并非所有编译器都要支持这个功能(linux 下的 gcc 就不支持),因为标准中根本没有定义 fflush(stdin)。
MSDN 文档里也清楚地写着:fflush on input stream is an extension to the C standard ( fflush 操作输入流是对 C 标准的扩充)。
以下是 C99 对 fflush 函数的定义:int fflush(FILE *stream);
如果 stream 指向输出流或者更新流 (update stream),并且这个更新流最近执行的操作不是输入,那么 fflush 函数将把任何未被写入的数据写入 stream 指向的文件(如标准输出文件 stdout)。否则, fflush 函数的行为是不确定的。 fflush (NULL) 清空所有输出流和上面提到的更新流。如果发生写错误, fflush 函数会给那些流打上错误标记,并且返回 EOF,否则返回 0。
由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用 fflush(stdin) 是不正确的。
void setbuf(FILE *stream, char *buffer);
功能:设置用于流操作的内部缓冲区。其长度至少应该为BUFSIZ个字符。
若buffer非空,则等价于setvbuf(stream,buffer,_IOFBF,BUFSIZ)
若buffer为空,则等价于setvbuf(stream,NULL,_IOBNF,0),这会关闭缓冲。
参数:
stream:要设置缓冲区的文件流
buffer:指向文件流所用的缓冲区的指针。若提供空指针,则关闭缓冲。
返回值无。
注解
若BUFSIZ不是适合的缓冲区大小,则能用setvbuf更改它。
setvbuf亦应当用于检测错误,因为setbuf不指示成功或失败。
此函数仅可在已将stream关联到打开的文件后,但要在任何其他操作(除了对setbuf/setvbuf的失败调用)前使用。
一个常见错误是设置stdin或stdout的缓冲区为生存期在程序终止前结束的数组:
int main(void)
{char buf[BUFSIZ];setbuf(stdin,buf);
} //buf的生存期结束,未定义行为
int main()
{ char buff[256]; int a = 10, b = 20; FILE *pf = fopen("yhp.txt","w"); setbuf(pf,buff); fprintf(pf,"a = %d b = %d \n",a,b); fclose(pf); pf = NULL; return 0;
}
函数原型
int setvbuf( FILE *stream, char *buffer, int mode, size_t size );
功能
以 mode所指示值更改给定文件流 stream的缓冲模式。
缓冲策略
若 buffer为空指针:重设内部缓冲区大小为 size。
若 buffer非空指针:指示流使用始于 buffer而大小为 size的用户提供缓冲区。
必须在 buffer所指向的数组的生存期结束前(用 fclose关闭流)。
成功调用 setvbuf后,数组内容不确定,任何使用它的尝试是未定义行为。
参数
stream:要设置缓冲的文件流。
buffer:指向要使用的流缓冲区的指针;若仅更改大小和模式则为空指针。
mode:使用的缓冲模式(可选值):
_IOFBF:全缓冲(当缓冲区为空时从流读入数据;缓冲区满时向流写入数据)。
_IOLBF:行缓冲(每次从流中读入一行数据或向流中写入一行数据)。
_IONBF:无缓冲(直接从流读入或写入数据;缓冲设置无效)。
size:缓冲区的大小。
返回值
成功时返回 0。失败时返回非零值。
注意
此函数仅可在已将 stream关联到打开的文件后使用(在任何其他操作前,除对 setbuf/setvbuf的失败调用)。实际缓冲区大小可能向下取整(如到 2 的倍数、页面大小的倍数等);并非所有 size字节都用于缓冲。行缓冲限制:多数实现中,行缓冲(_IOLBF)仅对终端输入流可用。
一个常见错误是设置stdin或stdout的缓冲区为生存期在程序终止前结束的数组:
// 错误示例:局部数组作缓冲区导致未定义行为
int main(void) {char buf[BUFSIZ];setvbuf(stdin, buf, _IOFBF, BUFSIZ);
} // buf 的生存期结束,后续操作未定义
期待默认缓冲区大小BUFSIZ为实现上文件I/O的最高效缓冲区大小,但POSIX fstat经常提供更好的估计。