💬 :如果你在閱讀過程中有任何疑問或想要進一步探討的內容,歡迎在評論區暢所欲言!我們一起學習、共同成長~!
👍 :如果你覺得這篇文章還不錯,不妨順手點個贊、加入收藏,并分享給更多的朋友噢~!
1. C 語言的輸入輸出
1.1?常用輸入輸出函數
C 語言中最常用的輸入輸出方式是?scanf()?
與?printf()?
。
scanf()
:從標準輸入流(通常是鍵盤)讀取格式化數據,按照指定的格式將輸入數據存儲到對應的變量中。printf()
:將格式化的數據輸出到標準輸出流(通常是顯示器)。使用時需注意寬度輸出和精度輸出控制。
1.2?輸入輸出緩沖區
C 語言借助相應的緩沖區來進行輸入與輸出,輸入輸出緩沖區作用如下:
- 屏蔽低級 I/O 的實現:低級 I/O 的實現依賴操作系統本身內核,屏蔽這部分差異可使程序更具可移植性。
- 實現 “行” 讀取行為:計算機本身沒有 “行” 的概念,通過緩沖區可定義 “行”,并解析其內容返回 “行”。
2. 流是什么
“流” 是對有序連續且具有方向性的數據的抽象描述。
C++ 流指信息從外部輸入設備(如鍵盤)向計算機內部(如內存)輸入,以及從內存向外部輸出設備(顯示器)輸出的過程。
C++ 流的特性:
- 有序連續:數據按順序流動。
- 有方向性:分為輸入流和輸出流。
- 基于 I/O 標準類庫:通過類對象實現輸入輸出操作。
3.?C++ ?IO 流
C++ 中構建了一個體系龐大的類庫,在這個類庫的繼承體系里,ios
類是基類,其他所有相關類均直接或間接派生自ios
類。
3.1?C++ 標準?IO 流
3.1.1?全局流對象
C++ 標準庫提供了 4 個全局流對象:cin
、cout
、cerr
、clog
。
cin
:標準輸入流(鍵盤輸入到程序),屬于istream
類。cout
:標準輸出流(程序輸出到顯示器),屬于ostream
類。cerr
:標準錯誤輸出流(無緩沖,直接輸出)。clog
:標準日志輸出流(有緩沖)。
cout
、cerr
、clog
是ostream
類的三個不同對象,應用場景不同,但基本功能類似。使用時必須包含頭文件<iostream>
,并引入std
標準命名空間。
3.1.2 關鍵細節
3.1.2.1 緩沖機制
cin
為緩沖流,輸入數據先存入緩沖區,提取時從緩沖區讀取。- 輸入錯誤會設置流狀態字
state
,但程序繼續執行。
3.1.2.2 數據類型匹配
- 輸入類型需與提取類型一致,否則出錯(如給
int
變量輸入字符將出錯)。
3.1.2.3 分隔符處理
- 空格和回車可作為數據分隔符,字符型和字符串無法讀取空格或回車。
3.1.2.4 內置類型支持
- 標準庫已重載
<<
和>>
運算符,可直接輸入輸出內置類型(如int
、double
)。
3.1.2.5 自定義類型支持
- 需重載
<<
和>>
運算符才能使用cin
/cout
。
3.1.2.6 在線 OJ 輸入輸出
<1> IO類型的算法,一般都要循環輸入:
(1)單個元素循環輸入
適用場景:讀取多組單一類型數據(如多組整數)。
?
int num;
while (cin >> num)
{ // 輸入整數,遇EOF結束cout << "輸入的數:" << num << endl;
}
(2)多個元素循環輸入
適用場景:讀取每行包含多個數據的輸入(如每行輸入 “姓名 年齡 分數”)。
string name;
int age;
double score;
while (cin >> name >> age >> score)
{ // 按順序讀取多個元素cout << name << " " << age << " " << score << endl;
}
(3)非整行字符串輸入(不含空格)
適用場景:讀取以空格 / 換行分隔的單詞(如多個獨立字符串)。
string word;
while (cin >> word)
{ // 讀取單個單詞,遇空格/換行/EOF結束cout << "單詞:" << word << endl;
}
(4)整行字符串輸入(含空格)
適用場景:讀取完整句子或段落(如 “Hello, world!”)。
string line;
while (getline(cin, line))
{ // 讀取整行,包括空格,遇EOF或空行結束if (line.empty()) break; // 可選:跳過空行cout << "整行內容:" << line << endl;
}
<2> 輸出必須與題目要求完全一致,包括空格、換行符、標點符號等,多一個或少一個字符均可能導致錯誤。
<3> Windows 系統的 VS 系列編譯器中,通過輸入?Ctrl + Z?
?再按下?Enter?,就可發送?EOF(文件結束符)?信號,快捷終止輸入循環。
3.1.2.7 流對象的邏輯判斷
- istream 對象能直接用于條件判斷。若輸入成功,則繼續執行相關操作;若輸入失敗,則停止執行相關操作。
- 自定義類型可通過重載?
operator bool()
?函數來添加額外的邏輯判斷。這些額外邏輯判斷通常用于設置業務層面的結束條件。
istream
?對象可作為邏輯條件值,是因為該對象會調用?operator bool
?函數。
當流提取成功時,operator bool
返回true;
當遇到文件結束符(EOF
,如鍵盤輸入Ctrl+Z
)、提取類型不匹配(如給int
變量輸入字符)或流被關閉時,operator bool
返回false
,循環終止。
示例:
#include <iostream>
#include <string>
using namespace std;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) {}// 類型轉換運算符:定義Date對象的邏輯判斷條件explicit operator bool() const {// 假設輸入_year為0時,返回falsereturn _year != 0;}private:int _year;int _month;int _day;
};// 重載流提取運算符:從輸入流讀取Date對象
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day; return in; // 返回輸入流對象,支持鏈式調用(如cin >> d1 >> d2)
}// 重載流插入運算符:向輸出流寫入Date對象
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "-" << d._day; return out; // 返回輸出流對象,支持鏈式調用(如cout << d << endl)
}int main()
{// 輸出內置類型(自動調用標準庫重載的<<運算符)int i = 1;double j = 2.2;cout << "內置類型輸出:" << endl;cout << "整數i = " << i << endl;cout << "浮點數j = " << j << endl;// 輸出自定義類型Date(調用重載的<<運算符)Date d(2022, 4, 10); cout << "\n初始化的日期:" << d << endl;// 循環輸入Date對象,直到輸入年份為0時結束cout << "\n請輸入日期(格式:年 月 日,輸入0 0 0結束):" << endl;while (cin >> d) { // 用operator>>返回的istream對象調用operator bool// 輸入成功后,檢查Date對象的邏輯狀態(_year是否為0)if (!d) { cout << "檢測到結束標志(年份為0),退出程序。" << endl;break;}cout << "輸入的日期為:" << d << endl; cout << "請繼續輸入(或輸入0 0 0結束):" << endl;}return 0;
}
3.2?C++ 文件?IO 流
根據數據的組織形式,數據文件可分為二進制文件和文本文件。
- 數據在內存中以二進制形式存儲,若不加轉換輸出到外存文件中,就是二進制文件;
- 若要求在外存上以 ASCII 碼形式存儲,則需在存儲前轉換,以 ASCII 字符形式存儲的文件就是文本文件。
采用文件流對象操作文件的一般步驟:
- (1)定義文件流對象
類名 | 用途 | 定義語法 | 示例 |
---|---|---|---|
ifstream | 只讀取文件 | 1.直接構造并打開文件:
| |
ofstream | 只寫入文件 | 1. 直接構造并打開文件(覆蓋寫入):
| 類似?ifstream |
fstream | 讀寫文件 | fstream 對象名("文件名", 模式); (模式必須包含? ios_base::in ?|?ios_base::out ?) | |
注意:ifstream /?ofstream /?fstream
?均需包含頭文件?<fstream> ;ifstream 、ofstream 、fstream
?、ios_base 要展開命名空間或使用?std::
?前綴。
- (2)打開文件
- 成員函數:
open(const char* filename, ios_base::mode)
。 - 常用打開模式:
ios_base::in
:輸入模式(讀文件)。ios_base::out
:輸出模式(寫文件,覆蓋原有內容)。ios_base::app
:追加模式(寫文件,內容追加到末尾)。ios_base::binary
:二進制模式(默認為文本模式)。
- 成員函數:
- (3)讀寫文件
- 文本讀寫:使用
<<
和>>
運算符(需重載自定義類型運算符)。 - 二進制讀寫:使用
write()和read()
成員函數(按字節操作)。
- 文本讀寫:使用
- (4)關閉文件:調用
close()
成員函數。
3.2.1?模擬服務器配置信息的讀寫
#include <iostream>
#include <fstream>
#include <string>
using namespace std; class Date
{
public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day) {}friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);private:int _year; int _month; int _day;
};ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "-" << d._day;return out;
}istream& operator>>(istream& in, Date& d)
{char separator1, separator2; // 用于讀取日期中的分隔符(如'-')in >> d._year >> separator1 >> d._month >> separator2 >> d._day;return in;
}// 服務器信息結構體
struct ServerInfo
{char _address[32]; // 地址(字符數組)int _port; // 端口號Date _date; // 日期(已重載IO運算符)
};// 配置管理類
class ConfigManager
{
public:// 構造函數:初始化文件名explicit ConfigManager(const char* filename) : _filename(filename) {}// 二進制寫入文件void WriteBin(const ServerInfo& info) {ofstream ofs(_filename, ios_base::out | ios_base::binary); if (!ofs.is_open()) { // 檢查文件是否打開成功cerr << "Error: Failed to open file for writing (binary)." << endl;return;}// 寫入結構體數據(需保證結構體無虛函數、無動態成員,否則需自定義序列化邏輯)ofs.write(reinterpret_cast<const char*>(&info), sizeof(info));ofs.close();}// 二進制讀取文件void ReadBin(ServerInfo& info) {ifstream ifs(_filename, ios_base::in | ios_base::binary);if (!ifs.is_open()) { cerr << "Error: Failed to open file for reading (binary)." << endl;return;}// 讀取結構體數據ifs.read(reinterpret_cast<char*>(&info), sizeof(info));ifs.close();}// 文本寫入文件void WriteText(const ServerInfo& info) {ofstream ofs(_filename);if (!ofs.is_open()) { cerr << "Error: Failed to open file for writing (text)." << endl;return;}// 使用流插入運算符寫入數據(需保證Date類已重載<<)ofs << info._address << " " << info._port << " " << info._date << endl;ofs.close();}// 文本讀取文件void ReadText(ServerInfo& info) {ifstream ifs(_filename);if (!ifs.is_open()) { cerr << "Error: Failed to open file for reading (text)." << endl;return;}// 使用流提取運算符讀取數據(需保證Date類已重載>>)ifs >> info._address >> info._port >> info._date;ifs.close();}private:string _filename; // 配置文件名
};int main()
{// 初始化服務器信息ServerInfo winfo = {"192.0.0.1", // 地址80, // 端口號{2022, 4, 10} // 日期(使用Date構造函數初始化)};// 創建配置管理器對象(二進制和文本文件)ConfigManager cf_bin("test.bin");ConfigManager cf_text("test.txt");// ---------------------- 二進制操作 ----------------------// 寫入二進制文件cf_bin.WriteBin(winfo);cout << "二進制文件寫入完成。" << endl;// 讀取二進制文件ServerInfo rbinfo;cf_bin.ReadBin(rbinfo);cout << "二進制讀取結果:" << endl;cout << "地址:" << rbinfo._address << endl;cout << "端口:" << rbinfo._port << endl;cout << "日期:" << rbinfo._date << endl;cout << endl;// ---------------------- 文本操作 ----------------------// 寫入文本文件cf_text.WriteText(winfo);cout << "文本文件寫入完成。" << endl;// 讀取文本文件ServerInfo rtinfo;cf_text.ReadText(rtinfo);cout << "文本讀取結果:" << endl;cout << "地址:" << rtinfo._address << endl;cout << "端口:" << rtinfo._port << endl;cout << "日期:" << rtinfo._date << endl;return 0;
}
4. 簡單介紹?stringstream
傳統C語言方法(如 sprintf / itoa )在進行類型轉換與字符串操作時,存在緩沖區溢出風險且需手動管理內存空間,尤其在處理結構體數據的序列化與反序列化時不夠安全便捷。
stringstream 通過流式操作實現自動類型推導,并利用安全的string緩沖區管理,有效簡化了類型轉換與字符串拼接過程,規避了上述風險,更適用于序列化場景。
4.1 頭文件與類定義
- 頭文件:
#include <sstream>
- 相關類:
istringstream
:從字符串讀取數據(輸入流)。ostringstream
:向字符串寫入數據(輸出流)。stringstream
:雙向操作(讀寫字符串)。
4.2 核心功能
4.2.1 數值類型轉字符串
- 避免
itoa()
或sprintf()
的緩沖區溢出問題,自動推導類型。
示例代碼:
#include <iostream>
#include <sstream>
#include <string> // 包含string類型頭文件using namespace std; int main()
{int a = 12345678; string sa; // 定義用于存儲轉換結果的string對象stringstream s; // 定義stringstream對象,用于數據轉換// 第一次轉換:將整數a轉換為字符串s << a; // 向stringstream中插入int類型數據(自動格式化)s >> sa; // 從stringstream中提取數據到string對象sacout << "整數轉字符串結果:" << sa << endl; // ---------------------- 關鍵操作:清空流狀態和底層緩沖區 ----------------------s.clear(); // 重置流狀態標志(清除可能的badbit狀態)s.str(""); // 清空stringstream底層維護的string對象,避免殘留數據影響下次轉換// ----------------------------------------------------------------------------// 第二次轉換:將雙精度浮點數d轉換為字符串double d = 12.34; s << d; // 向stringstream中插入double類型數據(自動格式化)s >> sa; // 從stringstream中提取數據到string對象sacout << "浮點數轉字符串結果:" << sa << endl; return 0;
}
4.2.2 字符串拼接
- 方便合并多個字符串或變量。
示例代碼:
#include <iostream>
#include <sstream>
#include <string> using namespace std; int main()
{// 創建stringstream對象用于字符串操作stringstream sstream;// 向流中插入多個字符串進行拼接sstream << "Hello" << " " << "World," << " 你好!";// 從stringstream中提取拼接后的完整字符串string result = sstream.str();cout << "拼接結果:" << result << endl; return 0;
}
4.2.3 序列化與反序列化結構數據
- 將結構體數據轉為字符串(如網絡傳輸),或從字符串解析回結構體。
示例代碼:
#include <iostream>
#include <sstream>
#include <string>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1): _year(year), _month(month), _day(day) {}friend istream& operator>>(istream& in, Date& d);friend ostream& operator<<(ostream& out, const Date& d);private:int _year;int _month;int _day;
};istream& operator>>(istream& in, Date& d)
{char separator1, separator2; // 用于處理日期格式中的分隔符(如YYYY-MM-DD)in >> d._year >> separator1 >> d._month >> separator2 >> d._day;return in;
}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "-" << d._day; return out;
}struct ChatInfo
{string _name;int _id;Date _date; // 假設已重載<<和>>string _msg;
};int main()
{// 序列化:結構體轉字符串ChatInfo winfo = {"張三",135246,{2022, 4, 10}, // 使用Date構造函數初始化日期"晚上一起看電影吧"};ostringstream oss;// 按順序輸出結構體成員,用空格分隔(需注意字符串中的空格會導致反序列化問題)oss << winfo._name << " "<< winfo._id << " "<< winfo._date << " "<< winfo._msg; // 假設_msg中不含空格,否則需特殊處理(如轉義或使用其他分隔符)string str = oss.str();cout << "序列化字符串:" << str << endl;// 反序列化:字符串轉結構體ChatInfo rInfo;istringstream iss(str);// 按順序讀取數據,與序列化順序嚴格一致iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg;// 檢查反序列化是否成功(處理可能的讀取錯誤)if (iss.fail()) {cerr << "反序列化失敗:輸入格式錯誤" << endl;return 1;}cout << "\n反序列化結果:" << endl;cout << "姓名:" << rInfo._name << "(" << rInfo._id << ")" << endl;cout << "時間:" << rInfo._date << endl;cout << "消息:" << rInfo._msg << endl;return 0;
}
4.3 注意事項
- 狀態與緩沖區管理
clear()
:重置流狀態(如badbit
),但不清空底層字符串。str("")
:清空底層string
對象,避免多次轉換時數據累積。
- 安全性
- 使用
string
代替字符數組,避免緩沖區溢出。 - 自動類型推導,無需手動格式化,減少錯誤風險。
- 使用