File IO

img

o是output簡寫,輸出

i是input簡寫,輸入

f是file文字檔

上圖是各個輸入串流與輸出串流繼承狀況,本頁著重在fstream(讀取與寫入文字檔)與ifstream(讀取文字檔)與ofstream(寫入文字檔)。

寫入文字檔

步驟有4步

include

#include <fstream>

建立寫入文字檔的物件

寫入文字檔用ofstream(output file stream)。

1
  ofstream fout;

打開文字檔

1
fout.open(file_name, ios::trunc);
  • 參數1是檔名
  • 參數2是開啟文字檔的模式
    • 不填,使用預設ios::out
    • ios::trunc
    • ios:out
    • ios::app

ios::trunc與ios:out與預設不代入第2個參數,覆蓋(清除)原本文字檔內容

ios::app是append附加在文字檔內容後面

用ofstreamt物件打開文字檔

ofstream物件本身也可以打開文字檔,使用以下語法,就不需要使用fout.open()打開,可以取代fout.open()

1
  ofstream fout(file_name, ios::app);
  • 參數1是檔名
  • 參數2是開啟文字檔的模式
    • 不填,使用預設ios::out
    • ios::trunc
    • ios:out
    • ios::app

判斷是否打開成功

使用is_open()函式判斷是否有下列問題,若有以下問題會傳回true。

  • 目錄不存在
  • 硬碟容量不足
  • 權限不夠
1
2
3
4
  if (fout.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }

寫入文字檔

寫入(用法與cout一樣,cout«是輸出到螢幕,fout«是輸出到文字檔)

1
fout << "file 1 test test \n";

關閉文字檔

1
fout.close();

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <fstream>
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test.txt";
  // 1.建立寫入檔案的物件(輸出串流)
  ofstream fout;
  // 2.打開檔案,若無檔案會建立新
  // ios::trunc與ios:out與預設不代入第2個參數,覆蓋(清除)原本檔案內容
  // ios::app是append附加在檔案內容後面
  fout.open(file_name, ios::trunc);
  if (fout.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  // 3.寫入(用法與cout一樣,cout<<是輸出到螢幕,fout<<是輸出到檔案)
  fout << "file 1 test test\n";
  fout << "file 2 test test\n";
  fout << "file 3 test test\n";
  // 4.關閉檔案
  fout.close();
  return 0;
}

讀取文字檔

include

#include <fstream>

建立讀取文字檔的物件

讀取文字檔用ifstream(input file stream)。

1
  ifstream fin;

打開文字檔

1
fin.open(file_name, ios::in);
  • 參數1是檔名
  • 參數2是開啟文字檔的模式
    • 不填,使用預設ios::in
    • ios::in

用ifstream物件打開文字檔

ifstream物件本身也可以打開文字檔,使用以下語法,就不需要使用fin.open()打開,可以取代fin.open()

1
  ofstream fin(file_name, ios::in);
  • 參數1是檔名
  • 參數2是開啟文字檔的模式
    • 不填,使用預設ios::in
    • ios::in

判斷是否打開成功

使用is_open()函式判斷是否有下列問題,若有以下問題會傳回true。

  • 文字檔不存在
  • 硬碟容量不足
  • 權限不夠
1
2
3
4
  if (fin.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }

讀取一列文字檔

1
2
3
4
  // 用於存放讀取的內容
  string buffer;
  // 讀取一列字串,存放到buffer中
  getline(fin, buffer);

while讀取整個文字檔

前一個例子只能讀取一列,使用while()讀取整個文字檔,若讀不到文字,就會傳回nullptr

1
2
3
4
5
6
  // 用於存放讀取的內容
  string buffer;
  // 讀取一列字串,存放到buffer中
  while (getline(fin, buffer)) {
    cout << buffer << endl;
  }
file 1 test test 
file 2 test test 
file 3 test test 
file 1 test test 
file 2 test test 
file 3 test test 

關閉文字檔

1
fin.close();

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <fstream>
#include <string>  // getline()函式需要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test.txt";
  ifstream fin(file_name, ios::in);
  if (fin.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  // 用於存放讀取的內容
  string buffer;
  // 讀取一列字串,存放到buffer中
  while (getline(fin, buffer)) {
    cout << buffer << endl;
  }
  // 關閉檔案
  fin.close();
  return 0;
}

寫入Binary file

Binary file中文翻譯可為二進位檔案,它跟文字檔不同

文字檔

string str = “123”;

大小3byte

字元 '1' '2' '3'
ascii 49 50 51

記憶體實際存放的是二進位資料如下:

