C 語言學習筆記(七)——文件操作(2)

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語言所有的文件操作函數都是緩衝區函數
這裏寫圖片描述

下面用程序驗證這一點。

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

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章