1. 程式人生 > >c++筆記(10) 檔案輸入輸出

c++筆記(10) 檔案輸入輸出

c++ 定義了ifstream, ofstream, fstream類用於檔案處理和操作檔案,這些類定義在標頭檔案<fstream>中。

c++使用“流”來描述資料流動,資料流向程式,則為input stream(輸入流),反之為output stream輸出流。

1.文字檔案的讀寫操作。

寫入檔案

#include <iostream>
#include <fstream> 
using namespace std;

int main()
{
    ofstream output;
	output.open("score.txt");   // open a file
	
	output << "zhangjun" << " " << 'S' << " " << 90 << endl;
	output << "hehe" << " " << 'L' << " " << 88 << endl;
	
	output.close();  // close the file
	cout << "Done" << endl; 	
	return 0;
}

如果檔案已經存在,再次開啟的話,檔案的內容會被清除。

讀取檔案:

#include <iostream>
#include <fstream> 
using namespace std;

int main()
{
    ifstream input;
    input.open("score.txt");
    
    string name;
    char med;
    int score;
    
    input >> name >> med >> score;
    cout << "name is " << name << " char is " << med << " score is " << score << endl;
    
    input >> name >> med >> score;
    cout << "name is " << name << " char is " << med << " score is " << score << endl;
	input.close();
	return 0;
}

關於空格:

讀取的資料中是以空格作為分割。如果寫入的也有空格,則會導致獨處的資料有問題。

2. 檢測檔案是否存在

fail()函式用於檢測檔案是否存在!開啟檔案後可以用fail()函式判斷

對上面的程式碼新增檔案檢測是否存在:  

#include <iostream>
#include <fstream> 
using namespace std;

int main()
{
    ifstream input;
    input.open("score.txt");
    
    // if the file exist
    if(input.fail())
    {
    	cout << "the file not exist";
    	return 0;
    }
    
    string name;
    char med;
    int score;
    
    input >> name >> med >> score;
    cout << "name is " << name << " char is " << med << " score is " << score << endl;
    
    input >> name >> med >> score;
    cout << "name is " << name << " char is " << med << " score is " << score << endl;
	input.close();
	return 0;
}

3.檢測檔案結束

在不知道檔案有多少行又想讀取讀取全部資料的話,需要檢測檔案的結束位置

(1).eof()函式

   ifstream input;
   input.open("filename");
    while(!input.eof())
    {
    	// read data from file;
    	input >> number;
    	if(input.eof()) break;    // 每讀完一次資料,立即檢測一次 
    	//  other operation .....
    }

(2)通過input>>返回的值判斷

        while(input>>number)
	{
		// read data from file;
	} 

input>>number讀入資料返回的是一個物件,否則返回的是NULL,據此可以判斷檔案末尾

注: c++中輸入輸出流的建構函式引數為c字串,所以如果檔名問string,要轉換c_str()

string filename;
cin >> filename;
input.open(filename.c_str());

4.函式getline, get, put

流提取運算子讀取資料,只能以空格作為分隔符,如果讀取的資料中含有空格,則應該怎麼讀取

getline() : <iostream> , 函式引數getline(ifstream, int/string data, delimitChar)

get()    // 只能讀寫單個字元

put()     // 只能讀寫單個字元

例如讀取: New  york#New  Mexico# India

 string city
 while(!input.eof())
{
     getline(input, city, '#');   // 注意delimitChar! 是Char!
     cout << city << endl;
}

5.fstream和檔案開啟模式

fstream建立既能寫入又能讀出的檔案

開啟檔案的模式:

ios::in     讀取模式開啟檔案

ios::out    寫入檔案模式

ios::app    追加模式

ios::trunc   如果檔案已經存在,丟棄檔案內容

ios::binary    二進位制輸入輸出

例項:建立一個檔案,寫入資料並關閉,再次開啟追加資料

#include <iostream>
#include <fstream> 
using namespace std;

int main()
{
        fstream inout;
	// creat a file
	inout.open("city.txt", ios::out);   //開啟檔案的模式
	// write cities
	inout << "Dalloa" << " " << "Hoston" << " " << "Anlantas" << " ";
	inout.close();
	
	// open file 
	inout.open("city.txt", ios::out | ios::app);
	// write a line
	inout << "Swadas" << " " << "Austin" << " " << "Chicago" ;
	inout.close();
	 
    // read the file
	inout.open("city.txt", ios::in);
	string city;
	while(!inout.eof())
	{
		inout >> city;
		cout << city << endl;
	} 
	inout.close();
	
	return 0;
}

檢測流狀態:

c++以提供的檢測流狀態的函式: eof() 

fail()   檔案開啟是否成功

