11文件操作

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

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

11.2 文本文件与二进制文件

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

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

常见的二进制文件:exe,dll,jpg,doc等。

文本文件基于字符编码,以固定长度的二进制序列进行编码和解码(常见的有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 文件系统

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

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

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

11.4 打开文件fopen_s

在进行文件读写等操作的时候,首先需要调用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);

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

11.6 文件相关操作

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

11.6.1 rewind

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

rewind(fp);//首部

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

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

fseek(fp,0,0);

11.6.2 fseek

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.3 feof

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

函数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的有效字符数据,再写入年龄数据。其中len和age各为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;

本页共372段,9716个字符,15026 Byte(字节)