二進位 00110001 00110010 00110011

文字檔以字串一列一列組成,讀取也以一列一列的方式讀取。

Binary file

int i = 12345678;

int大小4byte,記憶體實際存放的是二進位資料如下:

二進位 00000000 10111100 01100001 01001110

二進位檔案可以存放陣列、結構、類別…什麼類型都可以,圖片檔、mp3檔、mp4這些都是Binary file。

打開Binary file

1
  ofstream fout(file_name, ios::binary);
  • 參數1是檔名
  • 參數2是ios::binary打開Binary file
1
  ofstream fout(file_name, ios::app | ios::binary);

ios::app | ios::binary意思是,寫入檔案從檔案最後開始寫。

1
  ofstream fout(file_name, ios::trunc | ios::binary);

ios::trunc | ios::binary意思是,寫入檔案用覆蓋原本檔案的方式寫入。

建立內容格式

假設要寫入類別,注意,不能用string,string 類型的資料無法直接用 write 和 read 操作進行正確的二進制序列化和反序列化。

1
2
3
4
5
6
7
8
  class Student{
   public:
    char name[100];
    int age;
  };
  Student s1;
  strcpy(s1.name, "Mary");
  s1.age = 15;

寫入write

1
2
3
4
  // 使用write(位址, size)寫入
  // 參數1:物件位址,需手動轉型成const char*
  // 參數2:物件大小
  fout.write((const char*)&s1, sizeof(s1));

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <fstream>
#include <cstring>  // strcpy()要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test1.exe";
  // 1.建立寫入檔案的物件(輸出串流)
  ofstream fout(file_name, ios::trunc | ios::binary);
  if (fout.is_open() == false) {
    cout << "開啟檔案失敗" << endl;
    return 0;
  }
  class Student{
   public:
    char name[100];
    int age;
  };
  Student s1;
  strcpy(s1.name, "Mary");
  s1.age = 15;
  // 使用write(位址, size)寫入
  // 參數1:物件位址,需手動轉型成const char*
  // 參數2:物件大小
  fout.write((const char*)&s1, sizeof(s1));
  // 4.關閉檔案
  fout.close();
  return 0;
}

讀取Binary file

打開Binary file

1
2
  string file_name = "/home/cici/test/app/test1.exe";
  ifstream fin(file_name, ios::in | ios::binary);

使用ios::in | ios::binary打開Binary file

讀取read

fin.read((char*)位址, 大小);

將物件位址手動轉型成(char*)

1
2
3
4
5
6
7
8
9
  // 怎麼寫入怎麼讀出來
  class Student{
    public:
     char name[100];
     int age;
  };
  Student s1;
  fin.read((char*)&s1, sizeof(s1));
  cout << "name = " << s1.name << ", age = " << s1.age << endl;

以上範例是只有寫入一個Student物件,若寫N個Student物件,可用while讀出來。

1
2
3
while (fin.read((char*)&s1, sizeof(s1))) {
	cout << "name = " << s1.name << ", age = " << s1.age << endl;
}

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <fstream>
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test1.exe";
  ifstream fin(file_name, ios::in | ios::binary);
  if (fin.is_open() == false) {
    cout << "開啟檔案失敗" << endl;
    return 0;
  }
  // 怎麼寫入怎麼讀出來
  class Student{
    public:
     char name[100];
     int age;
  };
  Student s1;
  fin.read((char*)&s1, sizeof(s1));
  cout << "name = " << s1.name << ", age = " << s1.age << endl;
  // 關閉檔案
  fin.close();
  return 0;
}

檔案的游標位置

用記事本或sublime文件編輯軟體打開文字檔,一開始閃爍的游標會停在第0格的位置,將滑鼠游標移動到其它文字或其它列,在這邊所說的”游標”就是會回應使用者輸入的位置,而這個”位置”“就是這小節的重點。

語法

取得游標位置函式,以下功能都相同

  • ofstream.tellp()
  • ifstream.tellg()
  • fstream.tellp()
  • fstream.tellg()

文字檔寫入與取得游標位置

