1. 概述
C++標準庫提供了 頭文件中的幾個類來進行文件操作,這些類封裝了底層的文件操作,提供了面向對象和類型安全的接口,使得文件讀寫更加便捷和高效。主要的文件流類包括:
std::ifstream
:用于從文件中讀取數據。
std::ofstream
:用于向文件中寫入數據。
std::fstream
:用于同時讀取和寫入文件。
這些文件流類(std::ifstream
、std::ofstream
、std::fstream
)繼承自 std::istream
、std::ostream
和 std::iostream
,這些類本身繼承自 std::ios
,從而提供了豐富的成員函數和操作符來處理文件I/O。
2. 文件流類詳解
2.1 std::ifstream:輸入文件流
std::ifstream
用于從文件中讀取數據。它繼承自 std::istream
,具備所有輸入流的功能,同時添加了文件特有的操作。
#include <fstream>
#include <iostream>
#include <string>int main() {// 創建并打開輸入文件流std::ifstream infile("input.txt"); // "input.txt" 是要讀取的文件名// 檢查文件是否成功打開if (!infile) {std::cerr << "無法打開文件 input.txt" << std::endl;return 1;}// 讀取并輸出文件內容std::string line;while (std::getline(infile, line)) {std::cout << line << std::endl;}// 關閉文件流(可選,因為析構函數會自動關閉)infile.close();return 0;
}
要點:
-
構造函數可以在創建對象時指定文件名和打開模式。
-
檢查文件是否成功打開,以避免后續操作錯誤。
-
使用
std::getline
逐行讀取文件內容。
2.2 std::ofstream:輸出文件流
std::ofstream
用于向文件中寫入數據。它繼承自 std::ostream
,具備所有輸出流的功能,同時添加了文件特有的操作。
#include <fstream>
#include <iostream>
#include <string>int main() {// 創建并打開輸出文件流std::ofstream outfile("output.txt"); // "output.txt" 是要寫入的文件名// 檢查文件是否成功打開if (!outfile) {std::cerr << "無法打開文件 output.txt" << std::endl;return 1;}// 寫入數據到文件outfile << "Hello, World!" << std::endl;outfile << "這是第二行文本。" << std::endl;// 關閉文件流(可選,因為析構函數會自動關閉)outfile.close();return 0;
}
要點:
-
默認情況下,
std::ofstream
以寫入模式打開文件。如果文件不存在,會創建新文件;如果文件存在,會截斷文件內容。 -
使用
<<
操作符向文件中插入數據。
2.3 std::fstream:文件流(讀寫)
std::fstream
用于同時讀取和寫入文件。它繼承自std::iostream
,結合了 std::istream
和 std::ostream 的功能
#include <fstream>
#include <iostream>
#include <string>int main() {// 創建并打開文件流,設置為讀寫模式std::fstream file("file.txt", std::ios::in | std::ios::out | std::ios::app);// 檢查文件是否成功打開if (!file) {std::cerr << "無法打開文件 file.txt" << std::endl;return 1;}// 寫入數據到文件file << "追加一行內容。" << std::endl;// 移動讀指針到文件開頭file.seekg(0, std::ios::beg);// 讀取并輸出文件內容std::string line;while (std::getline(file, line)) {std::cout << line << std::endl;}// 關閉文件流file.close();return 0;
}
要點:
-
通過構造函數的第二個參數指定文件打開模式,可以組合多個模式。
-
適用于需要同時進行讀取和寫入操作的場景。
3. 打開和關閉文件
文件流類提供了多種方式來打開和關閉文件。
可以在創建文件流對象時,通過構造函數直接指定文件名和打開模式。
std::ifstream infile("input.txt"); // 以默認模式(即只讀方式)打開 input.txt
std::ofstream outfile("output.txt", std::ios::out | std::ios::trunc); // 以寫入和截斷模式打開 output.txt
如果在創建對象時沒有指定文件名,可以使用 open()
成員函數在后續打開文件。
#include <fstream>
#include <iostream>int main() {std::ifstream infile; // 創建輸入文件流對象infile.open("input.txt"); // 打開文件if (!infile.is_open()) {std::cerr << "無法打開文件 input.txt" << std::endl;return 1;}// 進行讀取操作...infile.close(); // 關閉文件return 0;
}
使用 close()
成員函數可以顯式關閉文件流。盡管在對象銷毀時,析構函數會自動關閉文件,但在需要提前釋放資源時,顯式調用 close()
是必要的。
infile.close();
outfile.close();
4. 文件打開模式
文件打開模式通過 std::ios::openmode
枚舉類型指定,可以組合使用多個模式。
常用的打開模式:
- std::ios::in
:以讀取模式打開文件。
-
std::ios::out
:以寫入模式打開文件。 -
std::ios::app
:以追加模式打開文件,寫入操作將添加到文件末尾。 -
std::ios::ate
:打開文件后,將文件指針定位到文件末尾。 -
std::ios::trunc
:如果文件已存在,則截斷文件長度為0(默認與 std::ofstream 相關)。 -
std::ios::binary
:以二進制模式打開文件。
// 以讀寫模式打開文件,不截斷現有內容
std::fstream file("data.txt", std::ios::in | std::ios::out);// 以二進制模式寫入數據, 截斷現有文件內容std::ofstream binaryOut("data.bin", std::ios::out | std::ios::binary | std::ios::trunc);
說明:
-
組合使用:通過按位或操作符 | 組合多個打開模式,如
std::ios::in | std::ios::out
表示同時具備讀取和寫入權限。 -
二進制模式:對于非文本文件(如圖片、音頻等),應使用
std::ios::binary
模式,以防止數據在讀取或寫入過程中被轉換。
5. 讀取文件
5.1 使用 >> 操作符讀取單詞
>>
操作符用于從文件中提取數據,自動跳過空白字符(如空格、換行符、制表符)。
假設 words.txt 文件內容如下:
#include <fstream>
#include <iostream>
#include <string>int main() {std::ifstream infile("words.txt");if (!infile) {std::cerr << "無法打開文件 words.txt" << std::endl;return 1;}std::string word;while (infile >> word) { // 逐詞讀取std::cout << "讀取到的單詞: " << word << std::endl;}infile.close();return 0;
}
輸出如下:
說明:
-
適用于逐詞讀取數據,適合處理由空白字符分隔的數據。
-
無法讀取包含空格的完整句子或短語。
5.2 使用 std::getline 逐行讀取
std::getline 函數用于從文件中逐行讀取數據,適用于處理文本文件中的行數據。
假設 lines.txt 文件內容如下:
#include <fstream>
#include <iostream>
#include <string>int main() {std::ifstream infile("lines.txt");if (!infile) {std::cerr << "無法打開文件 lines.txt" << std::endl;return 1;}std::string line;while (std::getline(infile, line)) { // 逐行讀取std::cout << "讀取到的行: " << line << std::endl;}infile.close();return 0;
}
輸出如下:
說明:
-
適用于逐行處理文件內容,保留每行的完整信息。
-
可以處理包含空格和其他特殊字符的行。
5.3 讀取整個文件內容
有時需要一次性讀取整個文件內容,尤其適用于小文件或需要對整個文件內容進行處理的場景。
假設 content.txt 文件內容如下:
#include <fstream>
#include <iostream>
#include <string>int main() {std::ifstream infile("content.txt");if (!infile) {std::cerr << "無法打開文件 content.txt" << std::endl;return 1;}// 使用迭代器讀取整個文件內容到字符串std::string content((std::istreambuf_iterator<char>(infile)),std::istreambuf_iterator<char>());std::cout << "文件內容:\n" << content << std::endl;infile.close();return 0;
}
輸出如下:
說明:
使用 std::istreambuf_iterator
迭代器可以高效地讀取整個文件內容。
適用于小至中等大小的文件,對于非常大的文件,可能需要分塊讀取以避免內存問題。
對此行代碼的理解:
std::string content((std::istreambuf_iterator<char>(infile)),std::istreambuf_iterator<char>());
-
這行代碼使用了
std::istreambuf_iterator
來從輸入文件流infile
讀取內容并構建一個std::string
對象。具體理解如下: -
std::istreambuf_iterator(infile)
:創建一個輸入迭代器,從 infile 的緩沖區讀取字符。 -
std::istreambuf_iterator()
:這是一個空的迭代器,用于表示輸入結束。
構造std::string
:通過傳入兩個迭代器,構造函數會從infile
中讀取所有字符,直到遇到結束迭代器。 -
最終,這行代碼的作用是將整個文件的內容讀入到
content
字符串中。這樣,你就可以方便地對文件內容進行操作。
5.4 讀取二進制數據
對于非文本文件(如圖片、音頻、視頻等),需要以二進制模式讀取數據,以防止數據在讀取過程中被轉換或丟失。
假設需要讀取一個圖片文件 image.jpg 并輸出其大小。
#include <fstream>
#include <iostream>
#include <vector>int main() {// 以二進制模式打開文件std::ifstream infile("image.jpg", std::ios::binary);if (!infile) {std::cerr << "無法打開文件 image.jpg" << std::endl;return 1;}// 移動讀指針到文件末尾以獲取文件大小//這行代碼將文件指針移動到文件的末尾。seekg 函數用于設置輸入流的位置,0 是偏移量infile.seekg(0, std::ios::end);//tellg 函數返回當前文件指針的位置(即文件的長度)std::streamsize size = infile.tellg();//這行代碼將文件指針重置回文件的開頭infile.seekg(0, std::ios::beg);// 讀取文件內容到緩沖區std::vector<char> buffer(size);if (infile.read(buffer.data(), size)) {std::cout << "成功讀取 " << size << " 字節。" << std::endl;} else {std::cerr << "讀取文件失敗!" << std::endl;}infile.close();return 0;
}
輸出如下:
說明:
-
使用
std::ios::binary
模式打開文件,確保數據按原樣讀取。 -
通過
seekg
和tellg
獲取文件大小,預分配緩沖區。 -
使用
read
函數讀取二進制數據到緩沖區。
6. 寫入文件
6.1 使用 << 操作符寫入數據
<<
操作符用于向文件中插入數據,類似于向標準輸出流 std::cout 中插入數據。
將用戶信息寫入文件 users.txt。
#include <fstream>
#include <iostream>
#include <string>int main() {std::ofstream outfile("users.txt");if (!outfile) {std::cerr << "無法打開文件 users.txt" << std::endl;return 1;}// 寫入用戶信息outfile << "用戶名: Alice\n年齡: 30\n" << std::endl;outfile << "用戶名: Bob\n年齡: 25\n" << std::endl;outfile << "用戶名: Charlie\n年齡: 35\n" << std::endl;outfile.close();std::cout << "用戶信息已寫入 users.txt" << std::endl;return 0;
}
輸出如下:
文件即自動創建于當前工程根目錄下
說明:
-
使用
<<
操作符可以方便地將各種數據類型寫入文件。 -
std::endl
用于插入換行符并刷新緩沖區。
6.2 使用 std::ofstream::write 寫入二進制數據
對于需要寫入二進制數據的場景,使用 write() 成員函數更為合適。
將一個字符數組寫入二進制文件 output.bin。
#include <fstream>
#include <iostream>
#include <vector>int main() {std::ofstream outfile("output.bin", std::ios::binary | std::ios::trunc);if (!outfile) {std::cerr << "無法打開文件 output.bin" << std::endl;return 1;}// 準備二進制數據std::vector<char> data = {'H', 'e', 'l', 'l', 'o', '\0'};// 寫入二進制數據到文件outfile.write(data.data(), data.size());if (!outfile) {std::cerr << "寫入文件失敗!" << std::endl;} else {std::cout << "成功寫入 " << data.size() << " 字節到 output.bin" << std::endl;}outfile.close();return 0;
}
data.data()
:獲取 data 字符串的指針,指向字符串的首字符。這是寫入的起始地址。
data.size()
:返回字符串 data 的長度,表示要寫入的字節數。
outfile.write(data.data(), data.size());
:調用 write 函數,將從 data 指針開始的 data.size()
字節寫入 outfile 中。
輸出如下:
說明:
-
使用
write()
可以指定要寫入的數據地址和字節數,適用于二進制數據。 -
確保文件以二進制模式打開,防止數據被意外轉換。
7. 一個實際使用的示例
讀取mysql中conf文件的配置:
bool ConnectionPool::loadConfigFile()
{//setenv("MYSQL_CONF_PATH","/home/kyros1ee/QtEnviroment/WeChat-main/chatserver/conf/mysql.conf",1);// 設置環境變量或絕對路徑const char* configPath = getenv("MYSQL_CONF"); //if (!configPath){LOG_ERROR << " mysql.conf MYSQL_CONF_PATH not set!";return false;}ifstream file(configPath);if (!file.is_open()){LOG_ERROR << "mysql.conf 文件不存在!";return false;}string line;while (getline(file, line)){// 忽略空行和注釋行if (line.empty() || line.find('=') == string::npos)continue;if (line.back() == '\r') {line.pop_back();}istringstream iss(line);string key, value;if (getline(iss, key, '=') && getline(iss, value)){// 去除可能存在的前后空白key = trim(key);value = trim(value);if (key == "ip") _ip = value;else if (key == "port") _port = stoi(value);else if (key == "username") _username = value;else if (key == "password") _password = value;else if (key == "dbname") _dbname = value;else if (key == "initSize") _initSize = stoi(value);else if (key == "maxSize") _maxSize = stoi(value);else if (key == "maxIdleTime") _maxIdleTime = stoi(value);else if (key == "connectionTime") _connectionTimeout = stoi(value);}}return true;
}