欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C 语言学习笔记(七)——文件操作(2)

程序员文章站 2022-03-09 16:16:01
...

1.stat函数

它是得到文件各种属性的,和内容无关
函数的第一个参数代表文件名,第二个参数是struct stat结构。
得到文件的属性,包括文件建立时间,文件大小等信息。
需要包含三个头文件

      struct stat {
               dev_t     st_dev;     /* ID of device containing file */
               ino_t     st_ino;     /* inode number */
               mode_t    st_mode;    /* protection */
               nlink_t   st_nlink;   /* number of hard links */
               uid_t     st_uid;     /* user ID of owner */
               gid_t     st_gid;     /* group ID of owner */
               dev_t     st_rdev;    /* device ID (if special file) */
               off_t     st_size;    /* total size, in bytes */
               blksize_t st_blksize; /* blocksize for filesystem I/O */
               blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
               time_t    st_atime;   /* time of last access */
               time_t    st_mtime;   /* time of last modification */
               time_t    st_ctime;   /* time of last status change */
           };
           int size=st.st_size;//得到文件的大小

2.fread&fwrite函数

fgets fputs fprintf fscanf这些函数都是针对文本文件的,不能对一个二进制文件进行读写
除文本文件之外的文件都是二进制文件,比如图像,可执行程序,音乐等这些都是二进制

之前学习的内容都是往文件里面写一个字符串
如果要把一个整数(int)写入文件,以上函数都是不可用的,如果写入了,这个文件就不是文本文件了。

size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

第一个参数是buf的内存地址,第二个参数是每个单位的大小,第三个参数是写多少个单位,第四个参数是fopen返回的文件指针,只要第二个参数和第三个参数乘积一样,和第一个参数所指的内存区域一样大就可以。
注意:这个函数以二进制形式对文件进行操作,不局限于文本文件
返回值:返回实际写入的数据块数目

#include<stdio.h>
int main()
{
    FILE *p=fopen("a.dat","w");
    int a=100;
        fwrite(&a,1,sizeof(int),p);//要往文件里面写四个BYTE的内容
        //fwrite(&a,sizeof(int),1,p);//结果和上面是一样的,下面是写一个整数,上面是写四个字节
        fclose(p);
    return 0;
}

fwrite去读取二进制文件

#include<stdio.h>
int main(int argc,char **args)
{
    if(argc<2)
    return 0
    FILE *p=fopen(args[1],"w");
    while(1)
    {   int a=0;//unsigned char a=0;按字节读
        fread(&a,1,sizeof(a),p);//fread(&a,1,sizeof(a),p);
        if(feof(p))
            break;
        printf("%d\n",a);//printf("%x\n",a);
    }
    fclose(p);
    return 0;
}

fread返回值是成功读到的单位数目
fread第二个参数代表了一个单位多大,第三个参数代表一次要读多少单位

fopen的其他模式

a模式

a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留),如果没有文件,那么和w是一样的,如果文件存在,那么不覆盖这个文件
a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)

b模式

r 以只读方式打开文件,该文件必须存在,文件必须是可读的。
r+ 以可读写方式打开文件,该文件必须存在。
rb+ 读写打开一个二进制文件,允许读写数据,文件必须存在。
1.在windows系统中,文本模式下,文件以”\r\n”代表换行。若以文本模式打开文件,并用fputs等函数写入换行符”\n”时,函数会自动在”\n”前面加上”\r”。即实际写入文件的是”\r\n” 。
2.在类Unix/Linux系统中文本模式下,文件以”\n”代表换行。所以Linux系统中在文本模式和二进制模式下并无区别。
在windows读写文本文件的时候,是不写b,这样就不用单独处理这个\r了。但读写二进制文件的时候一定要写b,防止系统无谓的添加\r
Linux,b是忽略的。
windows所有的文件都是\r\n结尾的,而不是\n结尾的
如果读文件的时候,加“r”参数,那么系统会自动把\n前面的\r吃掉,一旦添加了”b”系统就不会自动吃掉。
在w写的时候,在\n前面会自动添加一个\r,如果添加了b参数,那么不会自动添加\r,如果在windows下,用“wb”写文本的时候,写了hello\nworld那么在文本模式下,并没有换行,得到的结果是helloworld

ftp传输文件
如果用binary模式传输,那么linux下的文件到了windows都不会换行,那么可以用 ascii模式传输,会自动加\r

二进制文件的拷贝

#include<stdio.h>
#include<sys/stat.h>
#include<stdlib.h>
int main(int argc ,char **args)
{
    if(argc<3)
    return 0;
    FILE *p=fopen("args[1]","rb")
    if(p==NULL)
    return 0;
    FILE *p1=fopen("args[2]","wb")
    if(p==NULL)
    return 0;
    stat(args[1],&st);
    int size=st.st_size;//得到文件大小
    char *buf=malloc(size);//根据文件大小,动态分配一个内存出来
    //while(1)
    while(!feof(p))
    {
        char a[1024]={0};
        //char a=0;//这个代码循环了很多很多次
        //fread(&a,1,1,p);
        //fread(a,1,sizeof(a),p);//这个拷贝完文件大小不同了,出问题了。那么此时可以得到它的返回值
        //int res=fread(a,1,sizeof(a),p);
        int res =fread(buf,1,size,p);//
    //  if(feof(p))
    //      break;
        //fwrite(&a,1,1,p1);
        //fwrite(a,1,sizeof(a),p1);
        //fwrite(a,1,res,p1);//在res为零的时候,直接break出去了,所以要改一下逻辑。
        fwrite(buf,1,res,p1);//这种也有问题,因为文件很大会一下把内存用完,所以需要在上面做一个阀值。
        //这个程序还能优化,但是还是需要循环很多次,所以需要进一步优化(通过得到文件的大小)
    }
    fclose(p);
    fclose(p1);
    free(buf);
    return 0;
}

