# 十、檔案處理

檔案是眾多的字元所組成的集合體，部分語言會將它視為一種資料串流(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);
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://information9527.gitbook.io/cbook/10.-dang-an-chu-li.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
