@TOC
一、C語言的輸入輸出機制
C語言中,輸入輸出主要依賴scanf() 和printf() 兩個核心函數。
- scanf():從鍵盤等標準輸入設備讀取數據,將其存儲到指定變量中。
- printf():將格式化後的內容輸出到屏幕等標準輸出設備。
C語言的輸入輸出依賴緩衝區實現,其核心作用有兩點:
- 屏蔽低級IO的系統差異,讓程序更具可移植性。
- 實現“行”讀取行為,為輸入輸出提供有序性支持。
二、C++中的“流”是什麼
“流”是對有序、連續且具有方向性的數據傳輸過程的抽象描述,本質是信息在外部設備與計算機內存之間的流動——既包括外部設備向內存的“輸入流”,也包括內存向外部設備的“輸出流”。
為實現這種流動,C++提供了標準IO類庫,所有相關類均直接或間接繼承自ios基類,核心頭文件包括<ios>``<istream>``<iostream>``<fstream>``<sstream>,構成了完整的流操作體系。
三、C++標準IO流詳解
C++標準庫封裝了便捷的標準IO流功能,涵蓋全局流對象、多場景輸入輸出及自定義類型適配。
3.1 四個全局流對象
C++提供4個全局流對象,均屬於ostream類的實例,應用場景各有側重:
- cin:標準輸入流,數據從外部設備流向程序,屬於緩衝流。
- cout:標準輸出流,數據從程序流向控制枱(屏幕)。
- cerr:標準錯誤輸出流,專門用於輸出錯誤信息。
- clog:日誌輸出流,用於輸出程序運行日誌。
使用cin和cout的注意事項:
- cin讀取數據時從緩衝區提取,輸入過多時數據會暫存緩衝區,按需提取;輸入錯誤需在回車前修正,且緩衝區讀取完畢才會要求新輸入。
- 輸入數據類型必須與目標變量類型一致,否則會觸發錯誤。
- 空格和回車可作為數據分隔符,支持多行或單行輸入;但cin讀取字符串時會以空格為終止符,無法讀取含空格的字符串(需用getline()函數,遇'\n'終止)。
- 內置類型可直接輸入輸出(標準庫已重載運算符),自定義類型需手動重載輸入輸出運算符。
3.2 OJ題中的輸入輸出技巧
OJ題常需處理多組測試用例,核心是循環讀取輸入,直到流結束:
int main(){
int year, month, day;
string str;
// 循環讀取,直到輸入結束(Ctrl+Z+回車或文件結束)
while (cin >> str) {
year = stoi(str.substr(0, 4));
month = stoi(str.substr(4, 2));
day = stoi(str.substr(6, 2));
cout << year << "年" << month << "月" << day << "日" << endl;
}
return 0;
}
cin可直接作為循環條件,源於istream類重載了bool類型轉換,流結束時返回false。
3.3 自定義類型的輸入輸出重載
自定義類型需通過重載>>(輸入)和<<(輸出)運算符,才能支持流操作:
class Date{
// 友元函數,允許訪問私有成員
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year), _month(month), _day(day) {}
private:
int _year, _month, _day;
};
// 輸出運算符重載
ostream& operator << (ostream& out, const Date& d){
out << d._year << " " << d._month << " " << d._day;
return out;
}
// 輸入運算符重載
istream& operator >> (istream& in, Date& d){
in >> d._year >> d._month >> d._day;
return in;
}
// 使用示例
int main(){
Date d(2024, 9, 11);
cout << d << endl; // 輸出:2024 9 11
cin >> d;
cout << d << endl;
return 0;
}
重載後的運算符支持與cout、cin直接配合,且可複用至文件流、字符串流(基於繼承體系)。
四、C++文件IO流操作
文件IO流用於實現程序與外部文件的數據交互,核心是通過專門的流類操作文件。
4.1 文件操作核心要素
4.1.1 核心操作類
|
類名
|
功能
|
|
fstream
|
支持文件的讀寫操作
|
|
ifstream
|
僅支持文件的讀取操作
|
|
ofstream
|
僅支持文件的寫入操作
|
4.1.2 文件打開方式
|
打開方式
|
功能描述
|
|
in
|
以只讀模式打開文件
|
|
out
|
以只寫模式打開文件
|
|
binary
|
以二進制模式操作文件
|
|
ate
|
打開文件後定位到文件末尾
|
|
app
|
以追加模式寫入文件
|
|
trunc
|
打開文件前清空文件內容
|
4.1.3 常用操作函數
|
函數名
|
功能描述
|
|
put
|
向文件寫入單個字符
|
|
write
|
向文件寫入字符串或字節流
|
|
get
|
從文件讀取單個字符
|
|
read
|
從文件讀取多個字符或字節流
|
|
tellg
|
獲取當前讀寫位置
|
|
seekg
|
設置文件讀寫位置
|
|
<<
|
流格式寫入數據(重載)
|
|
>>
|
流格式讀取數據(重載)
|
4.1.4 基礎操作演示
// 寫入文件
void writeFile() {
ofstream ofs("test.txt", ofstream::out | ofstream::app);
ofs << "C++ 文件 IO 測試" << endl;
}
// 讀取文件
void readFile() {
ifstream ifs("test.txt");
int num;
string str;
Date d;
ifs >> num >> str >> d; // 支持內置類型和自定義類型
cout << num << " " << str << " " << d << endl;
}
4.2 二進制與文本讀寫對比
4.2.1 核心差異演示
struct ServerInfo {
string _address;
int _port;
Date _date;
};
// 二進制寫入與讀取
void binaryIO() {
ServerInfo info = {"127.0.0.1", 8888, {2024, 9, 11}};
// 寫入
ofstream ofs("server.bin", ios::out | ios::binary);
ofs.write((char*)&info, sizeof(info));
// 讀取
ServerInfo readInfo;
ifstream ifs("server.bin", ios::in | ios::binary);
ifs.read((char*)&readInfo, sizeof(readInfo));
}
// 文本寫入與讀取
void textIO() {
ServerInfo info = {"127.0.0.1", 8888, {2024, 9, 11}};
// 寫入
ofstream ofs("server.txt", ios::out);
ofs << info._address << endl << info._port << endl << info._date << endl;
// 讀取
ServerInfo readInfo;
ifstream ifs("server.txt", ios::in);
ifs >> readInfo._address >> readInfo._port >> readInfo._date;
}
4.2.2 優缺點對比
- 二進制讀寫:按數據在內存中的原始格式寫入文件,速度快,但內容不可直接查看,且不適合含指針/string等動態類型的數據(易出現野指針問題)。
- 文本讀寫:將數據序列化為字符串格式寫入,內容可直接打開查看,兼容性強,但存在格式轉換開銷,速度略慢。
五、stringstream的功能與用法
stringstream是C++標準庫提供的字符串流類,包含istringstream(輸入)、ostringstream(輸出)、stringstream(讀寫)三類,核心用於字符串與其他類型的轉換及數據拼接。
5.1 核心功能應用
5.1.1 數值類型轉字符串
無需手動分配緩衝區,自動適配類型,避免格式化錯誤:
#include <sstream>
int main() {
int a = 12345;
double b = 3.1415;
stringstream ss;
// 整形轉字符串
ss << a;
string strA = ss.str();
cout << strA << endl; // 輸出:12345
// 清空狀態與內容,準備下次轉換
ss.clear();
ss.str("");
// 浮點型轉字符串
ss << b;
string strB = ss.str();
cout << strB << endl; // 輸出:3.1415
return 0;
}
5.1.2 字符串拼接
支持多類型數據無縫拼接,無需手動處理分隔符:
int main() {
stringstream ss;
ss << "姓名:" << "張三" << ",ID:" << 1001 << ",時間:" << Date(2024, 9, 11);
string result = ss.str();
cout << result << endl; // 輸出:姓名:張三,ID:1001,時間:2024 9 11
return 0;
}
5.1.3 數據序列化與反序列化
常用於網絡傳輸、數據存儲場景,將複雜數據轉為字符串(序列化),或反向解析(反序列化):
struct ChatInfo {
string _name;
int _id;
Date _date;
string _msg;
};
int main() {
// 序列化:將ChatInfo轉為字符串
ChatInfo sendInfo = {"李四", 1002, {2024, 9, 11}, "明天一起編程"};
stringstream ssOut;
ssOut << sendInfo._name << endl << sendInfo._id << endl << sendInfo._date << endl << sendInfo._msg;
string data = ssOut.str();
// 反序列化:從字符串解析為ChatInfo
ChatInfo recvInfo;
stringstream ssIn(data);
ssIn >> recvInfo._name >> recvInfo._id >> recvInfo._date >> recvInfo._msg;
// 輸出解析結果
cout << recvInfo._name << "[" << recvInfo._id << "] " << recvInfo._date << ":" << recvInfo._msg << endl;
return 0;
}
5.2 關鍵注意事項
- stringstream底層維護一個string對象存儲數據,多次轉換時需用
clear()清空狀態(不清除內容),用str("")清空底層字符串。 - 避免緩衝區溢出問題,自動適配數據類型,無需格式化控制符,比C語言的itoa()、sprintf()更安全便捷。
- 支持任意內置類型與字符串的雙向轉換,自定義類型需提前重載輸入輸出運算符。