【C語言】第10章 對文件的輸入輸出


第10章 對文件的輸入輸出

文件的分類

程序文件、數據文件、磁盤文件、輸入文件、輸出文件、

“文件”指存儲在外部介質上數據的集合

輸入操作時,數據從文件流向計算機內存
輸出操作時,數據從計算機流向文件

無論是用Word打開或保存文件,還是C程序中的輸入輸出都是通過操作系統進行的

“流”是一個傳輸通道,數據可以從運行環境流入程序中,或從程序流至運行環境

文件標識包括三部分:
(1) 文件路徑
(2) 文件名主干
(3) 文件后綴

根據數據的組織形式,數據文件可分為ASCII文件和二進制文件。

數據在內存中是以二進制形式存儲的,如果不加轉換地輸出到外存,就是二進制文件
如果要求在外存上以ASCII代碼形式存儲,則需要在存儲前進行轉換
ASCII文件又稱文本文件,每一個字節放一個字符的ASCII代碼

字符一律以ASCII形式存儲

image-20210306135756174

文件緩沖區

image-20210306135856070

打開和關閉文件

用fopen函數打開數據文件

用fclose函數關閉數據文件

fopen函數的調用方式為:
fopen(文件名,使用文件方式);
例如: 
fopen(“a1”,”r”); 
表示要打開名為“a1”的文件,使用文件方式為“讀入”
fopen函數的返回值是指向a1文件的指針
    
通常將fopen函數的返回值賦給一個指向文件的指針變量。如:
FILE *fp; 
fp=fopen(“a1”,”r”);
fp和文件a1相聯系,fp指向了a1文件
    
在打開一個文件時,通知編譯系統以下3個信息:
① 需要訪問的文件的名字
② 使用文件的方式(“讀”還是“寫”等)
③ 讓哪一個指針變量指向被打開的文件

r---只讀

w---只寫

a---希望向文件末尾添加新的數據

r+、w+、a+、輸入數據和輸出數據

如果打開失敗,fopen函數將帶回一個空指針值NULL

常用下面的方法打開一個文件:
  if ((fp=fopen(“file1”,’r″))==NULL)
  {  printf(“cannot open this file\n”);
     exit(0);//終止正在執行的程序
  }

標准輸入流、標准輸出流、標准出錯輸出流。

關閉文件用fclose函數。fclose函數調用的一般形式為
fclose(文件指針); 
例如: 
fclose  (fp); 

順序讀寫數據文件

讀寫一個字符的函數

函數名 調用形式 功能 返回值
fgetc fgetc(fp) 從fp指向的文件讀入一個字符 讀成功,帶回所讀的字符,失敗則返回文件結束標志EOF(即-1)
fputc fputc(ch,fp) 把字符ch寫到文件指針變量fp所指向的文件中 寫成功,返回值就是輸出的字符;輸出失敗,則返回EOF(即-1)
例10.1 從鍵盤輸入一些字符,逐個把它們送到磁盤上去,直到用戶輸入一個“#”為止。
思路:用fgetc函數從鍵盤逐個輸入字符,然后用fputc函數寫到磁盤文件即可。

#include <stdio.h>
#include <stdlib.h>
int main()
{  FILE *fp;
   char ch, filename[10];
   printf("請輸入所用的文件名:");
   scanf("%s",filename);
   if((fp=fopen(filename,"w"))==NULL)  
   {  printf("無法打開此文件\n");   
      exit(0); 
   }
   ch=getchar( );
   printf("請輸入一個字符串(以#結束):");
   ch=getchar( );  
   while(ch!='#')   
   {  fputc(ch,fp);   
      putchar(ch);   
      ch=getchar(); 
    }
    fclose(fp);   
    putchar(10);  
    return 0;
}
例10.2 將一個磁盤文件中的信息復制到另一個磁盤文件中。今要求將上例建立的file1.dat文件中的內容復制到另一個磁盤文件file2.dat中。
思路:處理此問題的算法是:從file1.dat文件中逐個讀入字符,然后逐個輸出到file2.dat中。

#include <stdio.h>
#include <stdlib.h>
int main( )
{  FILE *in,*out; 
   char ch, infile[10], outfile[10];   
   printf("輸入讀入文件的名字:");
   scanf("%s",infile);   
   printf("輸入輸出文件的名字:");
   scanf(“%s”,outfile);   
   if ((in=fopen(infile,“r”))==NULL)   
   {  printf("無法打開此文件\n"); exit(0);}
   if ((out=fopen(outfile,“w”))==NULL)   
   {  printf("無法打開此文件\n"); exit(0); }
   while(!feof(in))  //檢查當前讀寫位置是否移到文件末尾 
   {   ch=fgetc(in);  
       fputc(ch,out);   
       putchar(ch);   
   }
   putchar(10);   
   fclose(in);   
   fclose(out);   
   return 0;
}

讀寫一個字符串的函數