將先前寫入文字檔的範例,增加取得游標位置函式,每換行一次就印出目前的游標位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <fstream>
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test.txt";
  // 1.建立寫入檔案的物件(輸出串流)
  ofstream fout;
  // 2.打開檔案,若無檔案會建立新
  // ios::trunc與ios:out與預設不代入第2個參數,覆蓋(清除)原本檔案內容
  // ios::app是append附加在檔案內容後面
  fout.open(file_name, ios::trunc);
  if (fout.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  // 3.寫入(用法與cout一樣,cout<<是輸出到螢幕,fout<<是輸出到檔案)
  cout << "游標位置 = " << fout.tellp() << endl;
  fout << "file 1 test test \n";
  cout << "游標位置 = " << fout.tellp() << endl;
  fout << "file 2 test test \n";
  cout << "游標位置 = " << fout.tellp() << endl;
  fout << "file 3 test test \n";
  cout << "游標位置 = " << fout.tellp() << endl;
  // 4.關閉檔案
  fout.close();
  return 0;
}
游標位置 = 0
游標位置 = 18
游標位置 = 36
游標位置 = 54

二進位檔案讀取與游標位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <fstream>
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test1.exe";
  ifstream fin(file_name, ios::in | ios::binary);
  if (fin.is_open() == false) {
    cout << "開啟檔案失敗" << endl;
    return 0;
  }
  // 怎麼寫入怎麼讀出來
  class Student{
    public:
     char name[100];
     int age;
  };
  Student s1;
  cout << "游標位置 = " << fin.tellg() << endl;
  fin.read((char*)&s1, sizeof(s1));
  cout << "游標位置 = " << fin.tellg() << endl;
  cout << "name = " << s1.name << ", age = " << s1.age << endl;
  // 關閉檔案
  fin.close();
  return 0;
}
游標位置 = 0
游標位置 = 104
name = Mary, age = 15

隨機讀取與隨機寫入

移動游標位置

移動游標位置函式,以下功能都相同

std::istream & seekg(std::streampos _Pos);

  • 參數_Pos為游標位置
  • 傳回游標位置指標

以下都是移動游標位置的語法:

  • ofstream.seekp(_Pos)
  • ifstream.seekg(_Pos)
  • fstream.seekp(_Pos)
  • fstream.seekg(_Pos)

位置參數

  • ios::beg,代表移動到檔案開始的位置
  • ios::end,代表移動到檔案最末尾的位置
  • ios::cur,代表目前游標在檔案中的位置

以上的值,都可作為上述_Pos的參數

1
2
3
4
// 移動到檔案最末尾的位置
fout.seekp(ios::end);
// 移動到100
fout.seekp(100);

移動游標位置2

1
2
3
4
5
6
7
8
9
std::istream & seekg(std::streamoff _Off,std::ios::seekdir _Way);
// 移動到檔案開始的位置,再往右邊移動6個字元
fin.seekg(6, ios::beg);
// 移動到游標位置,再往左移動5字元
fin.seekg(-5, ios::cur);
// 移動到游標位置,再往右移動8字元
fin.seekg( 8, ios::cur);
// 移動到檔案最末尾的位置,再往左移動10字元
fin.seekg(-10, ios::end);

移動游標程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <fstream>
#include <string>  // getline()函式需要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test.txt";
  ifstream fin(file_name, ios::in);
  if (fin.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  // 用於存放讀取的內容
  string buffer;
  // 移動到檔案開始的位置,再往右邊移動6個字元
  fin.seekg(6, ios::beg);
  // 讀取一列字串,存放到buffer中
  while (getline(fin, buffer)) {
    cout << buffer << endl;
  }
  // 關閉檔案
  fin.close();
  return 0;
}

原本文字檔如下:

file 1 test test 
file 2 test test 
file 3 test test

執行後結果,確實”file 1”共6個字元都沒有被顯示出來。

 test test 
file 2 test test 
file 3 test test 

緩衝區

暫時置放輸出或輸入資料的記憶體區域,也就是說寫入文件沒有即時寫入檔案,而是先放在記憶體區域,等待記憶體區域滿了,才會寫入檔案。

img

以下程式碼可能可以證明緩衝區的存在。

開二個linux終端機,一個編譯並執行以下程式碼,另一個終端機輸入tail -f test1.txt

注意!fout « 結尾不能是endl因為endl本身自帶flush的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <fstream>
#include <unistd.h>  // usleep()要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test1.txt";
  // 1.建立寫入檔案的物件(輸出串流)
  ofstream fout;
  // 2.打開檔案,若無檔案會建立新
  // ios::trunc與ios:out與預設不代入第2個參數,覆蓋(清除)原本檔案內容
  // ios::app是append附加在檔案內容後面
  fout.open(file_name, ios::out);
  if (fout.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  for (int i = 0; i < 1000; i++) {
    // 寫入
    fout << "第 i = " << i << "次,abcdefghijklm.kljoppo kllkkjlkjjljjkldfjfdladfaklffadjkfladfjadklfalfakdfadlfjadkljfdakldfajfkfjkal \n";
    // usleep(微秒) 暫時使程式停止執行
    // 1秒 = 1,000,000 微秒
    // 0.1秒 = 100,000
    usleep(100000);
  }
  // 關閉檔案
  fout.close();
  return 0;
}

