一、文件相关概念
1、文件概念
指存储在外部介质上(如磁盘磁带)的具有名字的一组相关数据的集合。
(1)这里的文件是数据文件! 它是用来存放程序要处理的原始数据和程序运行的结果数据的。
(2)操作系统是以文件为单位对数据进行管理的。
(3)使用文件可以带来的好处:
①可以实现程序和数据分离。
②实现数据共享
③实现数据的长久保存。
(4)一个数据在磁盘上存储
①字符型数据(char,字符串)无论在文本文件中,还是二进制文件中,一律以文本形式存储!
②数值型数据(int,float,double)在二进制文件中是用二进制形式存储;而在文本文件中是用文本形式存储(这时需要进行格式转换,速度慢)。
③数组和链表及结构体数据存储在内存中,文件数据存储在磁盘中。
2、文件的分类
(1)从操作系统的角度看,每一个与主机相连的输入输出设备看作是一个文件。
例:输入文件:终端键盘
输出文件:显示屏和打印机
(2)从用户观点。
例:
特殊文件(标准输入输出文件或标准设备文件)
普通文件(磁盘文件)
(3)按数据的组织形式
例:
ASCII文件(文本文件):每一个字节放一个ASCII代码,换句话即每一个字符用其对应的ASCII保存;
二进制文件:把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。
例:
整数10000在内存中的存储形式以及分别按ASCII码形式和二进制形式输出如图所示:
如:-1000用ASCII文件存盘形式45 49 48 48 48五个节表示。
00101101 00110001 00110000 00110000 00110000
如-1000在内存中用两个字节表示(补码表示),则,在二进制文件中保存如下:253 24
11 11 11 00 00 01 10 00
3、文本文件和二进制文件的区别
(1)文本文件
ASCII文件便于对字符进行逐个处理,也便于输出字符。但一般占存储空间较多,而且要花费转换时间;
文本文件占据空间大,可读可写,但读写需转换;
使用ASCII文件保存数据的好处是:可以使用文字编辑软件如”记事本”直接查看其内容。但这种存储方式相对来说比较浪费存储空间。程序对其进行读写操作也慢,因为需要进行转换。
(2)二进制文件
二进制文件可以节省外存空间和转换时间,但一个字节并不对应一个字符,不能直接输出字符形式;
一般中间结果数据需要暂时保存在外存上,以后又需要输入内存的,常用二进制文件保存;
二进制文件所占空间小,读写效率高,虽然二进制文件我们不能直接查看其内容,但C程序能“读懂”其内容。而且使用二进制文件可以节省大量存储空间和减少处理时间。
4、文件系统
字节流或二进制流:在C语言中,保存到磁盘上的文件或从磁盘文件读取数据都被看作是对一连串的字节流或二进制流的操作,而不考虑是否有“记录”的结构。所以称为流式文件。
对文件的处理方式:缓冲文件系统和非缓冲文件系统。
(1)缓冲文件系统(ANSI C采用):系统自动在内存中为每个正在使用的文件名开辟一个缓冲区,读写文件的数据首先送到内存缓冲区,缓冲区满了再由系统后台送到内存或磁盘。缓冲文件系统读写效率比较高,后台“批量”操作。用缓冲文件系统进行的输入输出又称为高级磁盘输入输出。
(2)非缓冲文件系统(unix采用):系统不自动开辟缓冲区,而是由程序员编程时考虑缓冲问题。这样程序员需要处理更多的细节。由程序为每个文件设定缓冲区。用非缓冲文件系统进行的输入输出又称为低级输入输出系统。
二、文件操作函数
1、在C语言中,所有的文件操作都通过库函数来实现的。
文件读写(文件操作)的基本步骤是:定义文件类型指针变量,打开文件,读写文件,关闭文件。
2、C文件类型指针
文件函数:
文件的读写函数,ANSI C提供四种读写文件的方法,通过四组函数进行:
①读写单个字符: fgetc fputc 文本文件
②读写一个字符串: fgets fputs 文本文件
③格式化读写: fscanf fprintf 文本文件 (以文本格式读写数值型数据)
④读写一个“记录”(成“块”读写):fread fwrite 二进制文件
3、文件类型(FILE)
缓冲文件系统中每个被使用的文件都由系统自动在内存中开辟一个区,用来存放该文件的有关信息(如文件名,状态,当前位置等)。这些信息保存在一个结构体类型FILE中。
FILE在 stdio.h文件中有如下定义:
typedef struct
{
int _fd;//文件号
int _cleft;//缓冲区中剩下的字符
int _mode;//文件操作模式
char *_nexttc;//下一个字符位置
char *_buff;//文件缓冲区位置
}FILE;// FILE是用typedef定义的一结构体类型,不是变量
4、定义指向文件类型的指针变量
FILE *指针变量名;
如:FILE *fp;
含义:fp是一个指向FILE类型的指针变量。
三、文件输入输出函数
1、文件的打开函数(fopen函数)
(1)调用方式:
FILE *fp ;
FILE* fopen(const char* filename,const char* mode);
//filename-文件路径/文件名 mode-打开模式/文件使用方式
功能:以指定的方式打开指定的文件,若操作成功,则返回一个指向该文件的指针,若打开文件时出现错误,则返回空指针NULL。
打开的文件名。必须是用双引号引起的字符串常量。
如∶
FILE *fp; // FILE是系统定义的结构体类型
fp=fopen("stu.dat","r");//文件打开后,fp即代表该文件
通常需要判断该文件是否被成功打开,打开成功时,fopen( )返回文件信息区的起始地址,失败则返回0(NULL)。
if ((fp=fopen(…))==NULL)
{
printf(“打开失败”);
exit(0); /*使程序运行正常终止*/
}
例1:
#include <stdio.h>
#include <stdlib.h>
FILE *file_open(char *filename)//打开文件函数定义
{
FILE *fp=NULL;//文件指针
fp=fopen(filename,"rb+");//rb+为文件存在则读,不存在则不读。
if(!fp)//if文件不存在
{
//w+为没有文件先创建,有文件先删除再创建同名文件
fp=fopen(filename,"wb+");
}
return fp;
}
int main()
{
FILE *fp=NULL;//文件指针
fp=file_open("data.file");//引用打开函数
fflush(fp);//刷新缓冲区
fclose(fp);//关闭文件
}
输出:
(2)文件使用方式
说明:
①基本方式是 r(只读)、w(只写)、a(追加) --文件尾增加
②加b: 对二进制文件操作。
③加+ : 为可读可写。尽管存在这种可同时读写的操作方式,但仍然强烈建议不要使用这种方式,容易出错!可以先以只写方式打开文件进行写操作,关闭该文件,再以只读方式打开文件,进行读操作。
④r和a两种方式要求该文件必须已存在,否则出错(返回NULL)
⑤w为新建文件(如原来存在同名文件,其内容也会被覆盖)
"r+”和"w+”和 "a+”三者区别:
共同点:都为可读可写。
w+:写读,没有文件先创建,有文件先删除再创建同名文件;
r+: 读写,文件存在则读,从头开始,不存在则不读;
a+: 读写,文件不存在就创建,文件存在就追加。
2、文件的关闭函数(fclose函数)
调用方式:fclose(文件指针);
文件指针也是fopen的返回值,成功返回0,否则返回EOF。
功能:关闭由文件指针指定的文件,把缓冲区中的数据(未装满缓冲区的数据)输出到磁盘上,释放文件指针。
例如:
FILE *fp;
fp=fopen(“output.dat”,“r+”);
…… //进行读写操作
fclose(fp);
注意:fopen函数和fclose函数总是成对出现的,无fclose函数时会导致部分数据丢失。
fopen(文件名,使用文件方式) 打开文件和fclose(文件指针)关闭文件成对存在。
3、读写单个字节(fgetc-fputc-文本文件)
fputc函数
调用形式:fputc(字符常量或变量,文件指针);//成功返回c,失败返回EOF。
功能:把指定字符存入文件指针所指的文件中。同时把读写位置指针向后移动1个位置。指向下一个写入位置 。
例如:fputc(ch,fp); (其中ch为字符变量,fp为文件指针)
含义:将ch的值输出到fp所指向的文件中去。
fgetc函数
调用形式:字符变量=fgetc(文件指针);
//成功返回读到的字符,失败或者遇到文件尾返回EOF。
功能:从文件指针所指文件中读一个字节赋给指定的字符变量。当遇到文件结束符时,返回一个文件结束标志EOF(-1)。EOF是在stdio.h文件中定义的符号常量,其值为-1。通常可用while(ch!=EOF) 或while(!feof(fp))控制读取循环。对于文本文件使用EOF判断文件结尾是合适的,因为正常数据的ASCII码值不可能是负数;但对二进制文件就不行了,可以使用C标准库函数 feof()。当到达文件结尾时,该函数返回非0值,反之返回0值。所以无论是文本文件还是二进制文件,都可以使用feof()函数判断文件是否到达文件尾。
4、字符串读写(fgets-fputs-文本文件)
fputs函数
调用形式:fputs(字符串,文件指针);
功能:向指定文件输出一个字符串(’\0’不会写入)。
例如:fputs(str,fp); (其中str是字符数组名或指针名或字符串常量)
含义:向fp 所指向的文件中输出str中的字符串。
fgets函数
调用形式:fgets(字符串,字符串长度,文件指针);
例如:fgets(str,n,fp);
其中str是字符数组名或指针名,含义:从fp所指文件中读入n-1个字节数据给字符数组str(末尾会自动添加’\0’。如中间遇到’\n’或EOF即结束本次读入,但’\n’也会作为正常字符读入!所以输出数组时会自动换行,无需再加’\n’)。
5、格式化读写(fscanf-fprintf-文本文件)
调用形式:
fprintf(文件指针,格式字符串,输出表列);
fscanf(文件指针,格式字符串,输入表列);
功能:以格式字符串指定的格式读写文本文件,适用于一般实体,如数值型变量。注意:是以文本格式读写数据,所以对数值型数据要花费从二进制转换成文本的时间。
fscanf(fp,"%d,%f",&a,&b);
将磁盘文件中的数据送给变量a,下一个送给变量b
fprintf(fp,"%d,%6.2f",a,b);
将变量a和b按%d和%f格式输出到fp所指文件上
例:
#include <stdio.h>
#include <string.h>
#define FILENAME "test.txt"
int main()
{
FILE *fp;//文件指针
unsigned int i;//unsigned 无符号整型
int iArr[100];
int number;
int count = 0;
fp = fopen(FILENAME,"w");//打开文件
if(!fp)
{
printf("打开文件失败!\n");
return -1;
}
//生成一个1-100的整型数组并格式化方式保存到文件中
for(i=0;i<100;++i)
iArr[i] = i + 1;
for(i=0; i<100; ++i)
fprintf(fp,"%d ",iArr[i]);//数据写入文件
fclose(fp);//关闭文件
fp = fopen(FILENAME,"r");//打开文件
if(!fp)
{
printf("打开文件失败!\n");
return -1;
}
//以格式化的方式把文件当中的数字打印出来
while(1)
{
fscanf(fp,"%d ",&number);//数据从文件输出
printf("%4d",number);//显示文件中的数据
if(feof(fp))//判断是否到达文件末尾
break;
if(++count%10 == 0)//一行10个数字
printf("\n");
}
fclose(fp);
printf("\n");
return 0;
}
输出:
6、读写一个“记录”(成“块”读写效率高)(fread-fwrite-二进制文件)
调用形式:
读入 fread(p,size,n,fp);
写入 fwrite(p,size,n,fp);
//以二进制方式直接读写二进制文件
适用于结构体、数组等复杂实体。其中: P是指向该实体的指针,size是单个实体的字节数 ,n是指操作多少次, fp是指向文 件的指针。操作如成功,返回n的值!
对fp所指文件读写p所指向的size*n个字节数据
fread函数:
功能:对fp所指的文件读写p所指向的size*n个字节数据。
例如:
fread(&f[i],4,1,fp); (读f[i]元素,常用在循环中,i是循环变量)
fread(f,4,n,fp); (读具有n个元素的数组f)
fread(f,sizeof(f),1,fp); (读整个数组f)
例1:把数组a写入文件fa;再从fa读入数组b。
#include <stdio.h>
void main()
{
FILE* fp;
int a[10]={1,5,6,78,21,34,67,87,23},b[10], i ;
fp=fopen("tmp","wb");
fwrite(a,sizeof(a),1,fp);//fwrite(a,sizeof(int),10,fp);//-亦可
fclose(fp);
fp=fopen("tmp","rb");
fread(b,sizeof(a),1,fp);
for(i=0;i<10;i++)
{
printf("%6d",b[i]);
}
printf("\n");
fclose(fp);
}
fwrite()函数:
功能说明:fwrite是无格式写函数,用于向文件写入整块的数据。最有价值的一个应用就是读写用户定义的数据类型,尤其是结构体等。
函数原型:size_t fwrite(const void *buffer,size_t bytes, sizeo_t n,FILE *fp);
返回值:成功时返回写入的单元数,否则返回0
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE *fp=NULL;//文件指针
char ch[10]={0};
char num[10]={'a','b','c'};
fp=fopen("data.file","wb+");//创建和写入文件
fwrite(num,3,3,fp);//写入字符数组
fflush(fp);//刷新缓冲区
fclose(fp);//关闭文件
fp=fopen("data.file","rb+");//只读方式打开有内容的文件
fread(ch,8,1,fp);//读文件内容
printf("ch=%s\n",ch);//打印字符数组值
memset(ch,0,sizeof(ch));//清空字符数组值
fp=fopen("data.file","wb+");//创建和写入文件
//第一次打开一个文件时,文件不存在,用r+这种模式open会返回失败
//第二次打开这个文件时,用w+会重新创建一个新的文件,原来文件里的数据会丢失。
fflush(fp);//刷新缓冲区
fclose(fp);//关闭文件
fp=fopen("data.file","rb+");//以只读方式打开有内容的文件
fread(ch,8,1,fp);//读文件内容
printf("ch=%s\n",ch);//打印字符数组值
fflush(fp);//刷新缓冲区关闭文件
fclose(fp);//
return 0;
}
输出:
四、文件定位函数
1、概念
文件一旦打开,就有一个指针,指向当前正在读写的位置——位置指针。每读/写一个字节,该指针自动向后移动一个字节位置。
文件的操作中,可以直接移动该指针到给定的位置——文件定位。
常见函数:rewind()、fseek()、ftell()
(1) rewind函数
调用形式:rewind(文件指针);
功能:使位置指针重新返回文件的开头。
(2) fseek函数
调用形式:fseek(文件指针,位移量,起始点) :文件定位,改变文件位置指针的位置
起始点为:1,2,3或SEEK_SET(文件开始),SEEK_CUR(文件当前位置)或SEEK_END(文件末尾)。
位移量:指以起始点为基点,向前(-)或向后移动的字节数。ANSI C标准规定在数字末尾加一个字母L,表示long型。
关于fseek(fp,n,i)
fp 文件指针
n 位移量(以起始点为基点,向后移动的字节数,负数为倒移的字节数)
i 起始点:
功能:把文件的读写位置指针移到指定的位置。
例如:fseek(fp,128L,SEEK_SET); //将位置指针移到离文件头128个字节处。
例如:fseek(fp,-10L,2); //将位置指针从文件末尾处向后退10个字节。
注意:fseek函数一般用于二进制文件。
(3) ftell函数(不移动指针)- 返回文件位置指针的当前值
调用形式:ftell(文件指针);
功能:函数ftell(fp)用于获得位置指针在文件中的当前位置(用相对于文件开头的位移量来表示)。
五、文件函数应用
1、文件存取数组内容
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
FILE *fp=NULL;//文件指针
char str_input[10]={0};//存储从文件中获取的内容
char str_into[10]={'a','b','c'};//存储字符,用于输入到文件
char *string=NULL;
int num=0;//用于文件名编序号
char buf[10];
char buffer[10];
printf("请输入整数:");
scanf("%s",buf);
string=buf;//字符指针指向字符数组值
//字符转为整数
num=atoi(string);
num+=1;
printf("num=%d\n",num);
//将整型数据转换为字符串
sprintf(buffer,"bill%d.file",num);
printf("buffer=%s\n",buffer);//输出文件名
fp=fopen(buffer,"wb+");//创建了一个同名文件,并清空内容
fwrite(str_into,3,3,fp);//数组内容写入文件
fflush(fp);//刷新缓冲区
fclose(fp);//关闭文件
fp=fopen(buffer,"rb+");//以只读方式打开有内容的文件
fread(str_input,8,1,fp);//读文件内容
printf("str_input=%s\n",str_input);//打印字符数组值
return 0;
}
//输出:
//请输入整数:2
//num=3
//buffer=bill3.file
//str_input=abc
//Press any key to continue
2、文件存取一条结构体数据
#include <stdio.h>
#include <stdlib.h>
typedef struct MyStaff//数据域类型
{
char acNo[5];//员工工号
char acName[10];// 员工姓名
char acPwd[8];//登录密码
char iRole;
}STAFF_T;
int main()
{
FILE *fp=NULL;//文件指针
STAFF_T staff2 = {0};
STAFF_T staff1 = { "1000","admin","123456",'1'};
fp=fopen("data.file","wb+");//创建了一个同名文件,并清空内容
fwrite(&staff1,sizeof(STAFF_T),1,fp);//写内容到文件
fflush(fp);//刷新缓冲区
fclose(fp);//关闭文件
fp=fopen("data.file","rb+");//只读方式打开有内容的文件
fread(&staff2,sizeof(STAFF_T),1,fp);//读文件内容
printf("%s,%s,%s,%c\n",staff2.acName,staff2.acPwd,staff2.acNo,staff2.iRole); //打印文件内容
return 0;
}
//输出:
//admin,123456,1000,1
3、文件存取多条结构体数据以及对文件数据进行增删改查
例:
#include <stdio.h>
#include <stdlib.h>
typedef struct MyStaff//数据域类型
{
char acNo[5];//员工工号
char acName[10];// 员工姓名
char acPwd[8];//登录密码
char iRole;
}STAFF_T;
FILE *file_open(char *filename)//打开文件
{
FILE *fp=NULL;//文件指针
fp=fopen(filename,"rb+");//只读方式打开有内容的文件
if(!fp)//if文件为空
{
fp=fopen(filename,"wb+");//写内容到文件
}
return fp;//返回文件指针
}
void file_add(FILE *fp,void *data,int size)//增加文件内容
{
fseek(fp,0,SEEK_END);
fwrite(data,size,1,fp);
fflush(fp);
}
void file_update(FILE *fp,void *data,int size,int index)//修改文件内容
{
fseek(fp,(index-1)*size,SEEK_SET);//光标定位
fwrite(data,size,1,fp);
fflush(fp);
}
int main()
{
FILE *fp=NULL;//文件指针
STAFF_T staff = {"1000","admin0","11110",'a'};
STAFF_T staff1 = {"1001","admin1","11111",'b'};
STAFF_T staff2 = {"1002","admin2","11112",'c'};
STAFF_T staff3 = {"1003","admin3","11113",'d'};
STAFF_T staff4 = {"1004","admin4","11114",'e'};
STAFF_T staff5 = {0};
int n=2;
fp=file_open("data.file");//打开文件data.file
fwrite(&staff1,sizeof(STAFF_T),1,fp);//写入数据1
//文件内容1001 admin1 11111 b
file_add(fp,&staff2,sizeof(STAFF_T));//添加数据4
file_add(fp,&staff3,sizeof(STAFF_T));//添加数据2
//文件内容1001 admin1 11111 b1002 admin2 11112 c1003 admin3 11113 d
fflush(fp);
fclose(fp);
fp=file_open("data.file");//打开文件data.file
file_update(fp,&staff,sizeof(STAFF_T),1);//修改函数
//文件内容1000 admin0 11110 a1002 admin2 11112 c1003 admin3 11113 d
fwrite(&staff4,sizeof(STAFF_T),1,fp);//写文件内容
//文件内容1000 admin0 11110 a1004 admin4 11114 e1003 admin3 11113 d
for(n=1;(n>0 && n<4);n++)
{
fseek(fp,(n-1)*sizeof(STAFF_T),0);
fread(&staff5,2*sizeof(STAFF_T),1,fp);//读文件内容
printf("%s,%s,%s,%c\n",staff5.acName,staff5.acPwd,staff5.acNo,staff5.iRole);
}
fflush(fp);//刷新缓冲区
fclose(fp);//关闭文件
return 0;
}
//第1次输出
//admin0,11110,1000,a
//admin2,11112,1002,c
//admin3,11113,1003,d
//第2次输出
//admin0,11110,1000,a
//admin4,11114,1004,e
//admin3,11113,1003,d
编辑 2020-09-13 14:14 首次编辑
修改 2021-08-10 22:30 内容结构优化
注:本文旨于作为自己的学习笔记,不作他用。