bad()

good()

clear()

#include <iostream>
#include <fstream> 
using namespace std;

void showState(fstream&);
int main()
{
        fstream inout;
	//creat an output file
	inout.open("temp.txt", ios::out);
	inout << "Dallas";
	cout << "Normal operation." << endl;
	inout.close();
	showState(inout);
	
	inout.open("temp.txt", ios::in);
	string city;
	inout >> city;
	cout << "End of file" << endl;
	showState(inout);
	inout.close();

	return 0;
}

void showState(fstream& a)
{
	cout << "stream state: " << endl;
	cout << "eof(): " << a.eof() << endl;
	cout << "fail(): " << a.fail() << endl;
	cout << "bad(): " << a.bad() << endl;
	cout << "good: " << a.good() << endl;
}

6.二進位制輸入輸出

二進位制檔案輸入輸出開啟方式

檔案可以分為文字檔案和二進位制檔案兩類

文字檔案:能被文字編輯器處理的檔案稱為文字檔案。 在c++中,副檔名為.txt

二進位制檔案:非文字檔案都稱為二進位制檔案。只能由計算機程式讀取處理,在c++中副檔名為.dat, 處理二進位制檔案效率更高

為了讀寫二進位制檔案,必須對流物件使用read(), write()函式

1.write()函式

streamObject.write(const char*, int size): char*: 寫入的字元陣列       size:寫入的位元組數

#include <iostream>
#include <fstream> 
#include <string> 
using namespace std;


int main()
{
        fstream binaryio;     
	binaryio.open("city.dat", ios::out | ios::binary);  // 二進位制寫入, 二進位制檔案 .dat 
	string s("Atlant");
	binaryio.write(s.c_str(), s.size());    // 轉化為c字串
	binaryio.close();
	cout << "Done" << endl;   
	return 0;
}

非字元資料的寫入,使用reinterpret_cast運算子實現將一個指標型別轉化為與其不相關的指標型別,它只是進行指標值的二進位制複製,並不改變指標指向的資料。

reinterpret_cast<datatype*>(address)       允許將任何指標轉換為任何其他指標型別

#include <iostream>
#include <fstream> 
#include <string> 
using namespace std;


int main()
{
    fstream binaryio;     
	binaryio.open("temp.dat", ios::out | ios::binary);  // 二進位制寫入, 二進位制檔案 .dat 
	int value = 199;
	binaryio.write(reinterpret_cast<char*>(&value), sizeof(value));  
	binaryio.close();
	cout << "Done" << endl;   
	return 0;
}

2 read()函式

streamObject.read(char* address, int size)     // size指定可以讀取的最大位元組數, gcount() 獲取實際讀取位元組數

#include <iostream>
#include <fstream> 
#include <string> 
using namespace std;


int main()
{
    fstream binaryio;     
	binaryio.open("city.dat", ios::in | ios::binary);  // 二進位制讀取, 二進位制檔案 .dat 
	char s[10]; 
	binaryio.read(s, 10);
	s[binaryio.gcount()] = '\0';   // gcount()獲取實際讀取的位元組數  // 設定字串的末尾'\0' 
	cout << s << endl;
	binaryio.close();
	
	return 0;
}

讀取整數; 檔案temp.dat中儲存的整數轉變為二進位制位元組,現在讀取這些位元組,轉化為整數

#include <iostream>
#include <fstream> 
#include <string> 
using namespace std;


int main()
{
        fstream binaryio;     
	binaryio.open("temp.dat", ios::in | ios::binary);  // 二進位制讀取, 二進位制檔案 .dat 
	int value; 
	binaryio.read(reinterpret_cast<char*>(&value), sizeof(value));
        cout << value << endl;
	binaryio.close();
	
	return 0;
}

3. 二進位制陣列IO

可以使用reinterpret_cast將任意型別的資料轉變為二進位制位元組,也可以將二進位制位元組轉化為任意型別的資料

如:將double陣列寫入二進位制檔案,然後再讀取出來

#include <iostream>
#include <fstream> 
#include <string> 
using namespace std;


int main()
{
        const int SIZE = 5;
	fstream binaryio;
	
	// 想陣列中寫入資料
	binaryio.open("array.dat", ios::out | ios::binary);
	double array[SIZE] = {2.3, 4, 3.8, 3.0, 11.11};
	binaryio.write(reinterpret_cast<char*>(&array), sizeof(array));
	binaryio.close();
	
	// 讀取檔案中的資料
	binaryio.open("array.dat", ios::in | ios::binary);
	double result[SIZE];
	binaryio.read(reinterpret_cast<char*>(&result), sizeof(result));
	binaryio.close();
	// display array
	for(int i=0; i<SIZE; i++)
	{
		cout << result[i] << " ";
	} 
	cout << endl;
		
	return 0;
}

