सामग्री पर जाएं

I/O: स्ट्रीम, फ़ॉर्मेटिंग और std::print

क्लासिक iostream से लेकर आधुनिक std::format और std::print तक C++ I/O में महारत हासिल करें, जिसमें स्ट्रीम स्टेट, फ़ाइल I/O और बफ़र की आम गड़बड़ियाँ शामिल हैं।

C++ I/O का विकास

भाषा के इतिहास में C++ I/O काफ़ी विकसित हुआ है। क्लासिक iostream लाइब्रेरी (std::cin, std::cout) C++98 से स्टैंडर्ड रही है, लेकिन इसमें उपयोग की सुगमता और परफ़ॉर्मेंस की जानी-मानी समस्याएँ हैं। C++20 ने std::format पेश किया, जो Python के str.format() से प्रेरित एक टाइप-सेफ़ फ़ॉर्मेटिंग लाइब्रेरी है। C++23 ने std::print जोड़ा, जो फ़ॉर्मेटिंग और आउटपुट को एक ही कॉल में जोड़ देता है।

तीनों तरीकों को समझना अहम है: मौजूदा कोड में iostream हर जगह है, std::format स्ट्रिंग फ़ॉर्मेट करने का आधुनिक तरीका है, और std::print C++ आउटपुट का भविष्य है।

क्लासिक I/O: cout, cin, cerr

iostream लाइब्रेरी स्टैंडर्ड इनपुट, आउटपुट और एरर आउटपुट के लिए स्ट्रीम ऑब्जेक्ट देती है:

io_basics.cppc++23
#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';
}
आउटपुट

इस उदाहरण को कंपाइल करके चलाने के लिए चलाएं दबाएं।

स्ट्रीम स्टेट फ़्लैग

हर स्ट्रीम स्टेट फ़्लैग (state flags) का एक समूह रखती है जो उसकी मौजूदा स्थिति बताता है:

- goodbit — कोई एरर नहीं। स्ट्रीम I/O ऑपरेशन के लिए तैयार है।
- eofbit — फ़ाइल/इनपुट का अंत आ गया है। तब सेट होता है जब स्ट्रीम अंत के आगे पढ़ने की कोशिश करती है।
- failbit — कोई तार्किक एरर हुआ (जैसे इंटीजर पढ़ने की कोशिश में टेक्स्ट मिल जाना)। clear() कॉल करने के बाद स्ट्रीम फिर से उपयोग हो सकती है।
- badbit — कोई ऐसा I/O एरर हुआ जिससे उबरा नहीं जा सकता (जैसे डिस्क फ़ेल होना)। स्ट्रीम आमतौर पर उपयोग के लायक नहीं रहती।

आप इन्हें stream.good(), stream.eof(), stream.fail(), stream.bad() से जाँच सकते हैं, या स्ट्रीम को बूलियन कॉन्टेक्स्ट में उपयोग करके: if (std::cin) और if (!std::cin.fail()) एक जैसे हैं।

एक अहम पैटर्न: इनपुट ऑपरेशन के बाद हमेशा स्ट्रीम स्टेट जाँचें, पहले नहीं। आम इडियम while (std::cin >> value) पढ़ना और जाँचना एक ही चरण में करता है — रीड विफल होने पर यह रुक जाता है।

सामान्य गलती

C++ में सबसे आम I/O बग >> (फ़ॉर्मेटेड extraction) और std::getline() को मिलाने से होता है। >> ऑपरेटर वैल्यू पढ़ता है लेकिन पीछे आने वाली newline को बफ़र में छोड़ देता है। इसके बाद का std::getline() उस बची हुई newline को खाली स्ट्रिंग के रूप में पढ़ लेता है, जिससे लगता है कि इनपुट छूट गया।

समाधान है >> और std::getline() के बीच std::cin.ignore(std::numeric_limits::max(), '\n') कॉल करना, ताकि बची हुई newline हट जाए।

दूसरा तरीका है सारे इनपुट के लिए std::getline() का उपयोग करना और स्ट्रिंग को std::istringstream या std::stoi/std::stod से खुद पार्स करना। इससे समस्या पूरी तरह टल जाती है और एरर हैंडलिंग भी बेहतर मिलती है।

  • cin >> x के बाद बफ़र में newline बची रहती है — अगला getline() उसे खाली स्ट्रिंग के रूप में पढ़ लेता है
  • समाधान: >> और getline() के बीच cin.ignore(numeric_limits::max(), '\n') कॉल करें
  • विकल्प: सारे इनपुट के लिए getline() का उपयोग करें और स्ट्रिंग को stoi/stod या istringstream से पार्स करें
  • इनपुट के बाद हमेशा स्ट्रीम स्टेट जाँचें: आँख मूँदकर पढ़ने के बजाय if (cin >> x) का उपयोग करें

