在接触文件操作之前,我们写的程序都是在内存中存储着,一旦程序结束内存中存储的数据都会被擦除,所以如果想要程序结束数据仍然要保留,那就需要将其持久化,就需要用文件操作,将需要保留的数据存储在硬盘中。下次使用时再打开即可。
那么关于文件主要介绍以下几个部分:
什么是文件
磁盘上的文件都算是文件,例如办公三件套,后缀为.ppt的演示文稿
.docx的word文稿
.xlsx图表文件
以及pdf
还有可执行文件.exe 文本文件.txt 以及图片jpg 、png等等等,这些都是文件。打开自己电脑中的硬盘。硬盘中的文件都统称为文件。
但是在程序设计中,我们谈论的文件一般有两种:程序文件、数据文件。
程序文件:
包括源程序文件(后缀为.c)目标文件(window后缀为.obj)可执行程序(windows后缀为.exe)
数据文件:
文件内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
那我们本章主要讨论的就是数据文件。
在之前写代码处理数据的输出输出都会以终端为对象,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出的磁盘上,当需要的时候再从磁盘上吧数据读取到内存中使用,这里处理的就是磁盘上的文件。
文件名
一个文件要有一个唯一的文件标识,以便于用户识别和引用。文件名包含3个部分:文件路径+文件名主+文件后缀
例如:c:\code\test.txt
为方便起见,文件标识常被称为文件名。
文件类型
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换,以ASCII字符的形式存储的文件就是文本文件。一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。如 有整数10000,如果以ASCII码的形式输出到磁盘上,则磁盘占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占有4个字节(VS2013测试)。
如下图:
测试代码:
int main(){
int a=10000;
FILE *pf= fopen("test.txt","wb");
fwrite(&a,4,1,pf);//二进制的形式写到文件中
fclose(pf);
pf=NULL;
return 0;
}
找到项目的目录下,会生成这个.txt 的文件。如果将这个文本以二进式编辑器形式打开,会有2710 这是十六进制,转换成10进制就是10000
文件缓冲区
ANSIC标准采用“缓冲文件系统”处理数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量)。缓冲区的大小根据C编译系统决定。
文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态以及文件当前的位置等),这些信息是保存在一个结构体变量中的。该结构体类型是由系统生命的,取名FILE。
mac 下的CLion编译器中对FILE的定义:
typedef struct __sFILE {
unsigned char *_p; /* current position in (some) buffer */
int _r; /* read space left for getc() */
int _w; /* write space left for putc() */
short _flags; /* flags, below; this FILE is free if 0 */
short _file; /* fileno, if Unix descriptor, else -1 */
struct __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */
int _lbfsize; /* 0 or -_bf._size, for inline putc */
/* operations */
void *_cookie; /* cookie passed to io functions */
int (* _Nullable _close)(void *);
int (* _Nullable _read) (void *, char *, int);
fpos_t (* _Nullable _seek) (void *, fpos_t, int);
int (* _Nullable _write)(void *, const char *, int);
/* separate buffer for long sequences of ungetc() */
struct __sbuf _ub; /* ungetc buffer */
struct __sFILEX *_extra; /* additions to FILE to not break ABI */
int _ur; /* saved _r when _r is counting ungetc data */
/* tricks to meet minimum requirements even when malloc() fails */
unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */
unsigned char _nbuf[1]; /* guarantee a getc() buffer */
/* separate buffer for fgetln() when line crosses buffer boundary */
struct __sbuf _lb; /* buffer for fgetln() */
/* Unix stdio files get aligned to block boundaries on fseek() */
int _blksize; /* stat.st_blksize (may be != _bf._size) */
fpos_t _offset; /* current lseek offset (see WARNING) */
} FILE;
仅供参考,不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异,每打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用更方便。
我们可以自己穿件一个FILE*指针变量:
FILE *pf
定义pf是一个指向FILE类型数据的一个指针变量。可以使pf指向某个文件的文件信息区,通过该文件信息区中的信息就能够访问该文件。也就是说通过文件指针变量能够找到与它关联的文件。
例如
文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC规定使用fopen函数来打开文件,fclose来关闭文件。
打开文件(fopen)
FILE * fopen ( const char * filename, const char * mode );
在cplusplus网站中对其定义如下:
“打开其名称在参数filename中指定的文件,并将其与返回的文件指针在未来操作中可以识别的流相关联。流上允许的操作及其执行方式由mode参数定义。”
模式如下:
文件使用方式 | 含义 | 如果指定文件不存在 | |
“r”只读 | 打开文件进行输入操作。 | 出错 | |
“rb”只读 | 打开一个二进制文件进行操作 | 出错 | |
“w”只写 | 为输出操作创建一个空文件。如果同名文件已经存在,其内容将被丢弃,该文件将被视为新的空文件。 | 创建新文件 | |
“wb”只写 | 为输出操作创建一个空的二进制文件。如果同名二进制文件已经存在,其内容将被丢弃,该文件将被视为新的空二进制文件 | 创建新二进制文件 | |
“a”追加 | 在文件末尾打开文件进行输出。输出操作总是在文件末尾写入数据,并扩展它。重新定位操作(fseek、fsetpos、倒带)被忽略。 | 创建新文件 | |
“ab”追加 | 向一个二进制文件末尾添加数据 | 创建新二进制文件 | |
“r+”读/更新 | 打开一个文件进行更新(用于输入和输出)。 | 出错 | |
“w+”写/更新 | 创建一个空文件并打开它进行更新(用于输入和输出)。如果同名文件已经存在,其内容将被丢弃,该文件将被视为新的空文件。 | 创建新文件 | |
“a+”追加/更新 | 打开一个文件进行更新(用于输入和输出),所有输出操作都在文件末尾写入数据。重新定位操作(fseek、fsetpos、倒带)会影响下一个输入操作,但输出操作会将位置移回文件末尾。 | 创建新文件 |
“使用上面的模式说明符,文件作为文本文件打开。为了将文件作为二进制文件打开,模式字符串中必须包含一个“b”字符。这个额外的“b”字符可以附加在字符串末尾(从而制作以下复合模式:“rb”、“wb”、“ab”、“r+b”、“w+b”、“a+b”),也可以插入混合模式的字母和“+”符号(“rb+”、“wb+”、“ab+”)之间。”
例如打开一个文件:
int main(){
//打开文件 相对路径
//..表示上一级路径
//.表示当前路径
FILE *pf=fopen("test.txt","r"); //模式为只读
if (pf=NULL){
printf("打开失败 ,%s\n", strerror(errno));
}
//打开成功
fclose(pf);
//假设要打开当前路径的上一级路径
FILE *pf2=fopen("../test.txt","r");
if (pf2==NULL){
printf("打开失败 ,%s\n", strerror(errno));
}//打开成功
fclose(pf2);
return 0;
}
关闭文件(fclose)
int fclose ( FILE * stream );
"关闭与流关联的文件并将其取消关联。与流相关的所有内部缓冲区都与之分离并刷新:任何未写入的输出缓冲区的内容被写入,任何未读输入缓冲区的内容都被丢弃。"
文件的顺序读写
字符输入函数(fgetc)
int fgetc ( FILE * stream );
从流中获取字符,返回指定流的内部文件位置指示器当前指向的字符。然后将内部文件位置指示器推进到下一个字符。
如果调用时流位于文件末尾,该函数将返回EOF,并为流(feof)设置文件结束指示器。
如果发生读取错误,该函数返回EOF并设置流的错误指示器(ferror)。
Fgetc和getc是等价的,只是getc可以在一些库中作为宏实现。
字符输出函数(fputc)
int fputc ( int character, FILE * stream );
将字符写到流
向流写入一个字符并推进位置指示器。该字符写在流内部位置指示器指示的位置,然后由一个自动推进。
/* fputc example: alphabet writer */
#include <stdio.h>
int main ()
{
FILE * pFile;
char c;
pFile = fopen ("alphabet.txt","w");
if (pFile!=NULL) {
for (c = 'A' ; c <= 'Z' ; c++)
fputc ( c , pFile );
fclose (pFile);
}
return 0;
}
文本行输入函数(fgets)
char * fgets ( char * str, int num, FILE * stream );
从流中获取字符串,从流中读取字符,并将其作为C字符串存储到str中,直到读取(num-1)字符或到达换行符或文件末尾,以先发生者为准。
换行符使fget停止读取,但它被函数视为有效字符,并包含在复制到str的字符串中。
在字符复制到str后会自动附加终止的空字符。
请注意,fgets与get截然不同:fgets不仅接受流参数,还允许指定str的最大大小,并在字符串中包含任何结尾的换行字符。
代码示例:
/* fgets example */
#include <stdio.h>
int main()
{
FILE * pFile;
char mystring [100];
pFile = fopen ("myfile.txt" , "r");
if (pFile == NULL) perror ("Error opening file");
else {
if ( fgets (mystring , 100 , pFile) != NULL )
puts (mystring);
fclose (pFile);
}
return 0;
}
文本行输出函数(fputs)
int fputs ( const char * str, FILE * stream );
将字符串写入流:
将str指向流的C字符串写入。该函数开始从指定的地址(str)复制,直到它达到终止空字符('\0')。这个终止的空字符不会复制到流中。
请注意,fputs不仅不同于可以指定目标流,而且fputs不会写入其他字符,同时在末尾自动附加一个换行字符。
格式化输入函数(fscanf)
int fscanf ( FILE * stream, const char * format, ... );
从流中读取格式化的数据:
从流中读取数据,并根据参数格式将它们存储到附加参数指向的位置。
附加参数应指向格式字符串中相应格式说明符指定的类型已分配的对象。
这里的format其实跟printf中双引号引住的格式是很类似的。
更详细的format参数内容可参考:cpluscplus-fscnaf
代码样例:
/* fscanf example */
#include <stdio.h>
int main ()
{
char str [80];
float f;
FILE * pFile;
pFile = fopen ("myfile.txt","w+");
fprintf (pFile, "%f %s", 3.1416, "PI");
rewind (pFile);
fscanf (pFile, "%f", &f);
fscanf (pFile, "%s", str);
fclose (pFile);
printf ("I have read: %f and %s \n",f,str);
return 0;
}
格式化输出函数(fprintf)
int fprintf ( FILE * stream, const char * format, ... );
将格式化的数据写入流:
将格式指向流的C字符串写入。如果格式包括格式说明符(以%开头的序列),则格式后面的附加参数将被格式化并插入到生成的字符串中,替换其各自的说明符。
在格式参数之后,该函数期望至少与格式指定的额外参数一样多。
详细的format参数可参考:cplusplus-fprintf
/* fprintf example */
#include <stdio.h>
int main ()
{
FILE * pFile;
int n;
char name [100];
pFile = fopen ("myfile.txt","w");
for (n=0 ; n<3 ; n++)
{
puts ("please, enter a name: ");
gets (name);
fprintf (pFile, "Name %d [%-10.10s]\n",n+1,name);
}
fclose (pFile);
return 0;
}
二进制输入(fread)
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
从流中读取数据块(block):
从流中读取计数元素数组,每个元素的size字节大小,并将其存储在ptr指定的内存块中。
流的位置指示器由读取的总字节数来推进。
如果成功,读取的字节总数为(size*count)。
/* fread example: read an entire file */
#include <stdio.h>
#include <stdlib.h>
int main () {
FILE * pFile;
long lSize;
char * buffer;
size_t result;
pFile = fopen ( "myfile.bin" , "rb" );
if (pFile==NULL) {fputs ("File error",stderr); exit (1);}
// obtain file size:
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
// allocate memory to contain the whole file:
buffer = (char*) malloc (sizeof(char)*lSize);
if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}
// copy the file into the buffer:
result = fread (buffer,1,lSize,pFile);
if (result != lSize) {fputs ("Reading error",stderr); exit (3);}
/* the whole file is now loaded in the memory buffer. */
// terminate
fclose (pFile);
free (buffer);
return 0;
}
二进制输出(fwrite)
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
将数据块(block)写入流:
从ptr指向的内存块到流中当前位置,写入一个计数元素数组,每个元素的size字节大小。
流的位置指示器由写入的总字节数来推进。
在内部,该函数将ptr指向的块解释为无符号字符类型的(size*count)元素数组,并按顺序将它们写入流中,就像每个字节调用fputc一样。
代码示例:
/* fwrite example : write buffer */
#include <stdio.h>
int main ()
{
FILE * pFile;
char buffer[] = { 'x' , 'y' , 'z' };
pFile = fopen ("myfile.bin", "wb");
fwrite (buffer , sizeof(char), sizeof(buffer), pFile);
fclose (pFile);
return 0;
}
对比函数
scanf/fscanf/sscanf
printf/fprintf/sprintf
scanf/printf 是针对标准输入流/标准输出流的 格式化输入/输出语句
fscanf/fprintf是针对所有输入流/所有输出流的 格式化输入/输出语句
sscanf/sprintf是从字符串中读取格式化的数据/把格式化的数据存储到字符串中
对于sscanf和sprintf大家可能不熟悉,其实就是从字符串中读取一些数据,或者向字符串中输入一些数据,给个示例:
struct S{
int n;
float score;
char arr[10];
};
int main(){
struct S s={100,3.14f,"abcd"};
struct S tmp ={0};
char buf[1024]={0};
//把格式化的数据转换成字符串存储到buf
sprintf(buf,"%d %f %s",s.n,s.score,s.arr);
printf("%s\n",buf);
//从buf中读取格式化的数据到tmp
sscanf(buf,"%d %f %s",&(tmp.n),&(tmp.score)),tmp.arr);
printf("%d %f %s\n",tmp.n,tmp.score,tmp.arr);
return 0
}
文件的随机读写
fseek
根据文件指针的位置和偏移量来定位文件指针。
int fseek ( FILE * stream, long int offset, int origin );
重新定位流位置指示器,将与流关联的位置指示器设置为新位置。
对于以二进制模式打开的流,新位置是通过向原点指定的参考位置添加偏移量来定义的。
对于以文本模式打开的流,偏移量应为零或之前对ftell的调用返回的值,原点必须是SEEK_SET。如果函数与这些参数的其他值一起调用,则支持取决于特定的系统和库实现(非便携式)。
成功调用此函数后,流的文件结束内部指示器将被清除,并且之前在此流上调用ungetc的所有效果都会被删除。
在开放更新(读+写)的流上,对fseek的调用允许在阅读和写作之间切换。
代码示例:
/* fseek example */
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile = fopen ( "example.txt" , "wb" );
fputs ( "This is an apple." , pFile );
fseek ( pFile , 9 , SEEK_SET );
fputs ( " sam" , pFile );
fclose ( pFile );
return 0;
}
ftell
返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
在流中获取当前位置,返回流位置指示器的当前值。
对于二进制流,这是文件开头的字节数。
对于文本流,数值可能没有意义,但仍然可用于稍后使用fseek将位置恢复到同一位置(如果有使用ungetc放回的字符仍在等待读取,则该行为为定义)。
代码示例:
/* ftell example : getting size of a file */
#include <stdio.h>
int main ()
{
FILE * pFile;
long size;
pFile = fopen ("myfile.txt","rb");
if (pFile==NULL) perror ("Error opening file");
else
{
fseek (pFile, 0, SEEK_END); // non-portable
size=ftell (pFile);
fclose (pFile);
printf ("Size of myfile.txt: %ld bytes.\n",size);
}
return 0;
}
rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );
将流的位置设置为开头:
将与流关联的位置指示器设置为文件的开头。
成功调用此函数后,与该流相关的文件结束和错误内部指示器将被清除,之前对该流上对ungetc的调用的所有影响都会被删除。
在开放更新(读+写)的流上,倒带调用允许在读写之间切换。
代码示例:
/* rewind example */
#include <stdio.h>
int main ()
{
int n;
FILE * pFile;
char buffer [27];
pFile = fopen ("myfile.txt","w+");
for ( n='A' ; n<='Z' ; n++)
fputc ( n, pFile);
rewind (pFile);
fread (buffer,1,26,pFile);
fclose (pFile);
buffer[26]='\0';
puts (buffer);
return 0;
}
文件结束的判定
int feof ( FILE * stream );
检查文件结束指示器:
确定是否设置了与流相关的文件EOF,如果是,则返回一个不同于零的值。此指标通常由之前试图读取文件末尾或文件末尾的流上的操作设置。
请注意,流的内部位置指示器可能会指向下一个操作的文件结束,但在操作尝试在该点读取之前,文件结束指示器可能不会设置。
返回值:
如果设置了与流关联的文件EOF,则返回非零值。否则,返回零。
feof函数的错误使用
在文件读取过程中,不能用feof函数的返回值直接用来判断文件是否结束!!!而是应用于当文件读取结束的时候,判断是否读取失败结束,还是遇到文件尾结束。
1.文本文件读取是否结束,判断返回值是否为EOF(fgetc),或者NULL(fgets)
例如:
fgetc判断是否为EOF
fgets判断返回值是否为NULL
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数
例如:
fread判断返回值是否小于实际要读的个数。
正确使用
/* feof example: byte counter */
#include <stdio.h>
int main ()
{
FILE * pFile;
int n = 0;
pFile = fopen ("myfile.txt","rb");
if (pFile==NULL) perror ("Error opening file");
else
{
while (fgetc(pFile) != EOF) {
++n;
}
if (feof(pFile)) {
puts ("End-of-File reached.");
printf ("Total number of bytes read: %d\n", n);
}
else puts ("End-of-File was not reached.");
fclose (pFile);
}
return 0;
}
那么以上就是文件操作的全部内容了,本文到此结束。
标签:文件,pFile,int,C语言,char,buffer,FILE,操作 From: https://blog.51cto.com/u_16160587/8790269