C语言之文件
文件的分类
在我们的程序设计中文件分为2种,
一种是程序文件,另一种是数据文件
程序文件
程序文件是指源文件.c,目标文件.obj,可执行文件.exe等文件
数据文件
数据文件是指在程序的运行中,从中读取的数据的文件,或者进行写入数据,输出数据的文件
今天,我们讨论的就是数据文件
在之前,我们都是从终端(键盘)读入数据,在将数据输出到终端(显示器)上面去。
但是,今天我们就是要从数据文件上进行读和写入数据。
文件的名字
文件名必须是唯一的,以便被用户所识别
文件名由3个部分组成:
以下面这个例子来说:
c:\code\test.txt
- 文件路径 c:\code\
- 文件名主干 test
- 文件后缀 .txt
数据文件的分类
对数据文件来说,它还可以再进行分类。按照它的内容来分类,它可以被分为二进制文件和文本文件
二进制文件
数据在内存中本身就是按照2进制来存储文件的,如果不进行类型转换就直接输出到外存,那么就是二进制文件.
如果你在内存种就是以大端进行存储的,那就在二进制文件中也是大端。
如果是小端,那就是小端存储
文本文件
但是,如果把二进制数据转换为ASCII版本的,那就是文本文件了。
数据在内存中是怎么存储的?
如果是字符,一律按照ASCII进行存储。
如果是数字,既可以按照ASCII,也可以按照2进制进行存储。这两种方式在内存中存储时不一样的。
文件指针
对于每一个被打开的文件来说,系统都会在内存中为它开辟一个名为FILE的结构体类型,用来存放文件的各种信息(文件的名字,文件状态及文件当前的位置 )。而这个文件是由系统生成的,包含在stdio.h
内部。
不同的C编译器的FILE结构体的内容大致相同。
下面就是VS2008的FILE结构体的组成
这个也叫做该文件的文件信息区。文件信息区就是FILE结构体。
这时候,我们还缺少一个文件指针。
FILE* pf;//文件指针变量
每当我们打开一个文件的时候,我们就返回一个文件指针。该指针就指向该文件对应的FILE结构体,也就是它的文件信息区。
于是,我们就通过该文件指针就成功找到了我们需要打开的文件的信息。
文件的打开和关闭
在我们对文件读写前我们要打开文件,结束后我们就要关闭文件。
打开文件我们使用fopen
函数来进行打开
fopen函数
FILE* fopen(const char* filename,const char* mode);
//filename是文件名字
//mode是打开的方式
打开方式:
对于文件来说,一共有下面这几种打开方式:
[超详细的打开方式]((237条消息) C语言文件打开模式(r/w/a/r+/w+/a+/rb/wb/ab/rb+/wb+/ab+)浅析_StrayedKing-245176013-CSDN博客_c语言rb+)
‘w’
注意’w’是要打开一个空的文件,如果里面有内容的话,里面的内容会被销毁。
fclose函数
打开文件之后,不能只顾着打开,还要程序员手动的将文件关闭。
函数原型
int fclose(FILE* stream);
//stream是文件指针
具体使用
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen:");
return -1;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
//会返回错误信息:fopen:: No such file or directory
文件的顺序读写
文件,就如同我们的硬盘,软盘,光碟一样。都是存储数据的东西。
我们可以在里面输入数据进行存储,也可以将文件中的数据输出
fputs函数(单个数据写到文件中去)
fputs函数就是将数据输入到文件中去,这个文件就是相当于屏幕。输出到外部去。
函数原型:
int fputs(const char* s,FILE* stream);
实际上,就如同屏幕这样的展示输出的页面,它也是有着对应的FILE*类型的变量的,和文件并没有什么区别。
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//写文件
//这里我们使用fputc
fputc('h', pf);
fputc('e', pf);
fputc('l', pf);
fputc('l', pf);
fputc('o', pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
将hello写入到文件中去。
fgets函数(从文件中读单个数据)
函数原型:
int fgets(FILE* stream);
这个函数是从文件中读取数据,也相当于scanf
从键盘中读入数据一样。
一次只能返回一个字符。
从文件中读取:
#include<stdio.h>int main(){ //打开文件(以读的形式) FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //读文件 int ch=fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); ch = fgetc(pf); printf("%c\n", ch); return 0;}
从键盘上读取:
#include<stdio.h>int main(){ int ch = fgetc(stdin); printf("%c\n", ch); ch = fgetc(stdin); printf("%c\n", ch); ch = fgetc(stdin); printf("%c\n", ch); ch = fgetc(stdin); printf("%c\n", ch); ch = fgetc(stdin); printf("%c\n", ch); return 0;}
fputs(将字符串写入到输出流)
函数原型:
int fputs(const char* str,FILE* stream);
不同于fputc,fputs是将字符串写入到stream中去。
写到文件中去:
#include<stdio.h>int main(){ //打开文件 FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { perror("fopen"); return -1; } //写文件 //这里我们使用fputs fputs("helll", pf); //关闭文件 fclose(pf); pf = NULL; return 0;}
写到屏幕上去:
经过测试,我们发现就只可以使用puts,不可以使用fputs
#include<stdio.h>int main(){ //输出 //这里我们使用puts puts("helll"); return 0;}
fgets(从输入流中读入数据)
一个读取一行的字符
函数原型:
char* fgets(char* string,int number,FILE* stream);
从stream中读取number个字符,并返回到string中去。
具体使用:
函数使用:只会读取number-1个字符,放入到string中去,因为在字符串的最后要补上\0,所以加上\0后就成为了number个字符。
- 如果number小于文件中字符的个数:
只会将前number-1个字符拷贝,最后末尾加上\0
#include<stdio.h>int main(){ //打开文件(以读的形式) FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //读文件 char str[12]; fgets(str, 4, pf); printf("%s", str); return 0;}
- 如果number大于文件中字符的个数
只是拷贝这一行的数据,最后面加上\0
#include<stdio.h>int main(){ //打开文件(以读的形式) FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } //读文件 char str[20]; fgets(str, 20, pf); printf("%s", str); return 0;}
字符指针要注意:
对于字符指针,我们一定要引起注意。我们是用来存储字符的,所以我们采用malloc
一个字符串来进行
char* str2 = (char*)malloc(sizeof(char) * 20); fgets(str2, 20, pf); printf("%s", str2);
fprintf函数(按照格式化输出)
fprintf
的函数原型:
和printf
相比,多了一个pf文件指针。
fprintf
是严格按照格式化来进行输出的。就和printf
那样输出到屏幕上一样,输出到文件中来。
#include<stdio.h>struct s1{ int number; double score;};int main(){ //打开文件 FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { perror("fopen"); return -1; } struct s1 s = { 23,89.0 }; //写文件 //这里我们使用fprintf fprintf(pf,"%d %lf",s.number,s.score); //关闭文件 fclose(pf); pf = NULL; return 0;}
看看效果,就是将printf("%d %lf",s.number,s.score)
打印到文件pf中去了。
同样,它也可以输出到屏幕上。
pf是stdout即可
#include<stdio.h>struct s1{ int number; double score;};int main(){ //打开文件 FILE* pf = fopen("data.txt", "w"); if (pf == NULL) { perror("fopen"); return -1; } struct s1 s = { 23,89.0 }; //写文件 //这里我们使用fprintf fprintf(stdout,"%d %lf",s.number,s.score); //关闭文件 fclose(pf); pf = NULL; return 0;}
fscanf(按照格式化输入)
fprintf
是输出数据到文件中,fscanf
就是从文件中读取数据
#include<stdio.h>struct s1{ int number; double score;};int main(){ //打开文件 FILE* pf = fopen("data.txt", "r"); if (pf == NULL) { perror("fopen"); return -1; } struct s1 s ={ 0 }; //读文件 //这里我们使用fscanf fscanf(pf, "%d %lf", &(s.number),&( s.score)); printf("%d\n", s.number); printf("%lf\n", s.score); //关闭文件 fclose(pf); pf = NULL; return 0;}
fwrite(以二进制的形式输出)
fwrite是以二进制的形式将数据写入到流中去
函数原型:
size_t fwrite(const void* s,size_t size,size_t count,FILE* stream);
s是想要输出的元素的地址,size是该类型所占的字节数,count是想要输入的个数,stream是文件
正常返回:实际输出数据块的个数,即count。
异常返回:返回0值,表示输出结束或发生了错误。
具体使用1:
这样写入文件中都是以二进制的形式输出的。
具体使用2:
fread(以二进制的形式输入)
fread是读入二进制的文本信息
函数原型:
和fwrite没有什么区别
size_t fread(const void* s,size_t size,size_t count,FILE* stream);
s是储存读入元素的地址,size是该类型所占的字节数,count是想要输入的个数,stream是文件
对于这种乱码的二进制文件,也就只有fread可以读出来了。
返回值
正常返回:实际读取数据块的个数,即count。
异常返回:如果文件中剩下的数据块个数少于参数中count指出的个数,或者发生了错误,返回0值。此时可以用feof()和ferror()来判定到底出现了什么情况。
#include<stdio.h>struct s1{ int number; double score; char arr[20];};int main(){ //打开文件 FILE* pf = fopen("data.txt", "rb"); if (pf == NULL) { perror("fopen"); return -1; } struct s1 s ={ 0 }; //读文件 //这里我们使用fread fread(&s,sizeof(s),1,pf);//读出来的数据放入到s中 printf("%d\n", s.number); printf("%lf\n", s.score); printf("%s\n", s.arr); //关闭文件 fclose(pf); pf = NULL; return 0;}
fwrite,fread,fseek函数的联合使用
#include <stdio.h>#include <string.h>struct std_type{ int num; char name[20]; int age; char class;}stud;//定义了一个结构体变量stud//写入信息到文件中int cstufile(){ int i; FILE* fp; if ((fp = fopen("stufile", "wb")) == NULL) { printf("The file can't be opened for write.\n"); return 0; } for (i = 1; i <= 100; i++) { stud.num = i; strcpy(stud.name, "aaaa"); stud.age = 17; stud.class = '8'; fwrite(&stud, sizeof(struct std_type), 1, fp);//将数据写入到stud中去 } fclose(fp); return 1;}//读文件到信息中void main(){ int n; FILE* fp; if (cstufile() == 0) return; if ((fp = fopen("stufile", "rb")) == NULL) { printf("The file can not be opened.\n"); return; } //想要打印奇数学号的同学信息 for (n = 0; n < 100; n += 2) { fseek(fp, n * sizeof(struct std_type), SEEK_SET); //如果只打印奇数的话,那必须使用fseek调整文件指针的位置,因为正常条件下只是打印后面的那一个 fread(&stud, sizeof(struct std_type), 1, fp); printf("%10d%20s%10d%4c\n", stud.num, stud.name, stud.age, stud.class); } fclose(fp);}
sprintf(将格式化数据打印成字符串)
和fprintf不同的是,fprintf是将数据打印到文件中去,但是sprintf是将数据打印到一个字符中去。
#include<stdio.h>struct s1{ int number; double score; char arr[20];};int main(){ struct s1 s = { 1,90.0,"luoxiangyu" }; char str[100] = {0}; sprintf(str, "%d %lf %s", s.number, s.score, s.arr); printf("%s", str); return 0;}
sscanf(从字符串输入数据转换成格式化形式)
和fscanf不同的是,fscanf是从一个文件中读入信息,而sscanf是从一个字符串中读取信息,并且转换成格式化形式
#include<stdio.h>struct s1{ int number; double score; char arr[20];};int main(){ char arr[50] = "1 90.000000 luoxiangyu"; struct s1 s = { 0 }; sscanf(arr, "%d %lf %s", &s.number, &s.score, s.arr); printf("%d\n", s.number); printf("%lf\n", s.score); printf("%s\n", s.arr); return 0;}
相似函数的比较
输入函数 | 作用 |
---|---|
scanf | 从标准输入流(键盘)中输入格式化的数据 |
fscanf | 从各种输入流(键盘/文件)中输入格式化的数据 |
sscanf | 从字符中输入格式化的数据 |
输出函数 | 作用 |
---|---|
printf | 把格式化的数据输出到标准输出流(屏幕)中 |
fprintf | 把格式化的数据输出到各种输出流(屏幕/文件)中 |
sprintf | 把格式化的数据转换成为一个字符串中 |
文件的随机读写
上面的函数都是随机进行读写的。下面我们就来介绍怎么对任意位置进行读写。
我们使用fseek函数找到要读写的位置。
fseek函数
函数介绍:
功能说明
使文件指针fp
移到基于origin的相对位置offset处。
参数说明
fp
:文件指针。
offset:相对base的字节位移量。
origin:文件位置指针移动的基准位置,是计算文件位置指针位移的基点。
origin 有三种情况:
SEEK_CUR 当前位置
SEEK_END 文件末尾位置
SEEK_SET 文件的起始位置
返回值
正常返回:**当前指针位置。**注意返回的新的位置。想要打印还需要fgetc
函数
异常返回:-1,表示定位操作出错。
具体使用:
这个fseek
只是返回的新的指针的位置,还需要配合其他的打印函数才可以将字符打印在屏幕上。
int main(){ //打开文件 FILE* pf = fopen("data.txt", "r"); if (NULL == pf) { perror("fopen"); return -1; } //123456 //读数字3,当前位置为1 fseek(pf, 2, SEEK_SET); char c = fgetc(pf); printf("%c\n", c); //读数字1,当前位置为4 fseek(pf, -3, SEEK_CUR);//1在4的左边差3个位置 c = fgetc(pf);//读取字符1 printf("%c\n", c); //关闭文件 fclose(pf); pf = NULL; return 0;}
ftell(相对起始位置的偏移量)
在上面的fseek
函数中,offset的位置就是字节偏移量。
long int ftell ( FILE * stream );
返回文件指针相对于起始位置的偏移量
具体应用:
#include <stdio.h>int main(){ FILE* pFile; long size; pFile = fopen("data.txt", "rb"); if (pFile == NULL) perror("Error opening file"); else { fseek(pFile, 0, SEEK_END); // 此时函数指针在文件末尾位置 size = ftell(pFile); fclose(pFile); printf("Size of data.txt: %ld bytes.\n", size);//起始和末尾位置的偏移量就是文件所占字节数 } return 0;}
rewind(让文件回到起始位置的函数)
让文件指针的位置回到文件的起始位置
函数原型:
void rewind ( FILE * stream );
具体使用:
#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;}
feof
看到feof
函数,你认为它代表的是什么呢?
可能,很多人都认为这是一个判断文件是否结束的函数,但是其实不是的。
其实,它是来判断文件是以什么方式结束的:
是走到函数末尾结束的,还是遇到错误结束的
如果是走到函数末尾结束的,就返回1
如果是遇到错误结束的,就返回0
和feof
一般搭配使用的就是ferror
对于ferror
来说,
如果是遇到错误结束的,就返回1
如果是走到函数末尾结束的,就返回0
具体使用:
文本文件的例子:
int main(void){ int c; // 注意:int,非char,要求处理EOF FILE* fp = fopen("test.txt", "r"); if (!fp) { perror("File opening failed"); return EXIT_FAILURE; } //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环 { putchar(c); } //判断是什么原因结束的 if (ferror(fp)) puts("I/O error when reading"); else if (feof(fp)) puts("End of file reached successfully"); fclose(fp);}
二进制文件的例子:
enum { SIZE = 5 };int main(void){ double a[SIZE] = { 1.0,2.0,3.0,4.0,5.0 }; double b = 0.0; size_t ret_code = 0; FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式 fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组 fclose(fp); fp = fopen("test.bin", "rb"); // 读 double 的数组 while ((ret_code = fread(&b, sizeof(double), 1, fp)) >= 1) { printf("%lf\n", b); } if (feof(fp)) printf("End of file reached successfully\n"); else if (ferror(fp)) { perror("Error reading test.bin"); } fclose(fp); fp = NULL;}
文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在
使用的文件开辟一块“文件缓冲区”
在将数据从内存存入到磁盘或者从磁盘区数据到内存中去的时候,都会经过这个文件缓冲区的区域。都是将数据填满之后,再进行操作。
在C语言中fflush()函数是专门用来刷新缓存区的。
另外,fclose和exit()也都可以用来刷新缓存区。
所以,一定打开文件后一定要关闭文件,要不然的话写入的文件还在缓存区中就无法保存到文件中了。