फ़ाइल I/O

फ़ाइल I/O कंसोल I/O जैसा ही स्ट्रीम इंटरफ़ेस उपयोग करता है, पढ़ने के लिए std::ifstream और लिखने के लिए std::ofstream के साथ:

file_io.cppc++23
#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

C++20 का std::format और C++23 का std::print टाइप-सेफ़, Python जैसी फ़ॉर्मेटिंग देते हैं, जो iostream मैनिपुलेटर से ज़्यादा सुरक्षित और ज़्यादा पठनीय है:

format_print.cppc++23
#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++ आपको I/O के तीन तरीके देती है। हर एक का उपयोग कब करें:

std::print/std::println (C++23) का उपयोग करें नए कोड के लिए, जब आपका कंपाइलर इसे सपोर्ट करता हो। इसका सिंटैक्स सबसे साफ़ है, यह टाइप-सेफ़ है, और iostream की स्टेटफ़ुल मैनिपुलेटर वाली समस्याओं से बचाता है।

std::format (C++20) का उपयोग करें जब आपको फ़ॉर्मेट की हुई स्ट्रिंग चाहिए (सीधा आउटपुट नहीं), या जब आपका कंपाइलर C++20 सपोर्ट करता हो लेकिन C++23 नहीं।

iostream का उपयोग करें जब आप ऐसे कोड से इंटरैक्ट कर रहे हों जो स्ट्रीम ऑब्जेक्ट की अपेक्षा करता है, जब आपको स्ट्रीम एब्स्ट्रैक्शन चाहिए (जैसे cin से पढ़ना, फ़ाइल I/O), या जब आप पुराने (legacy) कोडबेस के साथ काम कर रहे हों।

C के printf/scanf से बचें — वे टाइप-सेफ़ नहीं हैं (गलत फ़ॉर्मेट स्पेसिफ़ायर पास करना अपरिभाषित व्यवहार है) और std::string जैसे C++ टाइप के साथ काम नहीं करते।

  • नए आउटपुट कोड के लिए std::print/println (C++23) को प्राथमिकता दें — सबसे साफ़ और सबसे सुरक्षित
  • जब आपको फ़ॉर्मेट की हुई स्ट्रिंग चाहिए, सीधा आउटपुट नहीं, तब std::format (C++20) का उपयोग करें
  • cin, फ़ाइल I/O और स्ट्रीम-आधारित एब्स्ट्रैक्शन के लिए iostream की अब भी ज़रूरत है
  • printf/scanf से बचें — वे टाइप-सेफ़ नहीं हैं और std::string को सीधे संभाल नहीं सकते
मुख्य बातें
  • इनपुट ऑपरेशन के बाद हमेशा स्ट्रीम स्टेट जाँचें — 'if (cin >> x)' या 'while (getline(in, line))' का उपयोग करें
  • cin >> और getline() को मिलाने से बफ़र में newline वाला बग होता है — इनके बीच cin.ignore() कॉल करें
  • फ़ाइल स्ट्रीम RAII का उपयोग करती हैं — स्कोप से बाहर जाते ही वे अपने आप बंद हो जाती हैं
  • std::format (C++20) टाइप-सेफ़, Python जैसी स्ट्रिंग फ़ॉर्मेटिंग देता है
  • std::print/println (C++23) फ़ॉर्मेटिंग और आउटपुट को जोड़ता है — नए कोड के लिए पसंदीदा तरीका
  • iostream मैनिपुलेटर स्टेटफ़ुल (sticky) होते हैं — std::format इस समस्या से पूरी तरह बचाता है

क्विज़ — अपना ज्ञान परखें

(10 XP)

1. `std::cin >> number` के ठीक बाद `std::getline(std::cin, str)` कॉल करने पर क्या होता है?

2. `std::format("{:#06x}", 255)` का नतीजा क्या होगा?

3. फ़ाइल स्ट्रीम (ifstream/ofstream) स्कोप से बाहर जाते ही अपने आप क्यों बंद हो जाती हैं?