函數名 調用形式 功能 返回值
fgetc fgetc(fp) 從fp指向的文件讀入長度為(n-1)的字符串,存放到字符數組str中 讀成功,返回地址str,失敗則返回NULL)
fputc fputc(ch,fp) str所指向的字符串寫到文件指針變量fp所指向的文件中 寫成功,返回0;否則返回非0值

說明:
fgets函數的函數原型為:
char *fgets (char *str, int n, FILE *fp);
其作用是從文件讀入一個字符串
調用時可以寫成:
fgets(str,n,fp);

fgets(str,n,fp);中n是要求得到的字符個數,但實際上只讀n-1個字符,然后在最后加一個’\0’字符,這樣得到的字符串共有n個字符,把它們放到字符數組str中
如果在讀完n-1個字符之前遇到換行符“\n”或文件結束符EOF,讀入即結束,但將所遇到的換行符“\n”也作為一個字符讀入
執行fgets成功,返回str數組首地址,如果一開始就遇到文件尾或讀數據錯,返回NULL

fputs函數的函數原型為:
int fputs (char *str, FILE *fp);
str指向的字符串輸出到fp所指向的文件中
調用時可以寫成: fputs(″China”,fp);
fputs函數中第一個參數可以是字符串常量、字符數組名或字符型指針
字符串末尾的′\0′不輸出
輸出成功,函數值為0;失敗,函數值為EOF

例10.3 從鍵盤讀入若干個字符串,對它們按字母大小的順序排序,然后把排好序的字符串送到磁盤文件中保存。
思路:為解決問題,可分為三個步驟:
從鍵盤讀入n個字符串,存放在一個二維字符數組中,每一個一維數組存放一個字符串;
對字符數組中的n個字符串按字母順序排序,排好序的字符串仍存放在字符數組中;
將字符數組中的字符串順序輸出。

#include <stdio.h>
#include <stdlib.h>
#include <string.h> 
int main()
{  FILE *fp;
   char  str[3][10],temp[10];   
   int i,j,k,n=3;
   printf("Enter strings:\n"); 
   for(i=0;i<n;i++) gets(str[i]);
   for(i=0;i<n-1;i++)   
   {  k=i;
      for(j=i+1;j<n;j++)
         if(strcmp(str[k],str[j])>0) k=j;
      if(k!=i)
      {  strcpy(temp,str[i]); strcpy(str[i],str[k]); 
         strcpy(str[k],temp);}
   }
   if((fp=fopen("string.dat", "w"))==NULL)   
   {  printf("can't open file!\n"); exit(0);}
   printf("\nThe new sequence:\n");
   for(i=0;i<n;i++)
   {  fputs(str[i],fp);
      fputs("\n",fp);  
      printf("%s\n",str[i]);   
   }
   return 0;
}

用格式化的方式讀寫文件

一般調用方式為:
fprintf(文件指針,格式字符串,輸出表列);
fscanf (文件指針,格式字符串,輸入表列);
如:
fprintf (fp,”%d,%6.2f”,i,f);
fscanf (fp,”%d,%f”,&i,&f);

用二進制方式向文件讀寫一組數據

fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);

例10.4 從鍵盤輸入10個學生的有關數據,然后把它們轉存到磁盤文件上去。
思路:定義有10個元素的結構體數組,用來存放10個學生的數據
從main函數輸入10個學生的數據
用save函數實現向磁盤輸出學生數據
用fwrite函數一次輸出一個學生的數據

#include <stdio.h>
#define SIZE 10
struct Student_type
{  char name[10];
   int num;
   int age;
   char addr[15];
 }stud[SIZE]; 

void save( )   
{  FILE *fp;   int i;
   if ((fp=fopen("stu.dat","wb"))==NULL)      
   {  printf("cannot open file\n");
       return;
   }
   for (i=0;i<SIZE;i++)
      if (fwrite(&stud[i], sizeof(struct Student_type), 1,fp)!=1)
         printf("file write error\n");
   fclose(fp);
}

int main()
{  int i;
   printf("enter data of students:\n");
   for(i=0;i<SIZE;i++)   
      scanf("%s%d%d%s",
         stud[i].name,&stud[i].num,
         &stud[i].age,stud[i].addr);
   save( );
   return 0;
}
為了驗證在磁盤文件“stu.dat”中是否已存在此數據,可以用以下程序從“stu.dat”文件中讀入數據,然后在屏幕上輸出。
#include <stdio.h>
#include <stdlib.h>
#define SIZE 10
struct Student_type
{  char name[10];
   int num;
   int age;
   char addr[15];
}stud[SIZE]; 

int main( )
{  int i;   FILE *fp;
   if ((fp=fopen("stu.dat","rb"))==NULL)
   {  printf("cannot open file\n"); exit(0); }
   for (i=0;i<SIZE;i++)
   {  fread (&stud[i],sizeof(struct
	Student_type),1,fp);   
       printf (“%-10s %4d %4d  %-15s\n”,
 	stud[i].name,stud[i].num,
 	stud[i]. age,stud[i].addr);
   }
   fclose (fp);  
   return 0;
}
如果修改例10.4:從已有的二進制文件“stu.list”中,讀入數據並輸出到“stu.dat”文件中,應如何修改程序? 
思路:編寫load函數
main函數中再調用load函數

