ورودی/خروجی: جریانها، قالببندی و std::print
بر ورودی/خروجی C++ از iostream کلاسیک تا std::format و std::print مدرن، شامل حالتهای جریان، ورودی/خروجی فایل و دامهای رایج بافر مسلط شوید.
تکامل ورودی/خروجی C++
ورودی/خروجی C++ در طول تاریخ زبان به طور قابل توجهی تکامل یافته است. کتابخانه کلاسیک iostream (std::cin، std::cout) از C++98 استاندارد بوده، اما مشکلات ارگونومیک و عملکردی شناختهشدهای دارد. C++20 std::format را معرفی کرد، کتابخانه قالببندی ایمن از نظر نوع الهامگرفته از str.format() پایتون. C++23 std::print را اضافه کرد که قالببندی و خروجی را در یک فراخوانی واحد ترکیب میکند.
درک هر سه رویکرد مهم است: iostream در همه جای کد موجود هست، std::format روش مدرن قالببندی رشتهها است، و std::print آینده خروجی C++ است.
ورودی/خروجی کلاسیک: cout، cin، cerr
کتابخانه iostream اشیاء جریان برای ورودی، خروجی و خروجی خطای استاندارد فراهم میکند:
#include <iostream>
#include <string>
#include <limits> // For numeric_limits
int main() {
// Output with cout
std::cout << "Hello, " << "World!" << '\n'; // Chained insertion
std::cout << "The answer is: " << 42 << '\n'; // Automatic type formatting
// Error output — cerr is unbuffered (immediate), clog is buffered
std::cerr << "Error: something went wrong\n";
// Input with cin
std::cout << "Enter your name: ";
std::string name;
std::getline(std::cin, name); // Read entire line (including spaces)
std::cout << "Enter your age: ";
int age{};
std::cin >> age; // Read a single value (stops at whitespace)
// Check if input succeeded
if (std::cin.fail()) {
std::cerr << "Invalid input!\n";
std::cin.clear(); // Reset error flags
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // Discard bad input
}
// The getline-after-cin trap:
// After cin >> age, a '\n' remains in the buffer.
// A subsequent getline() would read an empty string!
// Fix: call cin.ignore() before getline():
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::string city;
std::cout << "Enter your city: ";
std::getline(std::cin, city); // Now works correctly
std::cout << name << " (" << age << ") from " << city << '\n';
}برای کامپایل و اجرای این نمونه، روی اجرا بزنید.
پرچمهای حالت جریان
هر جریان مجموعهای از پرچمهای حالت نگه میدارد که وضعیت فعلی آن را نشان میدهند:
- goodbit — بدون خطا. جریان برای عملیات ورودی/خروجی آماده است.
- eofbit — انتهای فایل/ورودی رسیده. وقتی جریان تلاش میکند فراتر از انتها بخواند تنظیم میشود.
- failbit — خطای منطقی رخ داده (مثلاً تلاش برای خواندن عدد صحیح اما مواجهه با متن). جریان پس از فراخوانی clear() همچنان قابل استفاده است.
- badbit — خطای غیرقابل بازیابی ورودی/خروجی رخ داده (مثلاً خرابی دیسک). جریان معمولاً غیرقابل استفاده است.
اینها را با stream.good()، stream.eof()، stream.fail()، stream.bad() یا با استفاده از جریان در زمینه بولین بررسی کنید: if (std::cin) معادل if (!std::cin.fail()) است.
الگوی حیاتی: همیشه حالت جریان را پس از عملیات ورودی بررسی کنید، نه قبل. الگوی رایج while (std::cin >> value) خواندن و بررسی را در یک مرحله انجام میدهد — وقتی خواندن شکست بخورد متوقف میشود.
رایجترین باگ ورودی/خروجی در C++ شامل ترکیب >> (استخراج قالببندیشده) با std::getline() است. عملگر >> مقداری میخواند اما خط جدید انتهایی را در بافر باقی میگذارد. سپس std::getline() بعدی آن خط جدید باقیمانده را به عنوان رشته خالی میخواند و به نظر میرسد ورودی را رد کرده است.
راهحل فراخوانی std::cin.ignore(std::numeric_limits بین >> و std::getline() برای دور ریختن خط جدید باقیمانده است.
رویکرد دیگر استفاده از std::getline() برای تمام ورودی و تجزیه رشتهها خودتان با std::istringstream یا std::stoi/std::stod است. این مشکل را کاملاً برطرف و مدیریت خطای بهتری فراهم میکند.
- پس از cin >> x، خط جدید در بافر باقی میماند — getline() بعدی آن را به عنوان رشته خالی میخواند
- راهحل: بین >> و getline() فراخوانی cin.ignore(numeric_limits
::max(), '\n') کنید - جایگزین: از getline() برای تمام ورودی استفاده کنید و رشتهها را با stoi/stod یا istringstream تجزیه کنید
- همیشه حالت جریان را پس از ورودی بررسی کنید: if (cin >> x) به جای خواندن کورکورانه
ورودی/خروجی فایل
ورودی/خروجی فایل از همان رابط جریان ورودی/خروجی کنسول استفاده میکند، با std::ifstream برای خواندن و std::ofstream برای نوشتن:
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
int main() {
// Writing to a file
{
std::ofstream out("scores.txt"); // Opens for writing (creates or truncates)
if (!out) {
std::cerr << "Failed to open file for writing\n";
return 1;
}
out << "Alice 95\n";
out << "Bob 87\n";
out << "Charlie 92\n";
} // File is automatically closed when 'out' goes out of scope (RAII!)
// Reading from a file
std::ifstream in("scores.txt");
if (!in) {
std::cerr << "Failed to open file for reading\n";
return 1;
}
std::string name;
int score{};
std::vector<std::pair<std::string, int>> results;
while (in >> name >> score) { // Read until failure (EOF or bad data)
results.emplace_back(name, score);
}
for (const auto& [n, s] : results) {
std::cout << n << ": " << s << '\n';
}
// Appending to a file
std::ofstream append("scores.txt", std::ios::app); // Open in append mode
append << "Diana 98\n";
// Reading entire file line by line
std::ifstream in2("scores.txt");
std::string line;
while (std::getline(in2, line)) {
std::cout << "Line: " << line << '\n';
}
}برای کامپایل و اجرای این نمونه، روی اجرا بزنید.
قالببندی مدرن: std::format و std::print
std::format در C++20 و std::print در C++23 قالببندی ایمن از نظر نوع و شبیه پایتون فراهم میکنند که هم ایمنتر و هم خواناتر از دستکاریکنندههای iostream هستند:
#include <format> // C++20
#include <print> // C++23
#include <string>
#include <vector>
#include <numbers> // C++20: mathematical constants
int main() {
// std::format (C++20) — returns a formatted std::string
std::string msg = std::format("Hello, {}!", "World");
// msg == "Hello, World!"
// Positional arguments
std::string pos = std::format("{1} comes before {0}", "second", "first");
// pos == "first comes before second"
// Formatting specifiers
std::string nums = std::format(
"int: {:>10d}\n" // Right-aligned, width 10, decimal
"hex: {:#06x}\n" // '0x' prefix, width 6, zero-padded hex
"float: {:.3f}\n" // 3 decimal places, fixed notation
"sci: {:.2e}\n" // 2 decimal places, scientific notation
"pi: {:.10f}", // 10 decimal places
42, 255, 3.14159, 12345.6789, std::numbers::pi
);
// std::print (C++23) — format + output in one step
std::print("Name: {}, Age: {}\n", "Alice", 30);
std::print("Pi is approximately {:.6f}\n", std::numbers::pi);
// std::println (C++23) — like print but adds a newline
std::println("Score: {:>5}", 95); // "Score: 95"
std::println("Score: {:>5}", 100); // "Score: 100"
// Formatting containers — print each element
std::vector<int> values = {10, 20, 30, 40, 50};
for (std::size_t i = 0; i < values.size(); ++i) {
std::println("[{:2}] = {:>4}", i, values[i]);
}
// Why prefer std::format/print over iostream?
// 1. Type-safe (unlike printf)
// 2. Readable format strings (unlike iostream manipulators)
// 3. No state leakage (iostream manipulators are sticky!)
// 4. Better performance in many cases
}برای کامپایل و اجرای این نمونه، روی اجرا بزنید.
C++ مدرن سه رویکرد ورودی/خروجی به شما میدهد. هر کدام را چه زمانی استفاده کنید:
از std::print/std::println (C++23) استفاده کنید برای کد جدید وقتی کامپایلرتان آن را پشتیبانی میکند. تمیزترین نحو، ایمن از نظر نوع، و مشکلات دستکاریکنندههای حالتدار iostream را ندارد.
از std::format (C++20) استفاده کنید وقتی نیاز به رشته قالببندیشده دارید (نه خروجی مستقیم)، یا وقتی کامپایلرتان C++20 را پشتیبانی میکند اما C++23 را نه.
از iostream استفاده کنید وقتی با کدی تعامل دارید که اشیاء جریان انتظار دارد، وقتی نیاز به انتزاع جریان دارید (مثلاً خواندن از cin، ورودی/خروجی فایل)، یا وقتی با پایگاههای کد قدیمی کار میکنید.
از printf/scanf زبان C اجتناب کنید — ایمن از نظر نوع نیستند (ارسال مشخصکننده قالب اشتباه رفتار تعریفنشده است) و با انواع C++ مانند std::string کار نمیکنند.
- std::print/println (C++23) را برای کد خروجی جدید ترجیح دهید — تمیزترین و ایمنترین
- از std::format (C++20) استفاده کنید وقتی نیاز به رشته قالببندیشده دارید، نه خروجی مستقیم
- iostream همچنان برای cin، ورودی/خروجی فایل و انتزاعات مبتنی بر جریان مورد نیاز است
- از printf/scanf اجتناب کنید — ایمن از نظر نوع نیستند و نمیتوانند std::string را مستقیماً مدیریت کنند
- همیشه حالت جریان را پس از عملیات ورودی بررسی کنید — از 'if (cin >> x)' یا 'while (getline(in, line))' استفاده کنید
- ترکیب cin >> و getline() باعث باگ خطجدیددربافر میشود — بین آنها cin.ignore() فراخوانی کنید
- جریانهای فایل از RAII استفاده میکنند — وقتی از دامنه خارج شوند به طور خودکار بسته میشوند
- std::format (C++20) قالببندی رشته ایمن از نظر نوع و شبیه پایتون فراهم میکند
- std::print/println (C++23) قالببندی و خروجی را ترکیب میکند — روش ترجیحی برای کد جدید
- دستکاریکنندههای iostream حالتدار (چسبنده) هستند — std::format این مشکل را کاملاً برطرف میکند
آزمون — دانش خود را بسنجید
(10 امتیاز)1. اگر بلافاصله پس از `std::cin >> number` فراخوانی `std::getline(std::cin, str)` کنید چه اتفاقی میافتد؟
2. `std::format("{:#06x}", 255)` چه تولید میکند؟
3. چرا جریانهای فایل (ifstream/ofstream) وقتی از دامنه خارج میشوند به طور خودکار بسته میشوند؟