若執行到999次之間有停頓住,代表緩衝區還沒滿。

flush

fout.flush()將緩衝區的資料立即寫入檔案,終端機使用tail -f test1.txt可以追蹤查看檔案是一列列顯示,而不會停頓一下又冒出一批資料出來。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <fstream>
#include <unistd.h>  // usleep()要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test1.txt";
  // 1.建立寫入檔案的物件(輸出串流)
  ofstream fout;
  // 2.打開檔案,若無檔案會建立新
  // ios::trunc與ios:out與預設不代入第2個參數,覆蓋(清除)原本檔案內容
  // ios::app是append附加在檔案內容後面
  fout.open(file_name, ios::out);
  if (fout.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  for (int i = 0; i < 1000; i++) {
    // 寫入
    fout << "第 i = " << i << "次,abcdefghijklm.kljoppo kllkkjlkjjljjkldfjfdladfaklffadjkfladfjadklfalfakdfadlfjadkljfdakldfajfkfjkal \n";
    fout.flush();
    // usleep(微秒) 暫時使程式停止執行
    // 1秒 = 1,000,000 微秒
    // 0.1秒 = 100,000
    usleep(100000);
  }
  // 關閉檔案
  fout.close();
  return 0;
}

endl

除了斷行之外,還會將緩衝區的資料立即寫入檔案。

1
fout << "第 i = " << i << "次,abcdefghi" << endl;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <fstream>
#include <unistd.h>  // usleep()要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test1.txt";
  // 1.建立寫入檔案的物件(輸出串流)
  ofstream fout;
  // 2.打開檔案,若無檔案會建立新
  // ios::trunc與ios:out與預設不代入第2個參數,覆蓋(清除)原本檔案內容
  // ios::app是append附加在檔案內容後面
  fout.open(file_name, ios::out);
  if (fout.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  for (int i = 0; i < 1000; i++) {
    // 寫入
    fout << "第 i = " << i << "次,abcdefghi" << endl;
    // usleep(微秒) 暫時使程式停止執行
    // 1秒 = 1,000,000 微秒
    // 0.1秒 = 100,000
    usleep(100000);
  }
  // 關閉檔案
  fout.close();
  return 0;
}

unibuf

將緩衝區設置為有資料立即寫入檔案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <fstream>
#include <unistd.h>  // usleep()要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test1.txt";
  // 1.建立寫入檔案的物件(輸出串流)
  ofstream fout;
  // 2.打開檔案,若無檔案會建立新
  // ios::trunc與ios:out與預設不代入第2個參數,覆蓋(清除)原本檔案內容
  // ios::app是append附加在檔案內容後面
  fout.open(file_name, ios::out);
  fout << unitbuf;
  if (fout.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  for (int i = 0; i < 1000; i++) {
    // 寫入
    fout << "第 i = " << i << "次 \n";
    // usleep(微秒) 暫時使程式停止執行
    // 1秒 = 1,000,000 微秒
    // 0.1秒 = 100,000
    usleep(100000);
  }
  // 關閉檔案
  fout.close();
  return 0;
}

判斷輸入串流錯誤

eof()

只有輸入串流有eofbit,輸出串流沒有。

當inputstream讀到檔案末尾會設定eofbit變數,eof()函式會檢查是否設定eofbit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <fstream>
#include <string>  // getline()函式需要用到
using namespace std;
int main() {
  // linux檔案位置
  string file_name = "/home/cici/test/app/test.txt";
  ifstream fin(file_name, ios::in);
  if (fin.is_open() == false) {
    cout << "開啟文字檔失敗" << endl;
    return 0;
  }
  // 用於存放讀取的內容
  string buffer;
  while (true) {
    fin >> buffer;
    cout << buffer << endl;
    if (fin.eof() == true) break;
  }
  // 關閉檔案
  fin.close();
  return 0;
}

以上的程式碼可被取代為

1
2
3
 while (fin >> buffer) {
    cout << buffer << endl;
 }

bad()

bad()函式會檢查badbit是否設定,當硬碟空間不足,或系統有錯誤,就會被設定。

fail()

fail()函式會檢查failbit是否設定,當檔案到了末尾,或程式有bug,就會被設定。

good()

eofbit,badbit,failbit都是0的時候good()函式就會傳回true

results matching ""

    No results matching ""