void load( )
{  FILE *fp;   int i;   if((fp=fopen("stu_list","rb"))==NULL)   
   {  printf("cannot open infile\n"); return;}
   for (i=0;i<SIZE;i++)
      if (fread(&stud[i],sizeof(struct student_type),1,fp)!=1)   
      {  if(feof(fp)) 
         {  fclose(fp);   return;}
         printf("file read error\n");
      }
   fclose (fp);
}
int main()
{  load();
 save(); 
   return 0;
}
隨機讀寫數據文件

文件位置標記的定位
可以強制使文件位置標記指向指定的位置
可以用以下函數實現:
(1) 用rewind函數使文件標記指向文件開頭
rewind函數的作用是使文件標記重新返回文件的開頭,此函數沒有返回值。

例10.5 有一個磁盤文件,內有一些信息。要求第一次將它的內容顯示在屏幕上,第二次把它復制到另一文件上。
思路:因為在第一次讀入完文件內容后,文件標記已指到文件的末尾,如果再接着讀數據,就遇到文件結束標志,feof函數的值等於1(真),無法再讀數據
必須在程序中用rewind函數使位置指針返回文件的開頭

#include<stdio.h>
int main()
{  FILE *fp1,*fp2;
   fp1=fopen(“file1.dat”,“r”);   
   fp2=fopen(“file2.dat”,“w”);   
   while(!feof(fp1))   
       putchar(getc(fp1));   
   putchar(10);  
   rewind(fp1);   
   while(!feof(fp1)) 
       putc(getc(fp1),fp2);   
   fclose(fp1);   fclose(fp2);
   return 0;
}

文件位置標記的定位
可以強制使文件標記指向指定的位置
可以用以下函數實現:
(2) 用fseek函數改變文件標記
fseek函數的調用形式為:
fseek(文件類型指針,位移量,起始點)
起始點0代表“文件開始位置”,1為“當前位置”,2為“文件末尾位置”

C標准指定的名字

起始點 名 字 用數字代表
文件開始位置 SEEK_SET 0
文件當前位置 SEEK_CUR 1
文件末尾位置 SEEK_END 2

fseek函數一般用於二進制文件。下面是fseek函數調用的幾個例子:
fseek (fp,100L,0);
fseek (fp,50L,1);
fseek (fp,-10L,2);

文件位置標記的定位
可以強制使文件位置標記指向指定的位置
可以用以下函數實現:
(3) 用ftell函數測定文件位置標記的當前位置
ftell函數的作用是得到流式文件中文件位置標記的當前位置。

由於文件中的文件位置標記經常移動,人們往往不容易知道其當前位置,所以常用ftell函數得到當前位置,用相對於文件開頭的位移量來表示。如果調用函數時出錯(如不存在fp指向的文件),ftell函數返回值為-1L。例如:
i=ftell(fp);
if(i==-1L) printf(“error\n”);

例10.6 在磁盤文件上存有10個學生的數據。要求將第1,3,5,7,9個學生數據輸入計算機,並在屏幕上顯示出來。(要求:從例10.4中建立的“stu.dat”中讀入數據)
思路:按二進制只讀方式打開文件
將文件位置標記指向文件的開頭,讀入一個學生的信息,並把它顯示在屏幕上
再將文件標記指向文件中第3,5,7,9個學生的數據區的開頭,讀入相應學生的信息,並把它顯示在屏幕上
關閉文件

#include<stdio.h>
#include <stdlib.h>
struct St
{  char name[10];
   int num;
   int age;
   char addr[15];
}stud[10]; 
int main()
{  int i;  FILE *fp;         
   if((fp=fopen(“stu.dat”,“rb”))==NULL)  
   {  printf("can not open file\n"); exit(0); }
   for(i=0;i<10;i+=2)
   {  fseek(fp,i*sizeof(struct St),0);  
      fread(&stud[i], sizeof(struct St),1,fp);  
      printf(“%-10s %4d %4d %-15s\n”,
 	stud[i].name,stud[i].num,
 	stud[i].age,stud[i].addr);  
    }
    fclose(fp);  return 0;
}
文件讀寫的出錯檢測
  1. ferror函數
    ferror函數的一般調用形式為:ferror(fp);
    如果返回值為0,表示未出錯,否則表示出錯
    每次調用輸入輸出函數,都產生新的ferror函數值,因此調用輸入輸出函數后立即檢查
    調用fopen時,ferror的初始值自動置為0

  2. clearerr函數
    作用是使文件錯誤標志和文件結束標志置為0
    調用一個輸入輸出函數時出現錯誤(ferror值為非零值),立即調用clearerr(fp),使ferror(fp)值變0,以便再進行下一次檢測
    只要出現文件讀寫錯誤標志,它就一直保留,直到對同一文件調用clearerr函數或rewind函數,或任何其他一個輸入輸出函數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM