第十一章 文件操作


11.1普通文件与设备文件,文件夹

    “文件”是指一组相关数据的有序集合。普通文件存储在磁盘等外部设备中,在需要的时候由程序将数据读入内存。普通文件都有一个文件名,以方便程序打开和读写等操作。在操作系统中,把外部设备也看作是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和写。比如显示器、打印机、键盘等。

11.2文本文件与二进制文件   

从存储的格式来看,文件分为文本文件与二进制文件。

常见的文本文件:.c文件,.txt文件等。

    常见的二进制文件:exedlljpgdoc等。

 

    文本文件基于字符编码,以固定长度的二进制序列进行编码和解码(常见的有ASCII文编码和Unicode编码),而二进制文件是基于值编码的文件,二进制文件编码是变长的,具体的长度由具体的格式决定,比如EXE或者BMP二进制文件。文本文件使用notepad就可以读取,而二进制文件需要专门的工具,比如图片就需要专门的读图软件才能打开,如果用notepad打开,就会看见不少乱码。

    文本5678”的存储形式为:字符的ASCII码: 00110101 00110110 00110111 00111000 (四个字节)

    5678的存储形式为:值的二进制: 00010110 00101110 (两个字节)

“用文本方式读写的文件一定是文本文件,用二进制读写的文件一定是二进制文件”这类观点是错误的。C的文本方读写与二进制读写的差别仅仅体现在回车换行符的处理上。文本方式写时,每遇到一个\n(0AH换行符),它将其换成\r\n(0D0AH,回车换行),然后再写入文件;当文本读取时,它每遇到一个\r\n将其反变化为\n,然后送到读缓冲区。二进制读写时,其不存在任何转换,直接将写缓冲区中数据写入文件。

11.3文件系统

“文件系统”是指操作系统中用于组织和管理磁盘上文件的方法以及数据结构。文件系统负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。程序中相关的文件操作,最后都会交给文件系统去替程序完成。

常见的文件系统有:FATNTFSExt2-4ZFS等文件系统。其中FATNTFS用于WINDOWS系统,而Ext2-4用于Linux系统,ZFS则用于Solaris系统。

VFSLinux中的一个抽象的统一接口:它定义了所有文件系统都支持的基本的和概念上的接口和数据结构,这样就在用户上层看来,无论对何种文件系统都拥有统一的接口,和操作方式。一是上层的文件系统的系统调用,二是虚拟文件系统 VFS(Virtual Filesystem Switch),三是挂载到 VFS 中的各实际文件系统。

11.4打开文件fopen_s

在进行文件读写等操作的时候,首先需要调用fopen()函数打开文件,先得到文件的指针或者句柄。在打开文件时候,需要设置打开文件的标志。一些常见的标志如图所示:

 

 fopen("newfile.txt", "rw, ccs=UTF-8");//默认为ANSI

 

fopenC标准IO库的函数,与非C标准库函数open()函数相比,用fopen打开的文件读写是带缓存的,即用fwrite向文件里写一个字节,一般来讲它不会立刻调用write将该操作提交给kernel,而是积累到一定程度再一起写。

Windows中,文本方式写时,存在”\n”à”\r\n”的转换,而二进制方式无转换。文本方式读时存在”\r\n”à”\n”的转换,而二进制方式无转换。在linux中文本方式的读写与二进制方式的读写无差别,不存在回车换行间的转换。这样当直接在windowslinux中共享文件时,将会出现与回车换行相关的问题。

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);

11.5读写文件

文件操作中,最频繁的就是文件的读写操作了。在C语言里,可以使用fread/fwrite,fscanf/fprintf,fgets/fputs,fgetc/fputc等函数来进行文件的读写。总的来说,C语言中文件的读写分为如下几种形式:

11.5.1 二进制文件读写

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;

}

11.5.2 格式化输入输出到文本文件。

将除了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;

}

11.5.3字符输入输出到文本文件

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;

 

}

11.5.4 字符串输入输出到文本文件

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中文本方式的读写与二进制方式的读写无差别,不存在回车换行间的转换。这样当直接在windowslinux中共享文件时,将会出现与回车换行相关的问题。

 

11.6文件相关操作

C语言中除了文件的读写操作之外,还有如下一些有关文件的其它操作:

11.6.1rewind

在文件进行读写操作的时候,有一个叫当前读写位置的指针,用来记录文件当前读写的位置。可以通过rewind()函数或者fseek()函数来移动文件的当前读写位置指针。

rewind(fp);//首部

执行完上面的函数后,会将文件fp当前的读写位置移动到文件的开头位置。

将文件当前读写位置移动到最开始的地方。等同于:

fseek(fp,0,0);

11.6.2fseek

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;

 

}

11.6.3feof

在读文件的时候,当文件的当前位置到达文件末尾,就不能继续读数据了。函数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;

}

11.6.4rename/remove/mkdir

文件的重命名,删除,创建文件夹都是与文件有关的一些基本操作。下面一一介绍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;

}

11.6.5ftell

函数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;

}

 

11.7结构体的文件读写更新

首先定义下面这样一个结构体:

#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的有效字符数据,再写入年龄数据。其中lenage各为4个字节的整数,name为字符。比如对于下面结构体记录数据:

record  r1  =  {“tom”,25};

record  r2  =  {“lily”,22};

 

3”tom”25 4”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;



看文字不过瘾?点击我,进入周哥教IT视频教学
麦洛科菲长期致力于IT安全技术的推广与普及,我们更专业!我们的学员已经广泛就职于BAT360等各大IT互联网公司。详情请参考我们的 业界反馈 《周哥教IT.C语言深学活用》视频

我们的微信公众号,敬请关注