C++ 採用流抽象統一處理各種輸入輸出設備(鍵盤、屏幕、文件、內存緩衝區、網絡套接字等)。流將數據傳輸視為字節序列的流動,屏蔽了底層設備差異。核心類層次結構如下:
ios_base (格式狀態、區域設置、同步控制)
│
├── ios (流緩衝區指針管理、rdbuf、tie、flags)
│ │
│ ├── istream (輸入流:operator>>, get, getline, read, seekg)
│ │ ├── ifstream (文件輸入)
│ │ ├── istringstream (字符串輸入)
│ │ └── iostream (輸入/輸出)
│ │ ├── fstream (文件輸入輸出)
│ │ └── stringstream (字符串輸入輸出)
│ │
│ └── ostream (輸出流:operator<<, put, write, seekp, flush)
│ ├── ofstream (文件輸出)
│ ├── ostringstream (字符串輸出)
│ └── iostream ...
| 對象 | 類型 | 關聯設備 | 緩衝模式 | 典型用途 |
|---|---|---|---|---|
cin | istream | 標準輸入 (stdin) | 行緩衝 | 鍵盤輸入 |
cout | ostream | 標準輸出 (stdout) | 行緩衝 (交互) / 全緩衝 (重定向) | 正常輸出 |
cerr | ostream | 標準錯誤 (stderr) | 無緩衝 | 錯誤信息,立即顯示 |
clog | ostream | 標準錯誤 (stderr) | 全緩衝 | 日誌記錄 |
wcin/wcout/wcerr/wclog | 寬字符版 | 同上 | 同上 | Unicode 支持 |
operator>> vs getlinestring s; // 1. operator>> (格式化輸入) cin >> s; // 行為:跳過前導空白(空格/換行/制表) → 讀取非空白字符 → 遇到空白停止 → 空白留在流中 // 2. getline (非格式化輸入) getline(cin, s); // 行為:不跳過前導空白 → 讀取直到 '\n' → 提取並丟棄 '\n' → 若首字符是 '\n' 返回空串 // 3. getline 帶分隔符 getline(cin, s, ','); // 以逗號分隔,保留換行符
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
string line;
cout << "請輸入文本 (Ctrl+D 結束):\n";
while (getline(cin, line)) { // 按行讀取
stringstream ss(line); // 行轉流
string word;
while (ss >> word) { // 流提取分詞 (自動跳過空白)
cout << word << '\n';
}
}
return 0;
}
#include <fstream>
#include <sstream>
#include <string>
#include <iostream>
using namespace std;
int main() {
ifstream fin("data.csv");
if (!fin) { cerr << "無法打開文件\n"; return -1; }
string line;
while (getline(fin, line)) { // 1. 按行讀取
stringstream ss(line);
string field;
while (getline(ss, field, ',')) { // 2. 按逗號分割
cout << field << '|';
}
cout << '\n';
}
return 0;
}
#include <fstream>
#include <string>
#include <iterator>
#include <algorithm>
void copy_methods(const string& src, const string& dst) {
// 方法一:字符級複製 (最慢,格式化開銷大)
{ ifstream in(src, ios::binary); ofstream out(dst + ".1", ios::binary);
char ch; while (in.get(ch)) out << ch; }
// 方法二:行級複製 (文本適用,二進制可能多加 '\n')
{ ifstream in(src, ios::binary); ofstream out(dst + ".2", ios::binary);
string line; while (getline(in, line)) out << line << '\n'; }
// 方法三:迭代器複製 (需關閉 skipws)
{ ifstream in(src, ios::binary); in.unsetf(ios::skipws);
ofstream out(dst + ".3", ios::binary);
copy(istreambuf_iterator<char>(in), istreambuf_iterator<char>(),
ostreambuf_iterator<char>(out)); }
// 方法四:緩衝區直接複製 (最快,推薦)
{ ifstream in(src, ios::binary); ofstream out(dst + ".4", ios::binary);
out << in.rdbuf(); } // 直接操作 streambuf
}
rdbuf() 直接訪問底層 streambuf,避免了格式化、區域設置檢查、字符擴展等開銷,且編譯器常優化為 read/write 系統調用循環。
streambuf) 底層編程// 簡化版 streambuf 接口
class streambuf {
protected:
// 輸入緩衝區指針
char* eback(); // 緩衝區開始
char* gptr(); // 當前讀位置
char* egptr(); // 緩衝區結束
void gbump(int n); // 移動 gptr
// 輸出緩衝區指針
char* pbase(); // 緩衝區開始
char* pptr(); // 當前寫位置
char* epptr(); // 緩衝區結束
void pbump(int n); // 移動 pptr
// 虛函數:派生類重寫實現具體設備操作
virtual int_type underflow(); // 輸入緩衝區空時調用 (裝填數據)
virtual int_type uflow(); // 取字符並推進指針
virtual int_type pbackfail(); // 退回字符
virtual int_type overflow(int_type c); // 輸出緩衝區滿時調用 (刷新數據)
virtual int sync(); // 同步緩衝區與設備
virtual streambuf* setbuf(char* s, streamsize n); // 設置用戶緩衝區
};
#include <iostream>
#include <streambuf>
#include <vector>
class TeeBuf : public streambuf {
vector<streambuf*> bufs_;
vector<char> buffer_; // 內部緩衝區
protected:
int_type overflow(int_type c) override {
if (c != traits_type::eof()) {
buffer_.push_back(traits_type::to_char_type(c));
}
return sync() == 0 ? c : traits_type::eof();
}
int sync() override {
for (auto buf : bufs_) {
if (buf->sputn(buffer_.data(), buffer_.size()) != buffer_.size()) return -1;
}
buffer_.clear();
return 0;
}
public:
void add_stream(streambuf* buf) { bufs_.push_back(buf); }
};
int main() {
TeeBuf tee;
tee.add_stream(cout.rdbuf()); // 標準輸出
ofstream log("log.txt");
tee.add_stream(log.rdbuf()); // 文件日誌
ostream dual_out(&tee);
dual_out << "這行同時輸出到屏幕和文件\n";
dual_out.flush();
return 0;
}
// 三種等價方式
ifstream fin("source.dat");
cout << fin.rdbuf(); // 1. operator<<(streambuf*)
fin.clear(); fin.seekg(0);
fin.get(*cout.rdbuf(), EOF); // 2. get 讀至 EOF 寫入 cout 緩衝區
fin.clear(); fin.seekg(0);
while (!fin.get(*cout.rdbuf()).eof()) { // 3. 逐行/逐塊 (需處理 failbit)
if (fin.fail()) fin.clear();
}
| 模式 | 觸發刷新條件 | 適用場景 | 設置方法 |
|---|---|---|---|
| 全緩衝 | 緩衝區滿 / flush() / 程序正常退出 | 磁盤文件、管道、非交互設備 | 默認 (文件流) |
| 行緩衝 | 遇到 '\n' / 緩衝區滿 / 輸入請求 / flush() | 交互終端 (stdin/stdout) | 默認 (關聯終端時) |
| 無緩衝 | 每次 I/O 操作即時執行 | 標準錯誤 cerr、調試輸出 | pubsetbuf(0,0) 或 setvbuf(fp, NULL, _IONBF, 0) |
// C++ 設置自定義緩衝區
char buf[4096];
ofstream fout("data.bin", ios::binary);
fout.rdbuf()->pubsetbuf(buf, sizeof(buf)); // 設置用戶緩衝區
// C 風格 (文件指針)
FILE* fp = fopen("data.bin", "wb");
setvbuf(fp, NULL, _IOFBF, 8192); // 全緩衝 8KB
ios::sync_with_stdio(false) 解除同步,提升性能,但之後不可混用 printf/scanf 與 cin/cout。\n ↔ \r\n,讀寫二進制文件必須加 ios::binary。if (stream) 等價於 !stream.fail();eof() 僅在讀取嘗試超過末尾後置位,不要用 while(!fin.eof()) 控制循環,應用 while (getline(fin, line)) 或 while (fin >> val)。endl = '\n' + flush(),高頻輸出用 '\n' 避免頻繁系統調用,關鍵日誌用 flush 或 cerr (無緩衝)。wifstream) 需設置 locale 和 codecvt (C++11) 或 std::wstring_convert (C++17 棄用),現代項目建議統一 UTF-8 + char + std::string,配合 iconv/icu 做轉換。C++ I/O 流體系通過 面向對象封裝 實現了設備無關的數據傳輸抽象。理解 stream (格式化邏輯) 與 streambuf (設備交互) 的分離設計,是高效編程、故障診斷、擴展自定義設備的關鍵。從簡單的 cin/cout 到高性能文件複製、自定義緩衝區、多路輸出分流,掌握流體系各層次用法,能讓 C++ 程序在文本處理、數據序列化、日誌系統、網絡通信等場景中游刃有余。