@TOC


一、C語言的輸入輸出機制

C語言中,輸入輸出主要依賴scanf()printf() 兩個核心函數。

  • scanf():從鍵盤等標準輸入設備讀取數據,將其存儲到指定變量中。
  • printf():將格式化後的內容輸出到屏幕等標準輸出設備。

C語言的輸入輸出依賴緩衝區實現,其核心作用有兩點:

  1. 屏蔽低級IO的系統差異,讓程序更具可移植性。
  2. 實現“行”讀取行為,為輸入輸出提供有序性支持。

二、C++中的“流”是什麼

“流”是對有序、連續且具有方向性的數據傳輸過程的抽象描述,本質是信息在外部設備與計算機內存之間的流動——既包括外部設備向內存的“輸入流”,也包括內存向外部設備的“輸出流”。

為實現這種流動,C++提供了標準IO類庫,所有相關類均直接或間接繼承自ios基類,核心頭文件包括<ios>``<istream>``<iostream>``<fstream>``<sstream>,構成了完整的流操作體系。

三、C++標準IO流詳解

C++標準庫封裝了便捷的標準IO流功能,涵蓋全局流對象、多場景輸入輸出及自定義類型適配。

C++ IO流_字符串

3.1 四個全局流對象

C++提供4個全局流對象,均屬於ostream類的實例,應用場景各有側重:

  • cin:標準輸入流,數據從外部設備流向程序,屬於緩衝流。
  • cout:標準輸出流,數據從程序流向控制枱(屏幕)。
  • cerr:標準錯誤輸出流,專門用於輸出錯誤信息。
  • clog:日誌輸出流,用於輸出程序運行日誌。

使用cin和cout的注意事項:

  1. cin讀取數據時從緩衝區提取,輸入過多時數據會暫存緩衝區,按需提取;輸入錯誤需在回車前修正,且緩衝區讀取完畢才會要求新輸入。
  2. 輸入數據類型必須與目標變量類型一致,否則會觸發錯誤。
  3. 空格和回車可作為數據分隔符,支持多行或單行輸入;但cin讀取字符串時會以空格為終止符,無法讀取含空格的字符串(需用getline()函數,遇'\n'終止)。
  4. 內置類型可直接輸入輸出(標準庫已重載運算符),自定義類型需手動重載輸入輸出運算符。

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;
}

C++ IO流_數據_02

重載後的運算符支持與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;
}

C++ IO流_數據_03

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 關鍵注意事項

  1. stringstream底層維護一個string對象存儲數據,多次轉換時需用clear()清空狀態(不清除內容),用str("")清空底層字符串。
  2. 避免緩衝區溢出問題,自動適配數據類型,無需格式化控制符,比C語言的itoa()、sprintf()更安全便捷。
  3. 支持任意內置類型與字符串的雙向轉換,自定義類型需提前重載輸入輸出運算符。