3.fseek函数&ftell

FILE结构内部是有一个指针的,每次调用文件读写函数,这些函数就会自动移动这个指针
默认情况下,指针只能从前往后移动。
int fseek(FILE * _File, long _Offset, int _Origin);
函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返回一个非0值。
实验得出,超出文件末尾位置,还是返回0。往回偏移超出首位置,会返回-1,请小心使用。

第一个参数stream为文件指针
第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移
第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾

fseek(fp, 3, SEEK_SET);

如果读到最后接着读,它就会打印之前的内容。

可以利用下面的程序瞬间生成一个大的空文件

fseek(p,10000000,SRRK_SET);
char a=0;
fwrite(&a,1,sizeof(a),p);

ftell函数
函数 ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。
long len = ftell(fp)

int loc=ftell(p)

可以利用以下代码获取文件大小

seek(p,o,SEEK_END);
int loc=ftell(p);

4.C语言读写缓冲区与fflush函数

c语言所有的文件操作函数都是缓冲区函数
C 语言学习笔记(七)——文件操作(2)

下面用程序验证这一点。

#include<stdio.h>
int main()
{
    FILE *p=fopen("a.txt","w");
    while(1)
    {
        char a[100]={0};
        scanf("%s",a);
        if(strcmp(a,"exit")==0)
        break;
        fprintf(p,"%s\n",a);
        fflush(p);//把缓冲区数据直接同步到磁盘
    }
    fclose;
    return 0;
}

在没有添加fflush时,在输入exit之前,打开创建的a.txt可以发现,里面内容一直是空的。如果加了fflush,那么输入什么,a.txt就会实时改变

fflush函数可以将缓冲区中任何未写入的数据写入文件中。
成功返回0,失败返回EOF。
int fflush(FILE * _File);
由于fflush是实时 的将缓冲区的内容写入磁盘,所以不要大量去使用,(首先特别慢,其次大量读写会减少磁盘寿命)但如果是特别敏感的数据,可以通过fflush写入磁盘,防止由于电脑各种故障,内存的数据丢失。

结构体和二进制文件

#include<stdio.h>
struct man{
char name[20];
int age;
}
int main01()//写
{
    struct man m[3]={{"李某"40},{"刘某"20},{"孙某",30}};
    FILE *p=fopen("a.dat","w");
    if(p==NULL)
    return 0;
    fwrite(m,3,sizeof(struct man),p);//此时m是数组,不需要&m了
    fclose(p);
    return 0;
}
int main02()
{
    struct man m[3]={0};
    FILE *p=fopen("a.dat","r");
    if(p==NULL)
    return 0;
    fread(m,3,sizeof(struct man),p);//也可以用循环,每次只读一个
    int i;
    for(i=0;i<3;i++)
    printf("%s,%d\n",m[i].name,m[i].age);
    fclose(p);
    return 0;
}

通过下面的程序直接把上面两个程序合并

#int main()//参数1 代表输入 2 代表显示
{
    if (argc<2)
    return 0;
    char c=args[1][0];
    if (c=='1')
    main01();
    if (c=='2')
    main 02();
    return 0;
}

练习——上面代码只能全部显示,需要修改为,如果输入all就全部显示,如果具体输入某人的名字,那么只显示这个人名,如果不存在,显示“not found”

int select()
{
    struct man m={0};
    char name[30]={0};
    printf("input name:");
    scanf("%s",&name);
    int status=0;
    FILE *p=fopen("a.dat","r");
    if(p==NULL)
    return 0;
    while(1){
    fread(&m,1,sizeof(struct man),p);
    if(feof(p))
        break;
    if(strcmp(name,"all")==0)
    {printf("%s,%d\n",m.name,m.age);status=1;}
    else
    {
    if(strcmp(name,m.name)==0)
    printf("%s,%d\n",m.name,m.age);
    status=1;
    }
    }
    fclose(p);
    if(status==0)
        printf("not found");
    return 0;
}

对于第一个写的程序,如何在里面减人呢?也就是可以删除一个指定的人名和对应的年龄,如果输入一个没有的名字,什么都不做

int delete()
{
    FILE *p=fopen("a.dat","r");
    if(p==NULL)
    return 0;
    fseek(p,0,SEEK_END);
    int size=ftell(p);//得到文件大小
    fseek(p,0,SEEK_SRT);
    fread(m,1,size,p);//一下把文件所有内容读到内存
    fclose(p);
    printf("input name");
    char name[30]={0};
    scanf("%s\n",name);
    int (n)=size/sizeof(struct man);//得到有多少记录数
    int i;
    p=fopen("a.dat","w");
    for(i=0;i<n;i++)
    {
        if(strcmp(m[i].name,name)!=0)
        fwrite(&m[i],1,sizeof(struct man),p);
        //printf("%s,%d\n",m[i].name,m[i].age);//看看是否能把所有内容读出来

    }

    fclose(p);
    free(m);
    return 0;

}