“文件”是指一组相关数据的有序集合。普通文件存储在磁盘等外部设备中,在需要的时候由程序将数据读入内存。普通文件都有一个文件名,以方便程序打开和读写等操作。在操作系统中,把外部设备也看作是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和写。比如显示器、打印机、键盘等。
从存储的格式来看,文件分为文本文件与二进制文件。
常见的文本文件:.c文件,.txt文件等。
常见的二进制文件:exe,dll,jpg,doc等。
文本文件基于字符编码,以固定长度的二进制序列进行编码和解码(常见的有ASCII文编码和Unicode编码),而二进制文件是基于值编码的文件,二进制文件编码是变长的,具体的长度由具体的格式决定,比如EXE或者BMP二进制文件。文本文件使用notepad就可以读取,而二进制文件需要专门的工具,比如图片就需要专门的读图软件才能打开,如果用notepad打开,就会看见不少乱码。
文本“
值5678的存储形式为:值的二进制: 00010110 00101110 (两个字节)
“用文本方式读写的文件一定是文本文件,用二进制读写的文件一定是二进制文件”这类观点是错误的。C的文本方读写与二进制读写的差别仅仅体现在回车换行符的处理上。文本方式写时,每遇到一个\n(0AH换行符),它将其换成\r\n(0D0AH,回车换行),然后再写入文件;当文本读取时,它每遇到一个\r\n将其反变化为\n,然后送到读缓冲区。二进制读写时,其不存在任何转换,直接将写缓冲区中数据写入文件。
“文件系统”是指操作系统中用于组织和管理磁盘上文件的方法以及数据结构。文件系统负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。程序中相关的文件操作,最后都会交给文件系统去替程序完成。
常见的文件系统有:FAT,NTFS,Ext2-4,ZFS等文件系统。其中FAT和NTFS用于WINDOWS系统,而Ext2-4用于Linux系统,ZFS则用于Solaris系统。
VFS是Linux中的一个抽象的统一接口:它定义了所有文件系统都支持的基本的和概念上的接口和数据结构,这样就在用户上层看来,无论对何种文件系统都拥有统一的接口,和操作方式。一是上层的文件系统的系统调用,二是虚拟文件系统 VFS(Virtual Filesystem Switch),三是挂载到 VFS 中的各实际文件系统。
在进行文件读写等操作的时候,首先需要调用fopen()函数打开文件,先得到文件的指针或者句柄。在打开文件时候,需要设置打开文件的标志。一些常见的标志如图所示:
fopen("newfile.txt", "rw, ccs=UTF-8");//默认为ANSI
fopen是C标准IO库的函数,与非C标准库函数open()函数相比,用fopen打开的文件读写是带缓存的,即用fwrite向文件里写一个字节,一般来讲它不会立刻调用write将该操作提交给kernel,而是积累到一定程度再一起写。
在Windows中,文本方式写时,存在”\n”à”\r\n”的转换,而二进制方式无转换。文本方式读时存在”\r\n”à至”\n”的转换,而二进制方式无转换。在linux中文本方式的读写与二进制方式的读写无差别,不存在回车换行间的转换。这样当直接在windows和linux中共享文件时,将会出现与回车换行相关的问题。
fopen_s()函数是用于fopen()的新的安全版本。现在都推荐使用fopen_s()来打开文件。它的调用方法:
FILE *pfile=NULL;
errno_t err = fopen_s(&pfile,FILENAME,"wb+");
if(err!=0 || pfile==NULL)
{
return -1;
}
当完成了文件IO之后,最后不要忘记了调用fclose()函数将文件关闭。比如:
fclose(pfile);
文件操作中,最频繁的就是文件的读写操作了。在C语言里,可以使用fread/fwrite,fscanf/fprintf,fgets/fputs,fgetc/fputc等函数来进行文件的读写。总的来说,C语言中文件的读写分为如下几种形式:
int binary_io()
{
//打开或者创建文件
char *path =
"h:\\doc\\new.txt";
FILE *fp1 = NULL;
errno_t err;
err =
fopen_s(&fp1,path,"w");
if(fp1==NULL || err !=
0)
{
printf("Open file failed\n");
return -1;
}
//基于fp1这个指针,对文件进行读写等操作
char buff[]="hello
world";
fwrite(buff,sizeof(buff),1,fp1);
int data = 0x10;
fwrite(&data,sizeof(data),1,fp1);
fclose(fp1);
//读数据
err =
fopen_s(&fp1,path,"r");
if(err!=0)
{
return -1;
}
data = 0;
memset(buff,0,sizeof(buff));
fread(buff,sizeof(buff),1,fp1);
printf("buff:%s\n",
buff);
fread(&data,sizeof(data),1,fp1);
printf("data:0x%x\n", data);
fclose(fp1);
return 0;
}
将除了ascci之外的数据以文本的方式写入文件
int format_io()
{
char
*file="h:\\doc\\1.txt";
FILE *pfile=NULL;
errno_t err =
fopen_s(&pfile,file,"w");
if(err!=0 ||
pfile==NULL)
{
printf("Open file failed\n");
return -1;
}
fprintf(pfile,"%s %x
%lf","hello-world",
0x10,3.1415);
fclose(pfile);
char buff[64]={0};
int data=0;
double d = 0.0;
err=fopen_s(&pfile,file,"r");
if(err!=0)
{
printf("Open file failed\n");
return -1;
}
fscanf_s(pfile,"%s%x%lf",buff,64,&data,&d);
printf("buff:%s,data:%d,d:%lf\n",buff,data,d);
fclose(pfile);
return 0;
}
int char_io()
{
char
*path="h:\\doc\\test.dat";
FILE *pfile = NULL;
errno_t err =
fopen_s(&pfile,path,"w");
if(err!=0 ||
pfile==NULL)
{
printf("Open file failed\n");
return -1;
}
char *str="hello world,
goodbye world!";
while(*str!='\0')
{
fputc(*str,pfile);
str++;
}
fclose(pfile);
err =
fopen_s(&pfile,path,"r");
if(err!=0
||pfile==NULL)
{
return -1;
}
while(!feof(pfile))
{
printf("%c",fgetc(pfile));
}
printf("\n");
fclose(pfile);
return 0;
}
int str_io()
{
char *file
="h:\\doc\\str.txt";
FILE
*pfile = NULL;
errno_t err =
fopen_s(&pfile,file,"w");
if(err!=0 ||
pfile==NULL)
{
return -1;
}
fputs("hello
world!\n",pfile);//\n-->\r\n
fputs("nice to meet u\n",pfile);
fclose(pfile);
err =
fopen_s(&pfile,file,"r");
if(err!=0 ||
pfile==NULL)
{
return -1;
}
char buff[1024]={0};
while(!feof(pfile))
{
fgets(buff,1024,pfile);
printf("%s", buff);
memset(buff,0,1024);
}
fclose(pfile);
return 0;
}
在Windows中,文本方式写时,存在”\n””\r\n”的转换,而二进制方式无转换。文本方式读时存在”\r\n”至”\n”的转换,而二进制方式无转换。在linux中文本方式的读写与二进制方式的读写无差别,不存在回车换行间的转换。这样当直接在windows和linux中共享文件时,将会出现与回车换行相关的问题。
在C语言中除了文件的读写操作之外,还有如下一些有关文件的其它操作:
在文件进行读写操作的时候,有一个叫当前读写位置的指针,用来记录文件当前读写的位置。可以通过rewind()函数或者fseek()函数来移动文件的当前读写位置指针。
rewind(fp);//首部
执行完上面的函数后,会将文件fp当前的读写位置移动到文件的开头位置。
将文件当前读写位置移动到最开始的地方。等同于:
fseek(fp,0,0);
fseek()函数用于更灵活的移动文件当前的读写位置指针。它有三个参数,第一个参数是被打开文件的指针,第二个参数是移动的偏移,偏移值可以为正或者为负,第三个参数是代表第二个参数的偏移相对于什么位置。比如:
fseek(fp,50,SEEK_CUR);
那么就是将文件的读写指针移动到当前位置后面50个字节。而SEEK_SET则是相对于文件开头,SEEK_END是文件结尾。比如
fseek(fp, -50, SEEK_END);
就是将文件读写指针移动到距离文件结束50个字节的位置。
下面是使用fseek()函数来移动文件读写指针的一个例子:
int seek_demo()
{
//打开或者创建文件
char *path = "h:\\doc\\new.txt";
FILE *fp1 = NULL;
errno_t err;
err = fopen_s(&fp1,path,"w");
if(fp1==NULL || err != 0)
{
printf("Open file failed\n");
return -1;
}
//基于fp1这个指针,对文件进行读写等操作
char buff[]="hello world";
fwrite(buff,sizeof(buff),1,fp1);
//rewind(fp1);
fseek(fp1,-5,SEEK_CUR);
int data = 0x10;
fwrite(&data,sizeof(data),1,fp1);
fclose(fp1);
//读数据
err = fopen_s(&fp1,path,"r");
if(err!=0)
{
return -1;
}
data = 0;
memset(buff,0,sizeof(buff));
fread(buff,sizeof(buff),1,fp1);
printf("buff:%s\n", buff);
fread(&data,sizeof(data),1,fp1);
printf("data:0x%x\n", data);
fclose(fp1);
return 0;
}
在读文件的时候,当文件的当前位置到达文件末尾,就不能继续读数据了。函数feof()就是用来判断文件当前位置是否达到文件末尾,如果到达,就返回真,否则为假。
下面是一个使用feof()来读文件的例子:
int read_ini()
{
char *file="h:\\doc\\config.ini";
FILE *pfile = NULL;
errno_t err = fopen_s(&pfile,file,"r");
if(err !=0 ||pfile == NULL)
{
return -1;
}
while(!feof(pfile))//如果文件当前读写位置没有到达结尾,就继续读
{
char buff[1024]={0};
fgets(buff,1024,pfile);
printf("%s",buff);
}
printf("\n");
fclose(pfile);
return 0;
}
文件的重命名,删除,创建文件夹都是与文件有关的一些基本操作。下面一一介绍C语言里这些函数的调用方式:
1,重命名文件:
void rename_file()
{
//将文件1.txt重命名为2.txt
char *src="h:\\doc\\1.txt";
char *dst="h:\\doc\\2.txt";
rename(src,dst);
}
2,删除文件
void delete_file()
{
char *del_file = "h:\\doc\\test.dat";
remove(del_file);
}
3,创建文件夹
int create_dir()
{
//创建文件夹
char *dir = "h:\\doc\\test\\x";
int res = _mkdir(dir);//注意,创建不能递归完成,h:\doc\test必须已经存在,否则函数会失败
if(res==-1)
{
printf("create dir failed\n");
return -1;
}
return 0;
}
函数ftell()用于获取文件读写指针的当前位置。可以结合fseek()函数来计算文件的大小,代码如下:
static long getfilesize(char *szFile)
{
long size = 0;
if (szFile == NULL)
{
return 0;
}
FILE *fp;
if((fp=fopen(szFile,"r"))==NULL)
return 0;
fseek(fp,0,SEEK_END);//首先将文件读写指针移动到文件结尾
size = ftell(fp); //获取文件读写指针的当前位置,即为文件大小;
fclose(fp);
return size;
}
首先定义下面这样一个结构体:
#define MAXLEN 64
typedef struct _record
{
char name[MAXLEN];
int age;
}record,*precord;
record r = {“tom”,25};
要将结构体变量r中的数据写入文件,可以按照下面的方法直接写入:
fwrite(&r,sizeof(r),1,fp);
当时,这样写入,会导致结构体中成员name[MAXLEN]中大量的零被写入,导致文件中存放了很多无用的数据零。所以,这样直接写入不是最佳方法。
于是可以考虑将这个结构体按照如下格式写入文件:
假如name[MAXLEN]中的有效字符个数为len,那么写入方法为:
Len+name+age
也就是先将name的字符数len写入文件,接着写入name的有效字符数据,再写入年龄数据。其中len和age各为4个字节的整数,name为字符。比如对于下面结构体记录数据:
record r1 =
{“tom”,25};
record r2 =
{“lily”,22};
这样在每个name前面都用一个长度来记录name中字符的个数,就可以避免将过多的无用的零写入文件了。
下面是实现这种方式写入和读出数据方法的代码:
int opt_write_file(FILE *fp)
{
char ch;
do
{
record rcd={0};
printf("Please input name and age\n");
scanf_s("%s%d", rcd.name,MAXLEN,&rcd.age);
size_t len = strlen(rcd.name);
fwrite(&len,sizeof(len),1,fp);//先写入name中字符的个数
fwrite(rcd.name,len,1,fp);//接着写入name字符数
fwrite(&rcd.age,sizeof(int),1,fp);//再写入年龄
printf("input q to quit,else to continue\n");
//scanf_s("%c",&ch,1);
//fflush(stdin);
ch = _getch();
if(ch=='q')
break;
} while (1);
return 0;
}
int opt_read_file(FILE *fp)
{
while(!feof(fp))
{
record rcd = {0};
size_t len=0;
int res = fread(&len,sizeof(len),1,fp);//先读取name的字符数
if(res == 0)
{
return 0;
}
fread(rcd.name,len,1,fp);//再按照字符数读取name
fread(&rcd.age,sizeof(rcd.age),1,fp);//再读取年龄
printf("name:%s,age:%d,res:%d\n", rcd.name,rcd.age,res);
}
return 0;
Copyright 2011-2020 © MallocFree. All rights reserved.