二進位制物件IO

如何向二進位制檔案寫入物件,以及從二進位制檔案讀出

定義一個Student類, 將類寫入檔案和從檔案中讀出

程式碼:

student.h

#include <iostream>
#include <string>

using namespace std;

#ifndef STUDENT_H
#define STUDENT_H
class Student
{
	private:
	char firstName[25];
	char mid;    // 中間名
	char lastName[25];
	int score;
	
	public:
	Student();   // 無參建構函式
	Student(const string& firstName, const char mid, const string& lastName, int score);  // 有參建構函式 
	// 定義訪問器和更改器
	void setFirstName(const string& firstName);
	void setMid(const char mid);
	void setLastName(const string& lastName);
	void setScore(int score);
	
	string getFirstName() const;
	char getMid() const;
	string getLastName() const;
	int getScore() const; 
};

#endif



student.cpp

#include <iostream>
#include <string>
#include <cstring>
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;


Student::Student()
{
	// all the data remain default value;
}
Student::Student(const string& firstName, const char mid, const string& lastName, int score)
{
	setFirstName(firstName);
	setMid(mid);
	setLastName(lastName); 
	setScore(score);
}

// mutator
void Student::setFirstName(const string& firstName)
{
	strcpy(this->firstName, firstName.c_str());    // string 型別的值複製到char[] 
}

void Student::setMid(const char mid)
{
	this->mid = mid;
}

void Student::setLastName(const string& lastName)
{
	strcpy(this->lastName, lastName.c_str());      // strcpy()函式的引數:,char[], string.c_str()才可以 
}

void Student::setScore(int score)
{
	this->score = score;
}

string Student::getFirstName() const
{
   return string(firstName);      // 將char[]轉換為string	
}

char Student::getMid() const
{
	return mid;
}

string Student::getLastName() const
{
	return string(lastName);     //  
	
}

int Student::getScore() const
{
	return score;
}
// accessor
	

main.cpp

#include <iostream>
#include <fstream> 
#include <string> 
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;

void displayStudent(const Student&);
int main()
{
    Student student1("Jhoon", 'K', "jerry", 90);     
    Student student2("Mary", 'L', "Charlotte", 100);
    Student student3("Koeras", 'B', "Long", 78);
    Student student4("Maksa", 'C', "short", 90);
    
    fstream binaryio;
    binaryio.open("student.dat", ios::out | ios::binary);   // 建立檔案,寫入資料 
    binaryio.write(reinterpret_cast<char*>(&student1), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student2), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student3), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student4), sizeof(Student));
    binaryio.close();
    
    binaryio.open("student.dat", ios::in | ios::binary);   // 這種讀取資料的方法,檔案指標自動下移 
    Student student;
    binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));
    displayStudent(student);
	
    binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));
    displayStudent(student); 
 
    return 0;
}

void  displayStudent(const Student& student)
{
	cout << student.getFirstName() << " " << student.getMid() << " " << student.getLastName() << " " << student.getScore() << endl;
}

注:

student類中lastName和firstName的資料型別為固定字元長度的陣列,char[25],  而不用string, 是因為在reinterpret(char*, size)時。size = sizeof(Student)保證每個學生記錄大小相同,保證正確讀取學生資料,如果採用string則不能保證。

char[] 與string型別之間的轉化:

string(char[])  型別轉化

strcpy(string, char[].c_str())   // 值複製

4. 隨機訪問檔案

使用seekg()函式和seekp()函式移動檔案指標到任意位置。

檔案由位元組序列組成,作業系統中會維護一個檔案指標的特殊標記,指向序列中的某個位置,讀寫操作都是從檔案指標指向的位置處進行。

檔案的訪問分為:

1. 順序訪問檔案        檔案指標位於檔案開始的地方。向後移動

2.隨機訪問檔案        讀取檔案的資料時,如果想跳過前面的資料項,訪問某一項資料,可以採用隨機訪問檔案。‘

c++允許對流物件使用seekp()和seekg()函式,移動檔案的指標到任意的位置

seekp() : seek put  用於輸出流

seekg() : seek get  用於輸入流

seekg(pos): 將指標移動到絕對位置pos

seekg(long a, pos):  pos是相對位置, a是偏移量, 注意偏移量的單位是位元組      pos: :ios:beg, ios::end, ios::cur

tellp:

tellg: 返回檔案指標的當前位置

修改上面的student程式碼

只對main.cpp作如下修改:  

#include <iostream>
#include <fstream> 
#include <string> 
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;

