在 C++/CLI 中,輸入輸出(I/O)透過 串流 概念統一抽象。資料可為 8 位元組或 16 位元 Unicode 字元,分別對應位元組串流(Stream 系)與字元串流(TextReader/TextWriter 系)。本文系統整理標準 I/O、檔案 I/O、字串 I/O、二進制 I/O、隨機存取、檔案目錄操作等核心主題。
.NET I/O 類別主要分兩大體系:
Stream → FileStream, MemoryStream, BufferedStream → BinaryReader/BinaryWriterTextReader → StreamReader, StringReader;TextWriter → StreamWriter, StringWriter此外還有 File, Directory, Path, FileInfo, DirectoryInfo 等輔助類別。
程式啟動時自動開啟三個標準串流:
| 串流 | 屬性 | 型別 | 預設裝置 | 重導向方法 |
|---|---|---|---|---|
| 標準輸入 | Console::In | TextReader | 鍵盤 | Console::SetIn |
| 標準輸出 | Console::Out | TextWriter | 螢幕 | Console::SetOut |
| 標準錯誤 | Console::Error | TextWriter | 螢幕 | Console::SetError |
using namespace System;
using namespace System::IO;
void Copy(TextReader^ inStream, TextWriter^ outStream);
int main() {
TextReader^ inStream = Console::In;
TextWriter^ outStream = Console::Out;
// 讀寫單一字元(需轉型)
outStream->Write(static_cast(inStream->Read()));
outStream->Write(static_cast(inStream->Read()));
outStream->Flush();
// 陣列緩衝讀寫
array^ buffer = {L'w', L'x', L'y', L'z'};
inStream->Read(buffer, 1, 2); // 讀 2 字元存入 buffer[1], buffer[2]
outStream->Write(buffer); // 輸出整個陣列
Copy(inStream, outStream); // 複製剩餘內容至 EOF
// 格式化輸出
outStream->Write("{0} * {1} = {2}\n", 10, 5, 10 * 5);
inStream->Close();
outStream->Close();
}
void Copy(TextReader^ inStream, TextWriter^ outStream) {
int c;
while ((c = inStream->Read()) != -1) {
outStream->Write(static_cast(c));
}
}
關鍵點:
Read() 回傳 int 而非 wchar_t,以便用 -1 表示 EOFFlush() 強制清空緩衝區TextReader/TextWriter 為抽象基類,實際操作多態於衍生類別檔案操作透過 FileStream、StreamReader/StreamWriter 或 File 靜態方法進行。
using namespace System;
using namespace System::IO;
void Copy(TextReader^ inStream, TextWriter^ outStream);
int main(array^ argv) {
if (argv->Length != 2) {
Console::WriteLine("需要兩個參數。");
Environment::Exit(1);
}
try {
// 開啟輸入檔
FileStream^ inFile = gcnew FileStream(argv[0], FileMode::Open);
StreamReader^ inStream = gcnew StreamReader(inFile);
// 等同於:StreamReader^ inStream = File::OpenText(argv[0]);
Console::WriteLine("CanRead={0}, CanWrite={1}", inFile->CanRead, inFile->CanWrite);
// 建立輸出檔
StreamWriter^ outStream = File::CreateText(argv[1]);
Copy(inStream, outStream);
outStream->Write("{0} * {1} = {2}\n", 10, 5, 10 * 5);
inStream->Close();
outStream->Close();
}
catch (FileNotFoundException^ ex) {
Console::WriteLine(ex->Message);
}
catch (IOException^ ex) {
Console::WriteLine(ex);
}
}
注意: catch 區塊順序很重要——FileNotFoundException 繼承自 IOException,必須先捕獲衍生類別。
使用 StringReader/StringWriter 對記憶體字串進行串流操作,底層以 StringBuilder 為緩衝區。
using namespace System;
using namespace System::IO;
using namespace System::Text;
void Copy(TextReader^ inStream, TextWriter^ outStream);
int main() {
String^ str = "abcde";
StringReader^ inStream = gcnew StringReader(str);
StringWriter^ outStream = gcnew StringWriter();
StringBuilder^ sb = outStream->GetStringBuilder();
Console::WriteLine("Capacity is {0}", sb->Capacity); // 預設 16
outStream->Write(static_cast(inStream->Read())); // 'a'
outStream->Write('!'); // '!'
outStream->Write(static_cast(inStream->Read())); // 'b'
outStream->Flush();
outStream->Write("Result = {0,4:0.##}", 10.0/3); // 格式化輸出
Console::WriteLine(outStream); // 隱式呼叫 ToString()
Copy(inStream, outStream); // 複製剩餘 "cde"
Console::WriteLine(outStream);
Console::WriteLine(sb); // 直接輸出 StringBuilder
inStream->Close();
outStream->Close();
}
輸出:
Capacity is 16
a!bResult = 3.33
a!bResult = 3.33cde
a!bResult = 3.33cde
使用 BinaryReader/BinaryWriter 進行型別化的位元組讀寫,支援 bool, char, int, long, float, double, String 等基本型別。
using namespace System;
using namespace System::IO;
int main() {
// 寫入
Stream^ fs = File::Create("io04.dat");
BinaryWriter^ bw = gcnew BinaryWriter(fs);
bw->Write(true);
bw->Write(L'A');
bw->Write(0xabcd);
bw->Write(0x12345678LL);
bw->Write(123.456F);
bw->Write("Hello");
bw->Close();
fs->Close();
// 讀取(須按寫入順序)
fs = File::Open("io04.dat", FileMode::Open);
BinaryReader^ br = gcnew BinaryReader(fs);
Console::WriteLine("bool: " + br->ReadBoolean());
Console::WriteLine("wchar_t: " + br->ReadChar());
Console::WriteLine("int: " + br->ReadInt32());
Console::WriteLine("long long: " + br->ReadInt64());
Console::WriteLine("float: " + br->ReadSingle());
Console::WriteLine("String: " + br->ReadString());
br->Close();
fs->Close();
}
輸出:
bool: True
wchar_t: A
int: 43981
long long: 305419896
float: 123.456
String: Hello
FileStream 支援 Position 與 Seek() 進行隨機定位,需指定 FileAccess::ReadWrite。
using namespace System;
using namespace System::IO;
int main() {
Stream^ fs = gcnew FileStream("Io05.dat", FileMode::Create, FileAccess::ReadWrite);
BinaryWriter^ bw = gcnew BinaryWriter(fs);
BinaryReader^ br = gcnew BinaryReader(fs);
Console::WriteLine("CanRead={0}, CanWrite={1}, CanSeek={2}",
fs->CanRead, fs->CanWrite, fs->CanSeek);
Console::WriteLine("Position at start: {0}", fs->Position);
bw->Write(true); // 位元組 0
long long pos1 = fs->Position; // 記住位置 1
bw->Write(1234); // 位元組 1-4
bw->Write(123.456); // 位元組 5-12
Console::WriteLine("Position at end: {0}", fs->Position); // 13
fs->Position = pos1; // 回到位置 1
bw->Write(5678); // 覆寫 1234 為 5678
fs->Position = 0; // 回到開頭
bw->Write(false); // 覆寫 true 為 false
fs->Seek(0, SeekOrigin::Begin);
Console::WriteLine("bool: " + br->ReadBoolean()); // False
fs->Seek(-1, SeekOrigin::Current); // 回退 1 位元組
Console::WriteLine("bool: " + br->ReadBoolean()); // 再次讀取 False
Console::WriteLine("int: " + br->ReadInt32()); // 5678
fs->Seek(-8, SeekOrigin::End); // 從末尾倒退 8 位元組
Console::WriteLine("double: " + br->ReadDouble()); // 123.456
bw->Close(); br->Close(); fs->Close();
}
輸出:
CanRead is True, CanWrite is True, CanSeek is True
Position at start is 0
Position at end is 13
boolean: False
boolean: False
int: 5678
double: 123.456
警告: 隨機定位至任意位元組可能落在多位元組數值中間,導致讀取無意義資料。
File, Directory, Path, FileInfo, DirectoryInfo 提供豐富的靜態與實例方法。
using namespace System;
using namespace System::IO;
int main() {
String^ fName1 = "Io06";
if (!Path::HasExtension(fName1)) {
fName1 = Path::ChangeExtension(fName1, ".dat");
}
Console::WriteLine("fName1 is {0}", fName1);
StreamWriter^ outStream = File::CreateText(fName1);
outStream->Write("some text");
outStream->Close();
String^ fName2 = "Io06Copy.dat";
Console::WriteLine("File {0} exists: {1}", fName2, File::Exists(fName2));
File::Copy(fName1, fName2);
Console::WriteLine("File {0} exists: {1}", fName2, File::Exists(fName2));
File::Delete(fName2);
Console::WriteLine("File {0} exists: {1}", fName2, File::Exists(fName2));
FileInfo^ f = gcnew FileInfo(fName1);
Console::WriteLine("FullName: {0}", f->FullName);
Console::WriteLine("DirectoryName: {0}", f->DirectoryName);
Console::WriteLine("Name: {0}", f->Name);
Console::WriteLine("Attributes: {0}", f->Attributes);
Console::WriteLine("Length: {0}", f->Length);
Console::WriteLine("CreationTime: {0}", f->CreationTime);
Console::WriteLine("LastAccessTime: {0}", f->LastAccessTime);
Console::WriteLine("LastWriteTime: {0}", f->LastWriteTime);
}
BeginRead/EndRead, BeginWrite/EndWrite 模式,避免阻塞執行緒(本文不詳細展開)StreamReader/StreamWriter 可指定 Encoding(UTF-8, Unicode, BigEndianUnicode 等)| 需求 | 建議類別 |
|---|---|
| 文字檔讀寫 | StreamReader / StreamWriter |
| 記憶體字串讀寫 | StringReader / StringWriter |
| 二進制型別資料 | BinaryReader / BinaryWriter |
| 隨機存取/覆寫 | FileStream + Position/Seek |
| 檔案/目錄管理 | File, Directory, Path, FileInfo, DirectoryInfo |
| 標準輸入輸出 | Console::In, Console::Out, Console::Error |