C++ IO流詳解之標(biāo)準(zhǔn)IO、文件IO與字符串IO實(shí)戰(zhàn)
一、學(xué)習(xí)目標(biāo)與重點(diǎn)
- 掌握C++ IO流的核心概念、體系結(jié)構(gòu)及基本工作原理
- 熟練使用標(biāo)準(zhǔn)IO流(cin、cout、cerr、clog)進(jìn)行控制臺(tái)輸入輸出,掌握格式控制技巧
- 精通文件IO流(ifstream、ofstream、fstream)的文件讀寫操作,處理常見文件場(chǎng)景
- 學(xué)會(huì)使用字符串IO流(istringstream、ostringstream)進(jìn)行內(nèi)存數(shù)據(jù)的格式化與解析
- 理解IO流的狀態(tài)標(biāo)志、錯(cuò)誤處理及緩沖區(qū)機(jī)制
- 結(jié)合實(shí)戰(zhàn)案例,解決實(shí)際開發(fā)中的IO流應(yīng)用問題(如配置文件讀寫、數(shù)據(jù)序列化)
?? 核心重點(diǎn):IO流類體系的繼承關(guān)系、文件IO的打開模式與權(quán)限控制、格式控制符的靈活運(yùn)用、字符串IO的數(shù)據(jù)解析技巧
二、C++ IO流概述
2.1 什么是IO流
IO(Input/Output)流是C++中處理輸入輸出的核心機(jī)制,核心思想是“將數(shù)據(jù)的傳輸抽象為流”——數(shù)據(jù)從源端(如鍵盤、文件、內(nèi)存)流向目標(biāo)端(如屏幕、文件、內(nèi)存)的過程,稱為“流”。
??? 生活中的IO流類比:
- 水流:水管中的水從水源(水庫)流向目的地(家庭),對(duì)應(yīng)數(shù)據(jù)從輸入源流向輸出目標(biāo)
- 物流:快遞從商家(源)通過運(yùn)輸網(wǎng)絡(luò)(流)送達(dá)用戶(目標(biāo)),對(duì)應(yīng)數(shù)據(jù)的傳輸過程
C++ IO流的核心優(yōu)勢(shì):
- 統(tǒng)一的接口:無論是控制臺(tái)、文件還是內(nèi)存,均通過相同的流操作(如
>>輸入、<<輸出)實(shí)現(xiàn),降低學(xué)習(xí)成本 - 靈活的格式控制:支持整數(shù)、浮點(diǎn)數(shù)、字符串等各種數(shù)據(jù)類型的格式化輸入輸出
- 可擴(kuò)展性:支持自定義類型的IO流操作(重載
>>和<<運(yùn)算符)
2.2 C++ IO流類體系
C++標(biāo)準(zhǔn)庫通過一系列類實(shí)現(xiàn)IO流功能,這些類主要定義在<iostream>(標(biāo)準(zhǔn)IO)、<fstream>(文件IO)、<sstream>(字符串IO)頭文件中,核心類體系如下:
2.2.1 基類(抽象類)
ios_base:所有IO流類的頂層基類,定義了流的狀態(tài)標(biāo)志(如錯(cuò)誤、文件結(jié)束)、格式控制標(biāo)志(如左對(duì)齊、十六進(jìn)制)等ios:繼承自ios_base,封裝了流的核心操作(如狀態(tài)檢查、緩沖區(qū)管理),是istream和ostream的直接基類
2.2.2 核心流類
| 流類 | 功能描述 | 頭文件 | 常用對(duì)象/實(shí)例化方式 |
|---|---|---|---|
istream | 輸入流基類,提供輸入操作(>>運(yùn)算符) | <iostream> | cin(標(biāo)準(zhǔn)輸入流,對(duì)應(yīng)鍵盤) |
ostream | 輸出流基類,提供輸出操作(<<運(yùn)算符) | <iostream> | cout(標(biāo)準(zhǔn)輸出流,對(duì)應(yīng)屏幕)、cerr(標(biāo)準(zhǔn)錯(cuò)誤流)、clog(標(biāo)準(zhǔn)日志流) |
iostream | 輸入輸出流類,繼承自istream和ostream | <iostream> | 用于雙向流操作(如文件讀寫) |
ifstream | 文件輸入流類,繼承自istream | <fstream> | 實(shí)例化對(duì)象讀取文件 |
ofstream | 文件輸出流類,繼承自ostream | <fstream> | 實(shí)例化對(duì)象寫入文件 |
fstream | 文件輸入輸出流類,繼承自iostream | <fstream> | 實(shí)例化對(duì)象讀寫文件 |
istringstream | 字符串輸入流類,繼承自istream | <sstream> | 從字符串中讀取數(shù)據(jù) |
ostringstream | 字符串輸出流類,繼承自ostream | <sstream> | 向字符串中寫入數(shù)據(jù)(格式化) |
stringstream | 字符串輸入輸出流類,繼承自iostream | <sstream> | 對(duì)字符串進(jìn)行讀寫操作 |
?? 類體系核心關(guān)系:
ios_base ← ios ← istream ← ifstream / istringstream ios_base ← ios ← ostream ← ofstream / ostringstream istream + ostream ← iostream ← fstream / stringstream
2.3 IO流的狀態(tài)標(biāo)志
IO流的操作結(jié)果(成功、失敗、文件結(jié)束)通過“狀態(tài)標(biāo)志”標(biāo)識(shí),定義在ios_base中,核心狀態(tài)標(biāo)志如下:
| 狀態(tài)標(biāo)志 | 描述 | 檢查方法 |
|---|---|---|
goodbit | 流狀態(tài)正常(無錯(cuò)誤、未到文件結(jié)束) | stream.good() |
eofbit | 到達(dá)文件結(jié)束(如讀取文件到末尾) | stream.eof() |
failbit | 操作失?。ㄈ巛斎敕穷A(yù)期類型、文件打開失?。?/td> | stream.fail() |
badbit | 嚴(yán)重錯(cuò)誤(如內(nèi)存訪問錯(cuò)誤、文件損壞) | stream.bad() |
?? 狀態(tài)檢查與重置:
- 流對(duì)象可直接作為布爾值判斷狀態(tài)(
if (cin)等價(jià)于if (cin.good())) - 重置狀態(tài)標(biāo)志:
stream.clear()(清除所有狀態(tài)標(biāo)志,恢復(fù)為goodbit) - 忽略錯(cuò)誤數(shù)據(jù):
stream.ignore(n, ch)(忽略最多n個(gè)字符,或直到遇到字符ch)
?? 警告:流操作失敗后(如cin輸入非數(shù)字),流會(huì)設(shè)置failbit,后續(xù)所有IO操作都會(huì)被忽略,需調(diào)用clear()重置狀態(tài),并處理錯(cuò)誤數(shù)據(jù)。
三、標(biāo)準(zhǔn)IO流:控制臺(tái)輸入輸出
標(biāo)準(zhǔn)IO流是指與控制臺(tái)(鍵盤、屏幕)交互的流,核心對(duì)象包括cin(輸入)、cout(輸出)、cerr(錯(cuò)誤輸出)、clog(日志輸出),定義在<iostream>頭文件中。
3.1 標(biāo)準(zhǔn)輸出流:cout、cerr、clog
3.1.1 核心區(qū)別
cout:緩沖輸出流,數(shù)據(jù)先存入緩沖區(qū),緩沖區(qū)滿或遇到endl時(shí)才輸出到屏幕,效率較高cerr:無緩沖輸出流,數(shù)據(jù)立即輸出到屏幕,用于輸出錯(cuò)誤信息(如程序異常)clog:緩沖輸出流,與cout類似,但專門用于輸出日志信息(如操作記錄)
?? 示例:標(biāo)準(zhǔn)輸出流對(duì)比
#include <iostream>
using namespace std;
int main() {
cout << "cout輸出:這是普通信息" << endl; // 換行并刷新緩沖區(qū)
cerr << "cerr輸出:這是錯(cuò)誤信息" << endl; // 立即輸出,不受緩沖區(qū)影響
clog << "clog輸出:這是日志信息" << endl; // 緩沖輸出,與cout類似
// 測(cè)試緩沖區(qū):cout未刷新時(shí),數(shù)據(jù)可能延遲輸出
cout << "cout無endl:";
// 此時(shí)數(shù)據(jù)在緩沖區(qū),未輸出到屏幕
cerr << "cerr插入:驗(yàn)證立即輸出" << endl;
// 輸出后,cout的緩沖區(qū)仍未刷新,直到程序結(jié)束或遇到endl
return 0;
}? 運(yùn)行結(jié)果:
cout輸出:這是普通信息
cerr輸出:這是錯(cuò)誤信息
clog輸出:這是日志信息
cerr插入:驗(yàn)證立即輸出
cout無endl:
3.1.2 格式控制
C++提供兩種格式控制方式:格式控制符(如setw、hex)和成員函數(shù)(如setf、precision),需包含<<iomanip>頭文件(格式控制符專用)。
?? 常用格式控制(以cout為例):
#include <iostream>
#include <<iomanip> // 包含格式控制符
using namespace std;
int main() {
int num = 255;
double pi = 3.1415926535;
string str = "C++ IO";
// 1. 整數(shù)格式:十進(jìn)制、八進(jìn)制、十六進(jìn)制
cout << "十進(jìn)制:" << dec << num << endl; // 255(默認(rèn))
cout << "八進(jìn)制:" << oct << num << endl; // 377(前綴無0)
cout << "十六進(jìn)制(小寫):" << hex << num << endl; // ff
cout << "十六進(jìn)制(大寫):" << hex << uppercase << num << endl; // FF
cout << resetiosflags(ios::uppercase); // 重置大寫格式
// 2. 浮點(diǎn)數(shù)精度控制
cout << "\n浮點(diǎn)數(shù)默認(rèn)精度(6位有效數(shù)字):" << pi << endl; // 3.14159
cout << "保留2位小數(shù):" << fixed << setprecision(2) << pi << endl; // 3.14
cout << "保留4位有效數(shù)字:" << resetiosflags(ios::fixed) << setprecision(4) << pi << endl; // 3.142
// 3. 寬度與對(duì)齊
cout << "\n左對(duì)齊(寬度10):" << left << setw(10) << str << "后續(xù)內(nèi)容" << endl; // "C++ IO 后續(xù)內(nèi)容"
cout << "右對(duì)齊(寬度10):" << right << setw(10) << str << "后續(xù)內(nèi)容" << endl; // " C++ IO后續(xù)內(nèi)容"
cout << "填充字符(寬度10,填充*):" << setfill('*') << setw(10) << str << endl; // "*****C++ IO"
// 4. 布爾值格式
bool flag = true;
cout << "\n布爾值默認(rèn):" << flag << endl; // 1
cout << "布爾值文字(true/false):" << boolalpha << flag << endl; // true
cout << resetiosflags(ios::boolalpha); // 重置布爾值格式
return 0;
}? 運(yùn)行結(jié)果:
十進(jìn)制:255
八進(jìn)制:377
十六進(jìn)制(小寫):ff
十六進(jìn)制(大寫):FF浮點(diǎn)數(shù)默認(rèn)精度(6位有效數(shù)字):3.14159
保留2位小數(shù):3.14
保留4位有效數(shù)字:3.142左對(duì)齊(寬度10):C++ IO 后續(xù)內(nèi)容
右對(duì)齊(寬度10): C++ IO后續(xù)內(nèi)容
填充字符(寬度10,填充*):*****C++ IO布爾值默認(rèn):1
布爾值文字(true/false):true
3.2 標(biāo)準(zhǔn)輸入流:cin
cin是istream類的實(shí)例,用于從鍵盤讀取數(shù)據(jù),核心操作是>>運(yùn)算符(提取運(yùn)算符),支持各種基本數(shù)據(jù)類型的輸入。
3.2.1 基本輸入用法
#include <iostream>
#include <string>
using namespace std;
int main() {
int a;
double b;
string s;
cout << "請(qǐng)輸入一個(gè)整數(shù)、一個(gè)浮點(diǎn)數(shù)和一個(gè)字符串(空格分隔):";
cin >> a >> b >> s; // 按順序讀取,空格/回車作為分隔符
cout << "\n你輸入的整數(shù):" << a << endl;
cout << "你輸入的浮點(diǎn)數(shù):" << b << endl;
cout << "你輸入的字符串:" << s << endl;
return 0;
}? 運(yùn)行示例:
請(qǐng)輸入一個(gè)整數(shù)、一個(gè)浮點(diǎn)數(shù)和一個(gè)字符串(空格分隔):10 3.14 Hello
你輸入的整數(shù):10
你輸入的浮點(diǎn)數(shù):3.14
你輸入的字符串:Hello
3.2.2 常見問題與解決方案
問題1:輸入數(shù)據(jù)類型不匹配
當(dāng)輸入的數(shù)據(jù)類型與變量類型不一致時(shí)(如int變量輸入字符串),cin會(huì)設(shè)置failbit,后續(xù)輸入操作失效。
?? 解決方案:檢查狀態(tài)并重置
#include <iostream>
#include <limits> // 包含numeric_limits
using namespace std;
int main() {
int num;
cout << "請(qǐng)輸入一個(gè)整數(shù):";
cin >> num;
if (!cin) { // 等價(jià)于cin.fail(),輸入失敗
cout << "輸入錯(cuò)誤!請(qǐng)輸入整數(shù)類型。" << endl;
cin.clear(); // 重置狀態(tài)標(biāo)志
// 忽略緩沖區(qū)中所有錯(cuò)誤數(shù)據(jù)(直到換行)
cin.ignore(numeric_limits<streamsize>::max(), '\n');
} else {
cout << "你輸入的整數(shù):" << num << endl;
}
// 重新輸入
cout << "請(qǐng)重新輸入一個(gè)整數(shù):";
cin >> num;
cout << "你重新輸入的整數(shù):" << num << endl;
return 0;
}問題2:讀取帶空格的字符串
>>運(yùn)算符默認(rèn)以空格、回車、制表符為分隔符,無法讀取帶空格的字符串(如“Hello World”)。
?? 解決方案:使用getline()函數(shù)
#include <iostream>
#include <string>
using namespace std;
int main() {
string name;
cout << "請(qǐng)輸入你的姓名(可帶空格):";
// 注意:若之前有cin >> 操作,需先忽略緩沖區(qū)中的換行符
cin.ignore(numeric_limits<streamsize>::max(), '\n');
getline(cin, name); // 讀取整行數(shù)據(jù),包括空格
cout << "你好," << name << "!" << endl;
return 0;
}?? 注意:cin >> 之后調(diào)用 getline() 時(shí),cin >> 會(huì)將換行符留在緩沖區(qū),getline() 會(huì)直接讀取空字符串,因此需先用 cin.ignore() 忽略換行符。
問題3:讀取指定長度的字符
使用cin.get(char* buf, int n)讀取最多n-1個(gè)字符(最后一個(gè)字符為’\0’),或直到遇到指定分隔符。
?? 示例:讀取指定長度的字符串
#include <iostream>
using namespace std;
int main() {
char buf[10]; // 最多存儲(chǔ)9個(gè)字符+1個(gè)'\0'
cout << "請(qǐng)輸入字符串(最多9個(gè)字符):";
cin.get(buf, 10); // 讀取最多9個(gè)字符,遇到換行或滿9個(gè)字符停止
cout << "你輸入的字符串:" << buf << endl;
return 0;
}四、文件IO流:文件讀寫操作
文件IO流用于與磁盤文件進(jìn)行交互,核心類是ifstream(讀文件)、ofstream(寫文件)、fstream(讀寫文件),需包含<fstream>頭文件。
4.1 文件打開與關(guān)閉
4.1.1 打開文件的兩種方式
- 構(gòu)造函數(shù)打開:實(shí)例化流對(duì)象時(shí),通過構(gòu)造函數(shù)指定文件名和打開模式
open()方法打開:先實(shí)例化流對(duì)象,再調(diào)用open(const string& filename, ios::openmode mode)方法
4.1.2 核心打開模式(ios::openmode)
文件打開模式用于指定文件的操作類型(讀/寫)、文件存在時(shí)的處理方式(覆蓋/追加)等,可通過位或(|)組合使用:
| 打開模式 | 描述 | 適用類 |
|---|---|---|
ios::in | 讀模式(文件必須存在,否則打開失?。?/td> | ifstream、fstream |
ios::out | 寫模式(文件不存在則創(chuàng)建,存在則覆蓋) | ofstream、fstream |
ios::app | 追加模式(文件不存在則創(chuàng)建,寫入數(shù)據(jù)追加到文件末尾) | ofstream、fstream |
ios::trunc | 截?cái)嗄J剑ㄎ募嬖趧t清空內(nèi)容,默認(rèn)與ios::out結(jié)合) | ofstream、fstream |
ios::binary | 二進(jìn)制模式(默認(rèn)是文本模式) | 所有文件流類 |
ios::ate | 打開文件后定位到文件末尾 | 所有文件流類 |
?? 常用模式組合:
- 讀文本文件:
ios::in(ifstream默認(rèn)模式) - 寫文本文件(覆蓋):
ios::out或ios::out | ios::trunc(ofstream默認(rèn)模式) - 寫文本文件(追加):
ios::out | ios::app - 讀寫文本文件:
ios::in | ios::out - 讀二進(jìn)制文件:
ios::in | ios::binary
4.1.3 文件關(guān)閉
文件使用完畢后,需調(diào)用close()方法關(guān)閉文件,釋放文件資源(如文件句柄)。即使不手動(dòng)調(diào)用close(),流對(duì)象析構(gòu)時(shí)也會(huì)自動(dòng)關(guān)閉文件,但建議手動(dòng)關(guān)閉(避免異常導(dǎo)致資源泄漏)。
?? 警告:打開文件后必須檢查是否打開成功,否則后續(xù)操作會(huì)失敗。
4.2 文本文件讀寫
文本文件存儲(chǔ)的是人類可讀的字符(如ASCII碼、UTF-8編碼),讀寫操作與標(biāo)準(zhǔn)IO流類似(使用>>、<<、getline())。
4.2.1 文本文件寫入(ofstream)
?? 示例:創(chuàng)建文本文件并寫入內(nèi)容
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 1. 構(gòu)造函數(shù)打開文件(寫模式,覆蓋原有內(nèi)容)
ofstream out_file("test.txt", ios::out | ios::trunc);
// 檢查文件是否打開成功
if (!out_file.is_open()) {
cerr << "文件打開失??!" << endl;
return 1;
}
// 2. 寫入數(shù)據(jù)(使用<<運(yùn)算符)
out_file << "Hello, File IO!" << endl;
out_file << "這是C++文本文件寫入示例" << endl;
out_file << "整數(shù):" << 100 << ",浮點(diǎn)數(shù):" << 3.14 << endl;
// 3. 寫入字符串(使用write()方法,指定長度)
string str = "使用write()方法寫入的內(nèi)容";
out_file.write(str.c_str(), str.length());
out_file << endl;
// 4. 關(guān)閉文件
out_file.close();
cout << "文件寫入完成!" << endl;
return 0;
}? 運(yùn)行結(jié)果:生成test.txt文件,內(nèi)容如下:
Hello, File IO!
這是C++文本文件寫入示例
整數(shù):100,浮點(diǎn)數(shù):3.14
使用write()方法寫入的內(nèi)容
4.2.2 文本文件讀?。╥fstream)
文本文件讀取有三種常用方式:>>運(yùn)算符、getline()函數(shù)、read()方法。
?? 示例:讀取文本文件內(nèi)容
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 1. 打開文件(讀模式)
ifstream in_file("test.txt", ios::in);
if (!in_file.is_open()) {
cerr << "文件打開失??!" << endl;
return 1;
}
cout << "=== 方式1:使用>>運(yùn)算符讀取(忽略空格和換行) ===" << endl;
string word;
while (in_file >> word) { // 直到文件結(jié)束(eofbit)
cout << word << " ";
}
cout << endl;
// 重置文件指針到開頭(否則后續(xù)讀取會(huì)從文件末尾開始)
in_file.clear(); // 重置狀態(tài)標(biāo)志(因eofbit已設(shè)置)
in_file.seekg(0, ios::beg); // 移動(dòng)文件指針到開頭(ios::beg=文件開頭)
cout << "\n=== 方式2:使用getline()讀取整行 ===" << endl;
string line;
while (getline(in_file, line)) { // 逐行讀取
cout << line << endl;
}
in_file.clear();
in_file.seekg(0, ios::beg);
cout << "\n=== 方式3:使用read()讀取指定字節(jié)數(shù) ===" << endl;
char buf[1024];
in_file.read(buf, sizeof(buf)); // 讀取最多1023個(gè)字符
buf[in_file.gcount()] = '\0'; // gcount()返回實(shí)際讀取的字節(jié)數(shù),添加字符串結(jié)束符
cout << buf << endl;
// 關(guān)閉文件
in_file.close();
cout << "\n文件讀取完成!" << endl;
return 0;
}? 運(yùn)行結(jié)果:
=== 方式1:使用>>運(yùn)算符讀?。ê雎钥崭窈蛽Q行) ===
Hello, File IO! 這是C++文本文件寫入示例 整數(shù):100,浮點(diǎn)數(shù):3.14 使用write()方法寫入的內(nèi)容
=== 方式2:使用getline()讀取整行 ===
Hello, File IO!
這是C++文本文件寫入示例
整數(shù):100,浮點(diǎn)數(shù):3.14
使用write()方法寫入的內(nèi)容
=== 方式3:使用read()讀取指定字節(jié)數(shù) ===
Hello, File IO!
這是C++文本文件寫入示例
整數(shù):100,浮點(diǎn)數(shù):3.14
使用write()方法寫入的內(nèi)容
文件讀取完成!
4.3 二進(jìn)制文件讀寫
二進(jìn)制文件存儲(chǔ)的是字節(jié)流(如圖片、視頻、可執(zhí)行文件),讀寫時(shí)需指定ios::binary模式,使用read()和write()方法(按字節(jié)操作)。
4.3.1 二進(jìn)制文件寫入
?? 示例:將結(jié)構(gòu)體數(shù)據(jù)寫入二進(jìn)制文件
#include <iostream>
#include <fstream>
using namespace std;
// 定義結(jié)構(gòu)體(存儲(chǔ)用戶信息)
struct User {
char name[20]; // 用戶名
int age; // 年齡
double score; // 分?jǐn)?shù)
};
int main() {
// 1. 打開二進(jìn)制文件(寫模式)
ofstream out_file("users.bin", ios::out | ios::binary | ios::trunc);
if (!out_file.is_open()) {
cerr << "文件打開失?。? << endl;
return 1;
}
// 2. 準(zhǔn)備數(shù)據(jù)
User user1 = {"張三", 20, 95.5};
User user2 = {"李四", 22, 88.0};
// 3. 寫入數(shù)據(jù)(write()參數(shù):數(shù)據(jù)地址、字節(jié)數(shù))
out_file.write(reinterpret_cast<const char*>(&user1), sizeof(User));
out_file.write(reinterpret_cast<const char*>(&user2), sizeof(User));
// 4. 關(guān)閉文件
out_file.close();
cout << "二進(jìn)制文件寫入完成!" << endl;
return 0;
}4.3.2 二進(jìn)制文件讀取
?? 示例:從二進(jìn)制文件讀取結(jié)構(gòu)體數(shù)據(jù)
#include <iostream>
#include <fstream>
using namespace std;
struct User {
char name[20];
int age;
double score;
};
int main() {
// 1. 打開二進(jìn)制文件(讀模式)
ifstream in_file("users.bin", ios::in | ios::binary);
if (!in_file.is_open()) {
cerr << "文件打開失??!" << endl;
return 1;
}
// 2. 讀取數(shù)據(jù)
User user;
cout << "=== 讀取二進(jìn)制文件內(nèi)容 ===" << endl;
// 循環(huán)讀取,直到文件結(jié)束
while (in_file.read(reinterpret_cast<char*>(&user), sizeof(User))) {
cout << "用戶名:" << user.name
<< ",年齡:" << user.age
<< ",分?jǐn)?shù):" << user.score << endl;
}
// 檢查讀取是否因文件結(jié)束而停止
if (in_file.eof()) {
cout << "讀取完成(已到文件末尾)" << endl;
} else if (in_file.fail()) {
cerr << "讀取失??!" << endl;
}
// 3. 關(guān)閉文件
in_file.close();
return 0;
}? 運(yùn)行結(jié)果:
=== 讀取二進(jìn)制文件內(nèi)容 ===
用戶名:張三,年齡:20,分?jǐn)?shù):95.5
用戶名:李四,年齡:22,分?jǐn)?shù):88
讀取完成(已到文件末尾)
?? 注意事項(xiàng):
- 二進(jìn)制文件讀寫需確保數(shù)據(jù)類型的大小和內(nèi)存布局一致(如32位系統(tǒng)與64位系統(tǒng)的
int大小可能不同) - 結(jié)構(gòu)體中若包含指針(如
char* name),寫入的是指針地址而非實(shí)際數(shù)據(jù),讀取后指針可能失效(懸空指針)
4.4 文件指針操作
文件指針用于定位文件中的讀寫位置,分為輸入指針(gptr,用于讀操作)和輸出指針(pptr,用于寫操作),通過以下成員函數(shù)操作:
| 函數(shù)名 | 功能描述 | 參數(shù)說明 |
|---|---|---|
seekg(pos, mode) | 移動(dòng)輸入指針到指定位置 | pos:偏移量;mode:基準(zhǔn)位置(ios::beg=開頭、ios::cur=當(dāng)前、ios::end=末尾) |
seekp(pos, mode) | 移動(dòng)輸出指針到指定位置 | 同seekg |
tellg() | 獲取輸入指針當(dāng)前位置(相對(duì)于文件開頭的字節(jié)數(shù)) | 返回值:streampos類型(可轉(zhuǎn)換為int) |
tellp() | 獲取輸出指針當(dāng)前位置 | 同tellg |
?? 示例:文件指針操作(讀取文件中間內(nèi)容)
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
ifstream in_file("test.txt", ios::in);
if (!in_file.is_open()) {
cerr << "文件打開失?。? << endl;
return 1;
}
// 1. 獲取文件大?。ㄒ苿?dòng)指針到末尾,獲取位置)
in_file.seekg(0, ios::end);
int file_size = in_file.tellg();
cout << "文件大?。? << file_size << "字節(jié)" << endl;
// 2. 移動(dòng)指針到文件中間(第20字節(jié)處)
in_file.seekg(20, ios::beg);
cout << "指針位置(第20字節(jié)):" << in_file.tellg() << endl;
// 3. 讀取從第20字節(jié)開始的內(nèi)容
string content;
getline(in_file, content);
cout << "從第20字節(jié)開始的內(nèi)容:" << content << endl;
in_file.close();
return 0;
}? 運(yùn)行結(jié)果(假設(shè)test.txt第20字節(jié)后內(nèi)容為“這是C++文本文件寫入示例”):
文件大?。?5字節(jié) 指針位置(第20字節(jié)):20 從第20字節(jié)開始的內(nèi)容:這是C++文本文件寫入示例
五、字符串IO流:內(nèi)存數(shù)據(jù)格式化與解析
字符串IO流用于在內(nèi)存中對(duì)字符串進(jìn)行格式化輸入輸出,核心類是istringstream(從字符串讀)、ostringstream(向字符串寫)、stringstream(讀寫字符串),需包含<sstream>頭文件。
字符串IO流的核心應(yīng)用場(chǎng)景:
- 數(shù)據(jù)格式化:將多個(gè)基本類型數(shù)據(jù)拼接為字符串(如日志記錄)
- 數(shù)據(jù)解析:將字符串按指定格式拆分為多個(gè)基本類型數(shù)據(jù)(如解析配置文件、網(wǎng)絡(luò)數(shù)據(jù))
5.1 ostringstream:字符串格式化輸出
ostringstream用于將各種類型數(shù)據(jù)寫入字符串(內(nèi)存緩沖區(qū)),支持格式控制,最終通過str()方法獲取格式化后的字符串。
?? 示例:格式化日志字符串
#include <iostream>
#include <sstream>
#include <<iomanip>
#include <string>
using namespace std;
int main() {
// 1. 創(chuàng)建ostringstream對(duì)象
ostringstream oss;
// 2. 格式化寫入數(shù)據(jù)(支持格式控制)
string level = "INFO";
string module = "FileHandler";
int line = 100;
double cost = 0.023;
oss << "[" << level << "] "
<< "模塊:" << module << ","
<< "行號(hào):" << line << ","
<< "耗時(shí):" << fixed << setprecision(4) << cost << "秒";
// 3. 獲取格式化后的字符串
string log = oss.str();
cout << "格式化日志:" << log << endl;
// 4. 追加內(nèi)容
oss << ",狀態(tài):成功";
cout << "追加后日志:" << oss.str() << endl;
// 5. 清空緩沖區(qū)(重新使用)
oss.str(""); // 清空字符串內(nèi)容
oss.clear(); // 重置狀態(tài)標(biāo)志
oss << "[" << "ERROR" << "] " << "文件打開失敗,路徑:test.txt";
cout << "新日志:" << oss.str() << endl;
return 0;
}? 運(yùn)行結(jié)果:
格式化日志:[INFO] 模塊:FileHandler,行號(hào):100,耗時(shí):0.0230秒
追加后日志:[INFO] 模塊:FileHandler,行號(hào):100,耗時(shí):0.0230秒,狀態(tài):成功
新日志:[ERROR] 文件打開失敗,路徑:test.txt
5.2 istringstream:字符串解析輸入
istringstream用于從字符串中讀取數(shù)據(jù),按指定格式拆分字符串為多個(gè)基本類型數(shù)據(jù),支持>>運(yùn)算符和getline()函數(shù)。
?? 示例1:解析空格分隔的字符串
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
string data = "10 3.14 Hello C++";
istringstream iss(data);
// 解析數(shù)據(jù)
int num;
double pi;
string str1, str2;
iss >> num >> pi >> str1 >> str2;
cout << "解析結(jié)果:" << endl;
cout << "整數(shù):" << num << endl;
cout << "浮點(diǎn)數(shù):" << pi << endl;
cout << "字符串1:" << str1 << endl;
cout << "字符串2:" << str2 << endl;
return 0;
}? 運(yùn)行結(jié)果:
解析結(jié)果:
整數(shù):10
浮點(diǎn)數(shù):3.14
字符串1:Hello
字符串2:C++
?? 示例2:解析逗號(hào)分隔的配置字符串(如CSV數(shù)據(jù))
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
// 模擬CSV格式的配置數(shù)據(jù):姓名,年齡,分?jǐn)?shù)
string config = "張三,20,95.5";
istringstream iss(config);
string field;
// 按逗號(hào)分隔解析
getline(iss, field, ','); // 讀取第一個(gè)字段(姓名)
string name = field;
getline(iss, field, ','); // 讀取第二個(gè)字段(年齡)
int age = stoi(field); // 字符串轉(zhuǎn)整數(shù)
getline(iss, field, ','); // 讀取第三個(gè)字段(分?jǐn)?shù))
double score = stod(field); // 字符串轉(zhuǎn)浮點(diǎn)數(shù)
cout << "配置解析結(jié)果:" << endl;
cout << "姓名:" << name << endl;
cout << "年齡:" << age << endl;
cout << "分?jǐn)?shù):" << score << endl;
return 0;
}? 運(yùn)行結(jié)果:
配置解析結(jié)果:
姓名:張三
年齡:20
分?jǐn)?shù):95.5
5.3 stringstream:字符串讀寫雙向操作
stringstream兼具istringstream和ostringstream的功能,可對(duì)字符串進(jìn)行讀寫操作,適用于需要先格式化再解析的場(chǎng)景。
?? 示例:字符串讀寫雙向操作
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main() {
stringstream ss;
// 1. 寫入數(shù)據(jù)(格式化)
ss << "姓名:" << "李四" << ",年齡:" << 22 << ",分?jǐn)?shù):" << 88.0;
cout << "格式化字符串:" << ss.str() << endl;
// 2. 讀取解析(忽略非數(shù)字字符,提取年齡和分?jǐn)?shù))
ss.clear(); // 重置狀態(tài)標(biāo)志
ss.seekg(0, ios::beg); // 移動(dòng)指針到開頭
string temp;
int age;
double score;
// 跳過"姓名:李四,年齡:"等非數(shù)字內(nèi)容
while (ss >> temp) {
if (temp == "年齡:") {
ss >> age; // 提取年齡
} else if (temp == "分?jǐn)?shù):") {
ss >> score; // 提取分?jǐn)?shù)
}
}
cout << "解析結(jié)果:" << endl;
cout << "年齡:" << age << endl;
cout << "分?jǐn)?shù):" << score << endl;
return 0;
}? 運(yùn)行結(jié)果:
格式化字符串:姓名:李四,年齡:22,分?jǐn)?shù):88
解析結(jié)果:
年齡:22
分?jǐn)?shù):88
六、IO流的高級(jí)應(yīng)用:自定義類型的IO流
C++支持為自定義類型(如類、結(jié)構(gòu)體)重載>>(輸入)和<<(輸出)運(yùn)算符,使自定義類型可以像基本類型一樣使用IO流操作。
6.1 重載<<運(yùn)算符(輸出)
重載<<運(yùn)算符需定義為全局函數(shù)(或類的友元函數(shù)),參數(shù)為ostream&和自定義類型的引用,返回ostream&(支持鏈?zhǔn)秸{(diào)用)。
6.2 重載>>運(yùn)算符(輸入)
重載>>運(yùn)算符同樣定義為全局函數(shù),參數(shù)為istream&和自定義類型的引用,返回istream&。
?? 示例:自定義Person類的IO流重載
#include <iostream>
#include <string>
#include <<iomanip>
using namespace std;
class Person {
private:
string name;
int age;
double height;
public:
// 構(gòu)造函數(shù)
Person(string n = "", int a = 0, double h = 0.0)
: name(n), age(a), height(h) {}
// 友元函數(shù):重載<<運(yùn)算符(輸出)
friend ostream& operator<<(ostream& os, const Person& p);
// 友元函數(shù):重載>>運(yùn)算符(輸入)
friend istream& operator>>(istream& is, Person& p);
};
// 重載<<運(yùn)算符:輸出Person對(duì)象
ostream& operator<<(ostream& os, const Person& p) {
os << "姓名:" << setw(8) << left << p.name
<< ",年齡:" << setw(3) << p.age
<< ",身高:" << fixed << setprecision(2) << p.height << "m";
return os; // 支持鏈?zhǔn)秸{(diào)用(如cout << p1 << p2)
}
// 重載>>運(yùn)算符:輸入Person對(duì)象
istream& operator>>(istream& is, Person& p) {
cout << "請(qǐng)輸入姓名、年齡、身高(空格分隔):";
is >> p.name >> p.age >> p.height;
// 可選:輸入驗(yàn)證
if (p.age < 0 || p.age > 150) {
is.setstate(ios::failbit); // 設(shè)置失敗狀態(tài)
cerr << "輸入錯(cuò)誤:年齡非法!" << endl;
}
return is;
}
int main() {
Person p1("張三", 20, 1.75);
Person p2;
// 輸出自定義類型(使用重載的<<)
cout << "p1信息:" << p1 << endl;
// 輸入自定義類型(使用重載的>>)
if (cin >> p2) {
cout << "p2信息:" << p2 << endl;
}
// 鏈?zhǔn)捷敵?
Person p3("李四", 22, 1.80);
cout << "\n鏈?zhǔn)捷敵觯? << p1 << " | " << p3 << endl;
return 0;
}? 運(yùn)行結(jié)果:
p1信息:姓名:張三 ,年齡: 20,身高:1.75m
請(qǐng)輸入姓名、年齡、身高(空格分隔):王五 25 1.85
p2信息:姓名:王五 ,年齡: 25,身高:1.85m鏈?zhǔn)捷敵觯盒彰簭埲? ,年齡: 20,身高:1.75m | 姓名:李四 ,年齡: 22,身高:1.80m
七、IO流的常見錯(cuò)誤與最佳實(shí)踐
7.1 常見錯(cuò)誤
錯(cuò)誤1:文件打開失敗未檢查
未檢查文件是否打開成功,直接進(jìn)行讀寫操作,導(dǎo)致程序崩潰或產(chǎn)生無效數(shù)據(jù)。
錯(cuò)誤2:流狀態(tài)異常未處理
如cin輸入類型不匹配、文件讀取到末尾后繼續(xù)讀取,未重置流狀態(tài),導(dǎo)致后續(xù)IO操作失效。
錯(cuò)誤3:二進(jìn)制文件與文本文件模式混用
用文本模式讀取二進(jìn)制文件(如圖片、視頻),會(huì)導(dǎo)致數(shù)據(jù)解析錯(cuò)誤(如換行符轉(zhuǎn)換)。
錯(cuò)誤4:字符串IO流未清空狀態(tài)
重復(fù)使用stringstream時(shí),未調(diào)用clear()重置狀態(tài)標(biāo)志,導(dǎo)致后續(xù)讀取失敗。
錯(cuò)誤5:自定義類型IO流重載錯(cuò)誤
重載<<或>>時(shí)未返回ostream&或istream&,導(dǎo)致鏈?zhǔn)秸{(diào)用失效。
7.2 最佳實(shí)踐
實(shí)踐1:始終檢查文件打開狀態(tài)
打開文件后,通過is_open()或流對(duì)象的布爾值判斷是否打開成功。
實(shí)踐2:及時(shí)處理流狀態(tài)異常
IO操作后檢查流狀態(tài)(如if (!cin)),失敗時(shí)調(diào)用clear()重置狀態(tài),并處理錯(cuò)誤數(shù)據(jù)。
實(shí)踐3:明確文件操作模式
- 文本文件:默認(rèn)模式(不指定
ios::binary),使用>>、<<、getline()操作 - 二進(jìn)制文件:必須指定
ios::binary模式,使用read()、write()操作
實(shí)踐4:合理使用緩沖區(qū)
- 標(biāo)準(zhǔn)輸出流
cout:使用endl刷新緩沖區(qū),或flush()手動(dòng)刷新 - 文件流:大文件讀寫時(shí),可通過
pubsetbuf()設(shè)置更大的緩沖區(qū),提升效率
實(shí)踐5:資源管理
- 文件流:使用完畢后手動(dòng)調(diào)用
close()關(guān)閉文件,或利用RAII(如將文件流封裝在類中,析構(gòu)時(shí)關(guān)閉) - 字符串流:不需要手動(dòng)關(guān)閉,析構(gòu)時(shí)自動(dòng)釋放內(nèi)存
實(shí)踐6:格式化輸出優(yōu)先使用ostringstream
拼接復(fù)雜字符串(如日志、配置)時(shí),優(yōu)先使用ostringstream,避免多次string拼接(效率低)。
八、實(shí)戰(zhàn)案例:配置文件讀寫工具
8.1 問題描述
實(shí)現(xiàn)一個(gè)配置文件讀寫工具,支持:
- 讀取INI格式的配置文件(鍵值對(duì)形式,如
name=張三) - 解析配置項(xiàng)為字符串、整數(shù)、浮點(diǎn)數(shù)類型
- 修改配置項(xiàng)并寫入文件(覆蓋原有內(nèi)容)
- 處理配置文件不存在、格式錯(cuò)誤等異常場(chǎng)景
8.2 實(shí)現(xiàn)思路
- 定義
ConfigReader類,封裝配置文件的讀取、解析、寫入操作 - 使用
map<string, string>存儲(chǔ)配置項(xiàng)(鍵-值對(duì)) - 讀取配置文件:使用
ifstream讀取文件,istringstream解析每行的鍵值對(duì) - 寫入配置文件:使用
ofstream將map中的鍵值對(duì)寫入文件 - 提供
getString()、getInt()、getDouble()方法獲取不同類型的配置值 - 提供
setConfig()方法修改配置項(xiàng)
8.3 代碼實(shí)現(xiàn)
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
#include <string>
#include <stdexcept> // 包含異常類
using namespace std;
class ConfigReader {
private:
string filename; // 配置文件名
map<string, string> config_map; // 存儲(chǔ)鍵值對(duì)
public:
// 構(gòu)造函數(shù):指定配置文件名并讀取配置
ConfigReader(const string& fname) : filename(fname) {
readConfig();
}
// 讀取配置文件
void readConfig() {
ifstream in_file(filename, ios::in);
if (!in_file.is_open()) {
// 配置文件不存在,創(chuàng)建空配置(不拋出異常,允許后續(xù)寫入)
cout << "配置文件" << filename << "不存在,將創(chuàng)建新文件" << endl;
return;
}
string line;
int line_num = 0;
while (getline(in_file, line)) {
line_num++;
// 跳過空行和注釋行(以#開頭)
if (line.empty() || line[0] == '#') {
continue;
}
// 解析鍵值對(duì)(格式:key=value)
size_t eq_pos = line.find('=');
if (eq_pos == string::npos) {
cerr << "警告:第" << line_num << "行格式錯(cuò)誤(無=),跳過該行" << endl;
continue;
}
string key = line.substr(0, eq_pos);
string value = line.substr(eq_pos + 1);
// 去除鍵和值的前后空格
key = trim(key);
value = trim(value);
if (!key.empty()) {
config_map[key] = value;
}
}
in_file.close();
cout << "配置文件讀取完成,共" << config_map.size() << "個(gè)配置項(xiàng)" << endl;
}
// 寫入配置文件(覆蓋原有內(nèi)容)
void writeConfig() {
ofstream out_file(filename, ios::out | ios::trunc);
if (!out_file.is_open()) {
throw runtime_error("配置文件" + filename + "寫入失?。?quán)限不足或路徑錯(cuò)誤)");
}
// 寫入注釋
out_file << "# 配置文件:" << filename << endl;
out_file << "# 格式:key=value(#開頭為注釋,空行忽略)" << endl << endl;
// 寫入鍵值對(duì)
for (const auto& pair : config_map) {
out_file << pair.first << "=" << pair.second << endl;
}
out_file.close();
cout << "配置文件寫入完成!" << endl;
}
// 獲取字符串類型配置
string getString(const string& key, const string& default_val = "") const {
auto it = config_map.find(key);
return (it != config_map.end()) ? it->second : default_val;
}
// 獲取整數(shù)類型配置
int getInt(const string& key, int default_val = 0) const {
auto it = config_map.find(key);
if (it == config_map.end()) {
return default_val;
}
// 字符串轉(zhuǎn)整數(shù)
istringstream iss(it->second);
int val;
if (!(iss >> val)) {
cerr << "警告:配置項(xiàng)" << key << "的值" << it->second << "不是整數(shù),返回默認(rèn)值" << default_val << endl;
return default_val;
}
return val;
}
// 獲取浮點(diǎn)數(shù)類型配置
double getDouble(const string& key, double default_val = 0.0) const {
auto it = config_map.find(key);
if (it == config_map.end()) {
return default_val;
}
istringstream iss(it->second);
double val;
if (!(iss >> val)) {
cerr << "警告:配置項(xiàng)" << key << "的值" << it->second << "不是浮點(diǎn)數(shù),返回默認(rèn)值" << default_val << endl;
return default_val;
}
return val;
}
// 修改配置項(xiàng)
void setConfig(const string& key, const string& value) {
config_map[key] = value;
}
private:
// 輔助函數(shù):去除字符串前后空格
string trim(const string& str) {
size_t start = str.find_first_not_of(" \t\n\r");
size_t end = str.find_last_not_of(" \t\n\r");
return (start == string::npos || end == string::npos) ? "" : str.substr(start, end - start + 1);
}
};
int main() {
try {
// 1. 創(chuàng)建配置讀取器(讀取config.ini)
ConfigReader config("config.ini");
// 2. 獲取配置項(xiàng)(帶默認(rèn)值)
cout << "\n=== 讀取配置項(xiàng) ===" << endl;
string name = config.getString("name", "未知");
int age = config.getInt("age", 18);
double score = config.getDouble("score", 60.0);
string address = config.getString("address", "未設(shè)置");
cout << "姓名:" << name << endl;
cout << "年齡:" << age << endl;
cout << "分?jǐn)?shù):" << score << endl;
cout << "地址:" << address << endl;
// 3. 修改配置項(xiàng)
cout << "\n=== 修改配置項(xiàng) ===" << endl;
config.setConfig("name", "李四");
config.setConfig("age", "22");
config.setConfig("score", "95.5");
config.setConfig("address", "北京市");
// 4. 寫入配置文件
config.writeConfig();
// 5. 重新讀取驗(yàn)證
cout << "\n=== 重新讀取配置項(xiàng) ===" << endl;
config.readConfig(); // 重新讀取文件
cout << "姓名:" << config.getString("name") << endl;
cout << "年齡:" << config.getInt("age") << endl;
cout << "地址:" << config.getString("address") << endl;
} catch (const exception& e) {
cerr << "錯(cuò)誤:" << e.what() << endl;
return 1;
}
return 0;
}8.4 運(yùn)行結(jié)果
首次運(yùn)行(config.ini不存在):
配置文件config.ini不存在,將創(chuàng)建新文件
配置文件讀取完成,共0個(gè)配置項(xiàng)
=== 讀取配置項(xiàng) ===
姓名:未知
年齡:18
分?jǐn)?shù):60
地址:未設(shè)置
=== 修改配置項(xiàng) ===
配置文件寫入完成!
=== 重新讀取配置項(xiàng) ===
配置文件讀取完成,共4個(gè)配置項(xiàng)
姓名:李四
年齡:22
地址:北京市
生成的config.ini文件內(nèi)容:
# 配置文件:config.ini # 格式:key=value(#開頭為注釋,空行忽略) address=北京市 age=22 name=李四 score=95.5
再次運(yùn)行(config.ini已存在):
配置文件讀取完成,共4個(gè)配置項(xiàng) === 讀取配置項(xiàng) === 姓名:李四 年齡:22 分?jǐn)?shù):95.5 地址:北京市 === 修改配置項(xiàng) === 配置文件寫入完成! === 重新讀取配置項(xiàng) === 配置文件讀取完成,共4個(gè)配置項(xiàng) 姓名:李四 年齡:22 地址:北京市
? 結(jié)論:該配置文件工具通過文件IO流實(shí)現(xiàn)了INI文件的讀寫,通過字符串IO流實(shí)現(xiàn)了配置項(xiàng)的解析和格式化,支持多種數(shù)據(jù)類型的配置獲取,處理了文件不存在、格式錯(cuò)誤等異常場(chǎng)景,符合IO流的最佳實(shí)踐。
九、總結(jié)
- C++ IO流將輸入輸出抽象為“流”,通過統(tǒng)一的接口(
>>、<<)實(shí)現(xiàn)控制臺(tái)、文件、內(nèi)存的交互,核心類體系基于ios_base和ios派生。 - 標(biāo)準(zhǔn)IO流(cin、cout、cerr、clog)用于控制臺(tái)交互,支持豐富的格式控制(如精度、對(duì)齊、進(jìn)制)。
- 文件IO流(ifstream、ofstream、fstream)用于文件讀寫,文本文件使用
>>/<</getline(),二進(jìn)制文件使用read()/write(),需指定ios::binary模式。 - 字符串IO流(istringstream、ostringstream、stringstream)用于內(nèi)存數(shù)據(jù)的格式化與解析,適用于日志拼接、配置解析等場(chǎng)景。
- 自定義類型可通過重載
>>和<<運(yùn)算符支持IO流操作,提升代碼靈活性。 - 最佳實(shí)踐:檢查文件打開狀態(tài)、處理流狀態(tài)異常、明確文件模式、合理管理資源,避免常見錯(cuò)誤。
通過本文學(xué)習(xí),你應(yīng)能熟練運(yùn)用C++ IO流處理各種輸入輸出場(chǎng)景,解決實(shí)際開發(fā)中的文件讀寫、數(shù)據(jù)格式化、配置解析等問題。下一篇將深入探討C++的多線程編程基礎(chǔ),開啟并發(fā)編程的大門!
到此這篇關(guān)于C++ IO流詳解:標(biāo)準(zhǔn)IO、文件IO與字符串IO實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)C++ IO流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vscode編譯運(yùn)行c語言報(bào)錯(cuò)亂碼的解決
本文主要介紹了vscode編譯運(yùn)行c語言報(bào)錯(cuò)亂碼,文中通過圖文介紹的的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07