void displayStudent(const Student&);
int main()
{
    Student student1("Jhoon", 'K', "jerry", 90);     
    Student student2("Mary", 'L', "Charlotte", 100);
    Student student3("Koeras", 'B', "Long", 78);
    Student student4("zhuwen", 'F', "short", 90);
    Student student5("xiang", 'F', "short", 90);
    Student student6("gouhao", 'S', "short", 90);
    Student student7("crappple", 'C', "short", 90);
    
	fstream binaryio;
    binaryio.open("student.dat", ios::out | ios::binary);   // 建立檔案,寫入資料 
    binaryio.write(reinterpret_cast<char*>(&student1), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student2), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student3), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student4), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student5), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student6), sizeof(Student));
    binaryio.write(reinterpret_cast<char*>(&student7), sizeof(Student));
    binaryio.close();
    
    binaryio.open("student.dat", ios::in | ios::binary);   // 開啟檔案,指標這時位於檔案起始的位置 
    cout << "current position is " << binaryio.tellg() << endl;   // 當前檔案中指標的位置
	 
	Student student;
	// 讀取第三個資料,跳過前兩個
	binaryio.seekg(2*sizeof(Student));  // 將指標向後移動兩個資料項
	cout << "current position is " << binaryio.tellg() << endl;   // 當前檔案中指標的位置
	 
	binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));   // 讀取的是第三項資料 
	displayStudent(student);
	cout << "current position is " << binaryio.tellg() << endl;   // 當前檔案中指標的位置
	
	// 指標的當前位置
	binaryio.seekg(2*sizeof(Student), ios::cur); // 讀取第七條資料  // 偏移量單位是位元組,所以向後移動兩項資料,偏移量應該是n*sizeof(Student)	 
	cout << "Current position is " << binaryio.tellg() << endl;
	
	binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));
	displayStudent(student); 
    cout << "Current position is " << binaryio.tellg() << endl;
    
	return 0;
}

void  displayStudent(const Student& student)
{
	cout << student.getFirstName() << " " << student.getMid() << " " << student.getLastName() << " " << student.getScore() << endl;
}

執行結果:

分析:

1. 首先將七個student物件依次寫進二進位制檔案

2. 開啟二進位制檔案,此時檔案指標位於檔案的開頭,指向的是第一個資料, position = 0

3. 將檔案指標向後移動兩個資料項     binaryio.seekg(2*sizeof(Student));    position = 112

4.讀取資料項,讀到的是第三個資料項,可以看到讀取完畢後文件指標後移到下一個資料項, position = 168

5.再將檔案指標從當前位置向後移動兩個位置,讀取資料項

6. 更新檔案:

可以使用組合模式開啟檔案。如:按照讀寫模式開啟一個二進位制檔案,對檔案進行更新

對上面的程式做出修改:  

#include <iostream>
#include <fstream> 
#include <string> 
#include "E:\back_up\code\c_plus_code\chapter5\external_file\student..h"
using namespace std;

void displayStudent(const Student&);
int main()
{   
	fstream binaryio;
    binaryio.open("student.dat", ios::out | ios::in | ios::binary);   // 以讀寫模式開啟二進位制檔案 
    
	Student student_new;
	binaryio.seekg(sizeof(Student));   // 檔案指標後移一個位置
	binaryio.read(reinterpret_cast<char*>(&student_new), sizeof(Student));
	displayStudent(student_new);
	
	student_new.setFirstName("SHI");
	student_new.setLastName("gououou");
	student_new.setScore(100);
	
	binaryio.seekg(sizeof(Student));    // 因為讀完資料後文件指標下移,所以需要將檔案指標拉回到原處再寫入修改後的資料,達到檔案更新的目的 
	binaryio.write(reinterpret_cast<char*>(&student_new), sizeof(student_new)); 
    binaryio.close();
    
    binaryio.open("student.dat", ios::in | ios::binary);   // 開啟檔案,指標這時位於檔案起始的位置 
    
	Student student;
	binaryio.seekg(sizeof(Student));  // 讀取修改後的資料  定位 
	//cout << "current position is " << binaryio.tellg() << endl;   // 當前檔案中指標的位置
	binaryio.read(reinterpret_cast<char*>(&student), sizeof(Student));   // 讀取的是第三項資料 
	displayStudent(student);
	//cout << "current position is " << binaryio.tellg() << endl;   // 當前檔案中指標的位置
	binaryio.close(); 
    
	return 0;
}

void  displayStudent(const Student& student)
{
	cout << student.getFirstName() << " " << student.getMid() << " " << student.getLastName() << " " << student.getScore() << endl;
}

需要注意的是對檔案修改時,必須保證指標指向資料的正確性,否則資料沒有修改,卻把別的資料給覆蓋了