> For the complete documentation index, see [llms.txt](https://information9527.gitbook.io/cbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://information9527.gitbook.io/cbook/10.-dang-an-chu-li.md).

# 十、檔案處理

檔案是眾多的字元所組成的集合體，部分語言會將它視為一種資料串流(Stream)。\
與資料串流有關的資訊，是記錄在一個資料型態為資料串流列別的資料串流物件變數，作用為資料串流與檔案的溝通橋樑。

所以檔案處理也能稱作是資料串流處理。資料串流類別分成三種【資料輸入串流類別(ifsteam)】、【資料輸出串流類別(ofsteam)】、【資料輸入/輸出串流類別(fsteam)】。

## 檔案類型

### 一、文字檔

文字檔以列為單位，列與列之間為 '\n' 字元；資料中每一個字元，以對應的ASCII碼來儲存。

{% hint style="info" %}
若用文字檔來存, 存下來的會是"100000"這個字串, 佔7個byte。
{% endhint %}

### 二、二進位檔

二進位 檔裡面沒有額外的 '\n' 字元 ; 資料中每一個字元都直接以二進制的格式存取。例如執行檔、圖片、影片等，但二進制是無法使用在文字編輯軟體處理，若強制開啟會遇到亂碼。

![](/files/-MVq7rRQRIp0yJsbXcmN)

檔案依存取權限，可分為一般檔案與唯讀檔 (read-only)，檢視方式對文件按下右鍵，是否有勾起屬性中的唯獨。

![](/files/-MVq8U1Bs7FCT9Tu0VgH)

{% hint style="info" %}
若用二進位檔來存, 存下來的是0x186A0的的二進位值, 固定佔4個byte. 因為int型態就是占4個byte.
{% endhint %}

## 檔案存取

存取方式分為兩種，循序存取、隨機存取。

### 一、循序存取(sequential access)

意指一組序列（例如存於記憶數組、磁盤軟體或是磁帶中的資料）是以預先安排，有秩序的方式被人存取。循序存取有時只是唯一的存取數據方式，磁帶即屬一例。循序存取亦有可能成為有選擇性的方式，就如我們純粹有意順序處理一組資料元件。

資料寫入檔案時，附加檔案尾端，讀取時，由檔案的開端由前往後一個字元一個字元讀出。常見於文字檔方式。

### 二、隨機存取(random access)

&#x20;又稱**直接存取**（direct access），代表同一時間存取一組序列中的一個隨意元件。反之則稱[循序存取](https://zh.wikipedia.org/wiki/%E5%BE%AA%E5%BA%8F%E5%AD%98%E5%8F%96)，即是需要更多時間去存取一個遠端元件。介分兩者的傳統圖解就似比較一軸古代畫卷（循序︰所有在元件之前的物料必須事先捲開）及一本圖書（隨機︰可以隨時翻至任何一頁）。而更近現代的例子就如比較卡式磁帶。

資料是以一筆紀錄(結構型態)作為單位寫入檔案，用每一筆紀錄長度相同，可利用目前資料紀錄所在位置，算出實際資料位置並取得資料。常見於二進制檔。

## 有緩衝區與無緩衝區的檔案處理

![](/files/-MVqAof3g4MF78CoTTRS)

## C語言

### 檔案處理函數 fopen()

{% hint style="info" %}

### 有緩衝區的檔案處理函數&#x20;

```c
FILE 指標變數;  /* 宣告指向檔案結構的指標 */ 
指標變數 = fopen("欲開啟檔案名稱", "存取模式");
```

{% endhint %}

![](/files/-MVqDtmUUdaWFIAI0MZZ)

### 開啟檔案的範例

```c
FILE *fptr; /* 宣告指向檔案的指標fptr */
fptr = fopen("abc.txt","r"); /* 開啟檔案abc.txt以供讀取 */
if(fptr!=NULL) /* 判別檔案是否開啟成功 */
{
   /* 檔案開啟成功時，所要執行的程式碼 */
}
else
{
   /* 檔案開啟失敗時，所要執行的程式碼 */
}
```

### stdio.h 標頭檔中的檔案處理函數

![](/files/-MVqELV4_hVz36oWBTYj)

![](/files/-MVqEOghHlCNNbvMVK9_)

### 寫入檔案(1) - 範例

使用輸出轉向來把printf的結果轉到一個檔案中, 其實就算是最簡單的寫檔案方式了. 輸出轉向的工作, 可以由程式碼來完成.

```c
#include <stdio.h>
#include <stdlib.h>


int main(int argc, char const *argv[])
{
    FILE *f1; // 用來指向一個輸出的位置
    f1 = fopen("test.txt","wt");
    fprintf(f1  , "Helle File !! \n");
    
    system("echo Write 'Test.txt' Content Done");
    system("PAUSE");
    return 0;
}

```

{% hint style="info" %}

### fopen 寫法

```c
f1 = fopen("test.txt","wt");
```

第1個參數是檔名\
第2個參數是開啟檔案的模式, w代write, t代text
{% endhint %}

### 寫入檔案(2) - 範例

此方法執行完，可以自動打開test.txt檔案。

```c
#include <stdio.h>
#include <stdlib.h>


int main(int argc, char const *argv[])
{
    FILE *f1;
    f1 = fopen("test.txt","wt");
    fprintf(f1  , "Helle File !! \n");
    
    fclose(f1);//關閉file
    system("start test.txt");
    return 0;
}

```

{% hint style="info" %}

### FILE \*的指標

C函式庫中已經宣告好3個FILE \*的指標, 分別是stdin, stdout, stderr. \
1.printf("...")事實上是呼叫fprintf( stdout, "....").\
2.scanf("...")事實上是呼叫fsanf( stdin, "..." ). \
3.fprintf( stderr, "....")所印在螢幕上的東西不會被輸出轉向所影響.
{% endhint %}

### 文字檔 input/output - 範例

使用文字檔時的兩個主要函式就是fprintf/fscanf. 它們的用法和printf/scanf幾乎完全一樣, 只差在需要指定它們input/output的來源/目標.

```c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *input;
	FILE *output;
	char string[80];

	input=fopen("test.txt","rt");
	output=fopen("outfile.txt","wt"); 

	if ( input==NULL )
	{
		fprintf( output, "open file fail!\n");
		exit(0);
	}
	
	fscanf(input,"%s",string);
	fprintf( output, "%s\n",string);

	fclose(input);
	fclose(output);
	system("start outfile.txt");
	return 0;
}
```

### 文字檔 迴圈(1)- 範例

```c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *output;
	output=fopen("test.txt","wt"); 
	for ( int i=0; i<10; i++ )
	{
		fprintf( output, "%d\n",i);
	}
	fclose(output);
	system("start test.txt");
	return 0;
}
```

### 文字檔 迴圈(2)- 範例

每執行一個fflush就會更新一次檔案的內容.

```c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *output;
	output=fopen("test.txt","wt"); 
	for ( int i=0; i<10; i++ )
	{
		fprintf( output, "%d\n",i);
		fflush(output);
	}
	fclose(output);
	system("start test.txt");
	return 0;
}
```

除了fprintf/fscanf, 文字檔還有fputc/fgetc可以用, 用法和putc/getc幾乎一樣

### 二進位檔 寫入檔案(1)-範例

```c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *output;
	int  i=10000;
	output=fopen("bin-file.bin","wb"); 
	fwrite( &i, sizeof(int), 1, output );
	fclose(output);
	// system("start bin-file.bin");
	return 0;
}
```

### 二進位檔 寫入檔案(2) fread/fwrite-範例

```c
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE *input;
	int  i=10000;
	input=fopen("bin-file.bin","rb"); 
	if ( input==NULL )
	{
		printf("open input file fail!\n");
		exit(0);
	}
	fread( &i, sizeof(int), 1, input );
	printf("%d\n",i);
	fclose(input);
	// system("start bin-file.bin");
	return 0;
}
```

### 二進位檔 寫入檔案(3) -範例

二進位檔可以很快的讀/寫入一組數列

```c
#include <stdio.h>
#define N 10
void main(void)
{
	int data[N];	
	int get[N];
	int i;
	FILE *file=fopen("data.bin","wb"); //開啟檔案來寫
	for ( i=0; i < N; i++ )
	{
		data[i]=i;
	}
	fwrite(data,sizeof(int),N,file);
	// fwrite可以一次把數個bytes的資料寫入檔案
	fclose(file);

	file=fopen("data.bin","rb"); // 開啟檔案來讀
	fread(get, sizeof(int),N,file);
	// fread可以一次把數個bytes的資料讀入記憶體中
	for ( i=0; i < N; i++ )
	{
		printf("%d ",get[i]);
	}
	printf("\n");
	fclose(file);
}
```

### 二進位檔 struct -範例

struct經當會使用在檔案讀寫中. 舉例, 假如現在訂好一個檔案結構用來記錄全班同學的基本資料, 每個同學需要記錄的資料有姓名, 身高, 體重, 年齡. 姓名在20字母以下, 身高體重都用float來記, 年齡用整數來記錄.

```c
#include <stdio.h>

struct Person 
{
	char name[20];
	float height;
	float weight;
	int   age;
};

void main(void)
{
	struct Person p={"Peter", 180.0f, 80.0f, 25};
	FILE *file=fopen("people.bin","wb");
	fwrite(&p, sizeof(struct Person), 1, file);

	printf("%s %f  %f  %d",p.name ,p.height ,p.weight ,p.age);
	fclose(file);
}
```

### 二進位檔 不使用 struct -範例

```c
#include < stdio.h >

void main(void)
{
	char name[20];
	float height=180.0f;
	float weight=80.0f;
	int   age=25;

	FILE *file=fopen("people.bin","wb");
	fwrite(name, sizeof(char), 20, file);
	fwrite(&height, sizeof(float), 1, file);
	fwrite(&weight, sizeof(float), 1, file);
	fwrite(&age, sizeof(int), 1, file);
	fclose(file);
}
```

## C++ 語言

### 檔案的輸出入

負責檔案輸出入的類別有 ifstream (檔案輸入)、ofstream (檔案輸出) 以及 fstream (檔案輸出入)。請注意所謂檔案的 IO (輸入 \ 輸出)，是以程 式的角度而言，因此，檔案開啟為輸入狀態時，表示程式將由檔案讀入資(in)，而 不是指將資料輸入檔案。檔案開啟為輸出狀態時，表示程式將輸出資料到檔案。為了避免混淆，我們將資料由檔案輸入到程式稱之為**讀取**，將資料由檔案輸出到程式稱之為**寫入**。

![](/files/-MXQ1TdctH_jzk_sbhCa)

### fstream類別的使用

fstream是一個由C++提供的類別，可以用 於將資料寫入檔案，或讀取檔案資料。欲使用fstream類別執行檔案的 IO時，必須先 宣告一個 fstream類別物件。

```cpp
fstream file ; //宣 告一個fstream物件
```

然後利用fstream所提供的open成員函數，開啟一個檔案。傳入open函數的參 數有兩個，分別為 ：欲開啟檔案名稱 、開啟該檔案的模式參數

```cpp
file.open(“Reader.txt”,ios::in) ;    //在讀取模式下開啟Reader.txt檔
```

若傳入的參數超過兩個可用 | 分開

```cpp
file.open(“Reader.txt”,ios::in | :ios::binary) ;    
//在IO以及二進位制模式下開啟Reader.txt檔
```

![](/files/-MXQ1_r8SQ8vyxCLgb6C)

### 常用的檔案處理函數：

| 函數              | 說明                                      |
| --------------- | --------------------------------------- |
| open(str,mode)  | 以mode模式開啟名為str的檔案                       |
| close( )        | 關閉檔案                                    |
| is\_open( )     | 檢查檔案是否為開啟狀態， 若是則傳回true，否則傳回flase        |
| write(str,size) | 將str陣列中size個字元寫入到檔案中                    |
| read(str,size)  | 從檔案中讀取資料至檔案結 尾為止設定給str陣列，但至多不超過size個 字元 |

### 寫入檔案(1)- 範例

```cpp
#include <iostream>
#include <fstream> 
#define size 10

using namespace std;
int main()
{
        std::fstream file;                              //fstream物件
        char str[size] = "測試中", str1[size];          //宣告 char
        
        file.open("Reader.txt", ios::out | ios::trunc); //開啟檔案為輸出狀態，若檔案已存在則清除檔案內容重新寫入
        file.write(str, size);                          //將str寫入檔案
        file.close();                                   //關閉檔案
        
        file.open("Reader.txt", ios::in);               //開啟檔案為輸出狀態
        file.read(str1, size);                          //從檔案讀取內容給str1
        file.close();                                   //關閉檔案

        std::cout << "Reading data from file...\n" << str1 << std::endl;
        system("start Reader.txt");
        return 0;
}
```

### 寫入檔案 將資料輸岀至檔案(2)- 範例

```cpp
#include <iostream>
#include <fstream> 
#include <stdlib.h>
#define size 10

using namespace std;
int main()
{
        fstream file;
        char str[5] = {"Mary","John","Judy","Joe"};  //宣告字串指標陣列
        int id[5] = {100,200,300,400};

        file.open("Reader.txt", ios::out);      //開啟檔案

        if(!file)     //檢查檔案是否成功開啟
        {
                cerr << "Can't open file!\n";
                exit(1);     //在不正常情形下，中斷程式的執行
        }

        for(int i = 0; i < 4; i++){ //將資料輸出至檔案
            file << id[i] << " " << str[i] << "\n";
        }      
        return 0;
}
```

### 寫入檔案 由檔案讀取資料(3)- 範例

```cpp
#include <iostream>
#include <fstream> 
#include <stdlib.h>
#define size 10

using namespace std;
int main()
{
        fstream file;
        char str[8];
        int id;
        
        file.open("Reader.txt", ios::in);        //將檔案開啟為輸入狀態
        if(!file)     //檢查檔案是否成功開啟
        {
                cerr << "Can't open file!\n";
                exit(1);     //在不正常情形下，中斷程式的執行
        }

        cout << setw(4) << setiosflags(ios::right) << "ID"
                        << setw(8) << setiosflags(ios::right) << "Name" << endl;
        cout << "-------------------\n";
        while(file >> id >> str)     //讀取記錄，若讀取至檔案結尾則傳回0
                        cout << setw(4) << setiosflags(ios::right) << id
                             << setw(8) << setiosflags(ios::right) << str << endl;
        return 0;
}
```

## 循序檔的使用

### 類似DOS-範例

寫作一個類似dos指令type的讀檔程式

```c
#include <stdio.h>
#include <stdlib.h>
void main(int argc, char *argv[])
{
	FILE *input;
	if ( argc < 2 )
	{
		printf("missing argument\n");
		exit(0);
	}
	input=fopen(argv[1],"rt");
	if ( input==NULL )
	{
		printf("open %s fail!\n",argv[1]);
		exit(0);
	}
	while( !feof(input) )
	{
		putchar( fgetc(input) );
	}
}
```

## 隨機讀取檔的使用

### 類似copy

寫作一個類似copy的程式

```c
#include <stdio.h>
#include <stdlib.h>
void main(int argc, char *argv[])
{
	int filelen;
	FILE *input,*output;
	if ( argc < 3 )
	{
		printf("Usage:%s source dest\n",argv[0]);
		exit(0);
	}
	// 開啟檔案
	if( (input=fopen(argv[1],"rb"))==NULL )
	{
		printf("open %s fail!\n",argv[2]);
		exit(0);
	}
	if( (output=fopen(argv[2],"wb"))==NULL )
	{
		printf("open %s fail!\n",argv[3]);
		exit(0);
	}
	fseek( input, 0, SEEK_END);//先寫位置移到檔尾
	filelen = ftell( input );// 再傳回檔案目前的讀寫位置
	fseek( input, 0, SEEK_SET);
	unsigned char *data=new unsigned char[filelen]; // 配置一塊可以讀整個檔案的空間
	fread( data, 1, filelen, input ); // 把整個檔案讀進記憶體
	fwrite( data, 1, filelen, output ); // 把整個檔案寫到另一個檔案中
	fclose( input );
	fclose( output );
}
```

{% tabs %}
{% tab title="fseek" %}
fseek是隨機讀取用的函式。

fseek( FILE \*, location, from\_where );\
from\_where可以代入
{% endtab %}

{% tab title="SEEK\_SET" %}
SEEK\_SET把讀寫位置設定成從檔案開頭開始向後算的第location個位置.(所以location要>=0才有意義)
{% endtab %}

{% tab title="SEEK\_END" %}
SEEK\_END把讀寫位置設定成從檔案結尾開始向後算的第location個位置.(所以location要<=0才有意義)
{% endtab %}

{% tab title="SEEK\_CUR" %}
SEEK\_CUR把讀寫位置設定成從檔案目前讀取位置開始向後算的第location個位置.(所以location0向後移)
{% endtab %}

{% tab title="ftell" %}
ftell會傳回目前檔案的讀寫位置. 所以, 下面的動作可以得知檔案的長度.
{% endtab %}
{% endtabs %}

寫一程式讓使用者查尋學生成績

```c
#include <iostream.h>
#include <stdio.h>

void main(void)
{
	int num;
	int data;
	FILE *file=fopen("points.bin","rb");
	do
	{
		printf("num=");
		scanf("%d",&num);
		if ( num>=50 || num<0 )
			break;
		fseek(file, num*sizeof(int), SEEK_SET);
		fread(&data, sizeof(int), 1, file);
		printf("Student(%d)=%d\n",num,data);
	} while(1);
	fclose(file);
}
```
