前言
计算机中能在内存中存储数据,但是内存的大小为8~16G,这个大小如果被用完将会溢出数据,那么我们要怎么储存更大的数据呢?这就要将数据存储到硬盘中。如果要将数据存储到硬盘中,C语言提供了文件操作函数,可以把数据储存到文件中。这样就解决了内存不够大的问题。
计算机中内存的大小为什么不做得更大一些呢?这是因为内存的运行速度比硬盘的运行速度大概要快上几千倍,访问的速度也是如此,那么内存的造价就会比硬盘贵。当初发明计算机的天才们为了兼顾运行速度、存储大小和造价,于是通过链接内存和硬盘,让计算都放到内存中,硬盘只用来存储数据解决了这样的问题。
那么接下来回到正题,讲一讲文件操作函数的使用吧。
一. 文件
文件储存在硬盘空间中,它分为两种文件:一种是程序文件、另一种是数据文件。
1. 程序文件
程序文件指的就是计算机中,以“.exe”结尾的文件。他们就是启动程序的文件,双击他们就能打开对应文件。
2. 数据文件
数据文件也包括两种:一种是文本文件,里面的数据以人能够直接读懂的文本方式存储,如“a、b、c”等。它们的结尾种类繁多,常见的有“.txt”、“.word”、“.jpg”。这里的图片文件实际上也是数据文件的一种。
另一种文件是二进制文件,它的储存方式就是以二进制大方式存储。计算机能够识别,但并没有转化为人能够直接读懂的语言。这种文件就相当于是加密过的文件,不让读者直接观看。电脑也有提供看二进制文件的软件,能够对二进制文件进行转换阅读。但是如果有其他的加密,那就无法直接阅读了。
3. 文件类型介绍
C语言中提供了结构体“FILE”,“FILE"结构体中包括了文件的各种信息:对象类型,标识流并包含控制流所需的信息,包括指向其缓冲区的指针、位置指示器和所有状态指示器。
FILE对象通常是通过调用fopen或tmpfile创建的,这两个对象都返回指向其中一个对象的指针。所以我们想在C语言中调用文件,就需要了解“FILE”是一种文件结构。
二. 文件操作函数
文件操作函数,顾名思义指的就是操作文件的函数。
1. fopen
fopen函数,用来打开文件并指定打开方式。它的结构如下:
FILE * fopen ( const char * filename, const char * mode );
1) filename
表示文件的名称,这里可以传入存文件名字的数组名,或者用双引号直接输入文件名。
2) mode
表示打开文件的方式,通常的包括
“r”:以读取的方式打开文件,如果文件不存在则报错,返回NULL。
“w”:以写的方式打开文件,如果文件不存在则创建文件。需要注意的是,文件会从头开始写起,而不会接着之后的内容写。如果打开失败返回NULL;
“a”:以写的方式打开文件,如果文件不存在则创建文件。这种打开方式会接着文件原有数据之后继续写。
可以使用“r+w”这样的写法,包括两种打开方式。
“wb”:表示以写的方式打开文件,以二进制的方式写入数据。
“rb”:表示以读的方式打开文件,将二进制数据读以文本方式出来。
3) 返回值FILE*
如果函数成功打开文件,就会返回该文件对应的“FILE*”指针,如果打开失败将会返回NULL。
4)注意事项
可以通过调用fclose或freopen将返回的指针与文件解除关联。所有打开的文件都会在正常程序终止时自动关闭。
5)使用举例
#include <stdio.h>
int main ()
{
FILE* pFile;
pFile = fopen("myfile.txt","w");
if(pFile!=NULL)
{
fputs("fopen example",pFile);
fclose(pFile);
}
return 0;
}
以上代码的意思是,以写的方式打开“myfile.txt”文件,以pFile接收返回值。如果返回值不为空则表示打开文件成功,在文本文件中写入文本“fopen example”,然后关闭文件。运行结果如下:
打开成功,同时写入成功。
2. fclose
fclose用于关闭文件,如果同时操作多个文件,那么就需要关闭用不到的文件,使用这个函数就能够做到。它的结构如下:
int fclose ( FILE * stream );
1)stream
很奇怪,为什么在这里指针的名字是“stream”——流呢?因为传输文件信息在经过研究者抽象过后,形成了流的概念,它表示数据流这样抽象的意义。包括从读取数据到输出数据的过程,向河流一样。
这里传入“FILE*”形的指针,就能够关闭指针所指向的文件。无论是以什么形式打开,都可以使用这个函数关闭。如果传入NULL,那么设么都不会做。
2)返回值int
如果流成功关闭,则返回零值。失败时,返回EOF。
3)使用举例
#include <stdio.h>
int main()
{
FILE * pFile;
pFile = fopen("myfile.txt","wt");
fprintf(pFile, "fclose example");
fclose(pFile);
return 0;
}
以上代码的意思是,以写的方式打开“myfile.txt”文件,以pFile接收返回值。在文本文件中写入文本“fclose example”,然后关闭文件。运行结果如下:
打开成功,同时写入成功,并关闭文件。打开文件的时候最好还是判断一下指针是否为NULL,这样出错了再报个错,不然哪里出问题了都不知道。当关闭文件后,指针也最好置空,防止野指针。
3. fputc
fputc函数,一次输入一个字符到文件中。它的格式如下:
int fputc ( int character, FILE * stream );
1)character
这里的character,表示的是需要输入的字符。但为什么输入的是int形?不是很重要,因为最后会被转化为ASCII码值写入文件。
2)stream
“FILE*"格式,表示指向文件的指针,字符会被写到由”stream“指向的文件中。
3)返回值int
如果输入成功返回“character”的值,如果输入失败返回EOF。
4)注意事项
要用这个函数向指定文件里输入文字,需要先以写的方式打开文件。
5)使用举例
#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;
}
以上代码的意思是,以写的方式打开文件“alphabet.txt”。如果打开成功,就输入字符‘A’~‘Z’,最后关闭文件。代码的执行结果如下:
成功写入了字符‘A’~‘Z’。
4. fgetc
和fputc相反,fgetc用来从文件中读取一个字符。它得结构如下:
int fgetc ( FILE * stream );
1)stream
“FILE*"格式,表示指向文件的指针,会从”stream“指向的文件中读取字符。
2)返回值int
从“stream”中读取完成之后,会将字符以int的格式返回。如果读到文件末尾,或者读取失败发生错误都会返回EOF。
3)注意事项
和fputc相同,要从文件中读取数据,就需要先以读的方式打开文件。如果打开文件的方式不对也不行。
4)使用举例
在使用过fputc的举例基础上,编入以下代码。
#include <stdio.h>
int main()
{
FILE* pFile;
char c;
pFile = fopen("alphabet.txt","r");
if(pFile != NULL)
{
while((c = fgetc(pFile)) != EOF)
{
printf("%c", c);
}
printf("\n");
fclose(pFile);
}
return 0;
}
以上代码的意思是,以读的方式打开文件“alphabet.txt”,从文件中读取字符打印直到文件末尾,最后换行并关闭文件。执行后结果如下:
打印了文件中的‘A'~'Z'。
5. fputs
和fputc相比,c改成了s,函数变成了写入字符串到文件中,fputs的结构如下:
int fputs ( const char * str, FILE * stream );
1)str
“str”表示字符串首元素地址,“const”修饰,因为在写入的时候,不会对字符串内容进行修改。输入的内容直到’\0‘结束。
2)stream
“FILE*"格式,表示指向文件的指针,字符串会被写到由”stream“指向的文件中。
3)返回类型int
如果输入成功会返回一个非负值,如果输入失败,出现错误会返回EOF,并将错误放到错误指示器中。
4)注意事项
和fputc一样,要用这个函数向指定文件里输入文字,需要先以写的方式打开文件。
5)使用举例
#include <stdio.h>
int main ()
{
FILE * pFile;
char sentence[256];
printf("Enter sentence to append: ");
fgets(sentence, 256, stdin);
pFile = fopen("mylog.txt","a");
if(pFile != NULL)
{
fputs(sentence,pFile);
fclose(pFile);
}
return 0;
}
以上代码的意思是,从标准输入中输入字符串写到“sentence”字符串中,最多256个。然后打开文件“mylog.txt”,以继续写的模式。如果打开成功写入“sentence”字符串中的内容,关闭文件。代码执行结果如下:
输入成功,没有问题。
6. fgets
fgets就是从文件中读取字符串的函数了,结构如下:
char * fgets ( char * str, int num, FILE * stream );
1)stream
“FILE*"格式,表示指向文件的指针,会从”stream“指向的文件中读取字符串。
2)num
“num”表示最多读取字符的个数。由于函数的特性当使用fgets读取如果读到函数结束,或者读到'\n’之后就不会再继续读取了,并且第num个数据不会被读取,而是以‘\0’的形式写入数组中。
3)str
“str”表示的就是被存入数据的字符串。
4)返回值char*
如果函数读取成功会返回“str”的起始地址,如果读取失败,则返回NULL并且str中的数据不改变。
5)注意事项
要从文件中读取数据,就需要先以读的方式打开文件。
6)使用举例
#include <stdio.h>
int main()
{
FILE * pFile;
char mystring[100];
pFile = fopen ("mylog.txt" , "r");
if(pFile == NULL)
{
perror("Error opening file");
return 1;
}
if(fgets(mystring, 100, pFile) != NULL )
{
puts(mystring);
}
fclose(pFile);
return 0;
}
借助fputs输入到“mylog.txt”的内容,以读的方式打开“mylog.txt”。如果打开失败则报错返回,反之则从文件中读取一行不超过99个字符的数据,并输出。执行结果如下:
换行符也会被读取打印,调试发现mystring中的内容与读取一行中的内容无误,为什么会多空一行可能与编译器有关。
7. fprintf
fprintf是对所有格式化数据输出的函数。它的结构如下:
int fprintf ( FILE * stream, const char * format, ... );
1) stream
“FILE*"格式,表示指向文件的指针,所有的数据都会被输出到该地址上。
2)format
“format”指的是需要输出的数据,和“printf”标准输出的用法相同。该函数如果将“stream”设置为stdout,那么将会将输出内容打印到标准输出上。
3)返回值int
输出成功后,将返回写入的字符总数。如果发生写入错误,则设置错误指示器(ferror)并返回负数。如果在写入宽字符时发生多字节字符编码错误,则将errno设置为EILSEQ,并返回一个负数。
4)注意事项
将内容输出到文件需要以写的方式先打开文件。
5)使用举例
#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;
}
以上代码的意思是,打开文件“myfile.txt”,总共三次输入字符串,格式"Name %d [%-10.10s]\n",转义字符分别为 n + 1, name。最后关闭文件。执行结果如下:
8. fscanf
fscanf适用于所有格式类型输入。它的格式如下:
int fscanf ( FILE * stream, const char * format, ... );
1)stream
“FILE*"格式,表示指向文件的指针,所有的数据都来自于这个文件。
2)format
和fprintf正好相反,这里的format指的是读取的格式,使用方法和scanf相同。读取数据最重要的就是需要增加“&”符。
3)返回值int
成功时,函数返回成功填充的参数列表的项数。此计数可能与预期的项数匹配,也可能由于匹配败、读取错误或到达文件末尾而减少(甚至为零)。
如果在读取时发生读取错误或到达文件末尾,则会设置正确的指示器(feof或ferror)。并且,如在成功读取任何数据之前发生任何一种情况,则返回EOF。
如果在解释宽字符时发生编码错误,函数会将errno设置为EILSEQ。
4)注意事项
如果要将数据从文件中读出来需要将,文件以写的方式先打开。
5)使用举例
#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;
}
以上代码的意思是,以写和读的方式打开文件“myfile.txt”先输入3.1416和字符创“PI”,将文件中的指针返回到起始位置,分别读取数据浮点数f和字符串str,关闭文件。最后调用标准输出函数输出"I have read: %f and %s \n",字符串中的转义字符分别指代f和str。运行结果如下:
9. sprintf
sprintf的作用是输出内容格式化到字符串,和fprintf、printf是兄弟关系,格式如下:
int sprintf ( char * str, const char * format, ... );
1)str
“str”指代字符数组地址,后面的内容会被格式化写入这里。
2)format
“format”指的是需要输出的数据,和“printf”标准输出的用法相同。
3)返回值int
成功后,将返回写入的字符总数。
失败时,将返回一个负数。
4)使用举例
#include <stdio.h>
int main ()
{
char buffer [50];
int n, a = 5, b = 3;
n = sprintf (buffer, "%d plus %d is %d", a, b, a+b);
printf ("[%s] is a string %d chars long\n",buffer,n);
return 0;
}
以上代码的意思是,将"%d plus %d is %d"格式化写入字符数组buffer中,指代内容分别为a、b、a + b。然后打印"[%s] is a string %d chars long\n",转义内容分别为buffer和n。最后的结果为。
其中转义字符只算一个字符,所以"%d plus %d is %d"输出之后值为13.
10. sscanf
和sprintf的用法相反,用于将字符串中数据拿出来。和scanf、fscanf是兄弟关系,它的格式如下:
int sscanf ( const char * s, const char * format, ...);
1)str
str”指代字符数组地址,数据从这个数组里读取。
2)format
和sprintf正好相反,这里的format指的是读取的格式,使用方法和scanf相同。读取数据最重要的就是需要增加“&”符。
3)返回值int
成功后,函数将返回参数列表中成功填充的项数。此计数可以与预期的项目数相匹配,或者在匹配失败的情况下小于(甚至为零)。
如果在成功解释任何数据之前输入失败,则返回EOF。
4)使用举例
#include <stdio.h>
int main ()
{
char sentence []="Rudolph is 12 years old";
char str [20];
int i;
sscanf (sentence,"%s %*s %d",str,&i);
printf ("%s -> %d\n",str,i);
return 0;
}
以上代码的意思是,从字符串“sentence”读取数据,先读一个字符串存到str中,跳过一个字符串,读取一个整数存到i里面。
11. fwrite
fwrite的作用是以二进制格式写入数据到文件中,它的格式如下:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
1)ptr
ptr表示数据读取的位置。
2)size
size表示类型的大小。
3)count
count表示“size”类型的个数。
4)stream
“stream”表示输出到的文件所在地址。
5)返回值size_t
返回成功写入的元素总数。
如果此数字与计数参数不同,则写入错误会阻止函数完成。在这种情况下,将为流设置误差指示器(ferror)。
如果大小或计数为零,则函数返回零,并且错误指示器保持不变。
6)使用举例
#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;
}
以上代码的意思是,以二进制写的方式打开“mtfile.bin”,从buffer中读取3个字符大小的数据写入“pFile"中,最后关闭文件。运行结果如下:
12. fread
fread用于读取文件中二进制数据,结构与fwrite类似,结构如下:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
1)ptr
ptr表示数据输出的位置。
2)size
size表示类型的大小。
3)count
count表示“size”类型的个数。
4)stream
“stream”表示内容读取的文件地址。
5)返回值size_t
返回成功读取的元素总数。
如果此数字与count参数不同,则表示发生读取错误,或者读取时已到达文件末尾。在这两种情况下,都设置了适当的指示器,可以分别用ferror和feof进行检查。
如果大小或计数为零,则函数返回零,并且ptr所指向的流状态和内容保持不变。
6)使用举例
在执行完fwrite举例代码的基础上编入以下代码。
#include <stdio.h>
int main ()
{
FILE * pFile;
char buffer[] = "xxxxxxxxxxx";
pFile = fopen ("myfile.bin", "rb");
fread (buffer , sizeof(char), 4, pFile);
fclose (pFile);
printf("%s\n", buffer);
return 0;
}
以上代码的意思是,以二进制读的方式打开“myfile。bin”,然后从中读取4个字符到buffer,关闭文件,打印buffer中的内容。结果如下:
从结果可知,读取数据不会再末尾增加’\0'和fgets作用并不相同。数据不足也可以读取,直到读取完毕就可以。
13. fseek
fseek用于调整文件中指针的位置,它的结构如下:
int fseek ( FILE * stream, long int offset, int origin );
1)stream
“stream” 表示需要调整指针位置的文件。
2)offset
“offset”表示需要偏移的量,正数向前负数向后。
3)origin
“origin”指的是指针开始调整时的位置。相关指令如下:
SEEK_SET,表示从文件开头开始偏移指针。
SEEK_CUR,表示从当前指针的位置开始偏移指针。
SEEK_END,表示从文件末尾开始偏移指针。
4)返回值int
如果成功,函数将返回零。
否则,它将返回非零值。
如果发生读取或写入错误,则设置错误指示器(ferror)。
5)使用举例
#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;
}
以上代码的意思是,以二进制写的方式打开“example.txt”。写入字符串"This is an apple.",到文件中。调整指针位置从文件开始调整指针位置到第9位,输入字符串“ sam”覆盖掉“n ap”,然后关闭文件,运行结果如下:
14. ftell
ftell函数用来检测函数指针相对于起始位置的偏移量,函数结构如下:
long int ftell ( FILE * stream );
1)stream
“stream”测定指针位置的文件。
2)返回值long int
成功后,将返回位置指示器的当前值。
失败时,返回-1L,errno设置为系统特定的正值。
3)使用举例
使用代码时预先建立“myfile.txt”文件。
#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;
}
以上代码的意思是,以读的方式打开“myfile.txt”文件,将指针至于末尾,用size接收指针的偏移量,关闭文件然后打印size。结果如下:
确实是11个字符的偏移量,无误。
15. rewind
“rewind”可以让文件的指针回到初始位置,它的结构如下:
void rewind ( FILE * stream );
1)stream
“stream”需要返回指针位置的文件。
2)返回值void
表示没有返回值。
3)使用举例
#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;
}
以上代码的意思是,以读写的方式打开文件“myfile.txt”,输入26个大写字母‘A'~'Z'。随后返回指针到初始位置,从“myfile.txt”读取26个字母到buffer,关闭文件,最后一个字符赋值为’\0‘,打印“buffer”然后结束。
三. 文件设置和缓冲区
1. 文件的标记值
打开一个流的时候这个流上有两个标记值:
1)到文件末尾。可以用函数feof测定,到了就会返回非0的值。
2)发生错误。可以用函数ferror测定,出错就会返回非0的值。
2. 文件缓冲区
因为将信息传递给文件的时候,计算机不能执行其他动作,所以如果一次只输入1个字符到文件然后不停如此的话将会非常占用时间。为了提高运行效率,计算机会将要输入输出的数据先存到对应的输出缓存区和输入缓存区。这个时候计算机会去计算其他的东西,如果使用函数fflush会刷新对应文件的缓冲区,储存在输出缓冲区的内容会被输入到文件,当然超过缓冲区总数或者用fclose关闭文件的时候都会刷新文件缓冲区。
文件缓冲区的结构大致如下:
作者结语
又是一篇很长的文章,不得不说计算机里的知识是真的多。敲得好累。而且函数fflush、foef、rferror并没有仔细介绍。不过使用起来也不难,可以去官网或者cplusplus查找相关释义。关于feof和ferror的话也有容易出错的地方,因为他们判断文件出错货到结尾和fgetc正好相反。
下一篇文章会去讲编译和链接,下午就开始写,现在先休息会。累瘫。