C++ Programming

Console Streams, Formatting and File Operations

PGCP-AC

1. The Stream Model

C++ input and output are based on streams. A stream presents data as a sequence that can be read or written while hiding many details of the underlying device. The same formatted operations can work with a terminal, a file or an in-memory string.

The stream library separates direction from destination:

  • std::istream supplies input operations;
  • std::ostream supplies output operations;
  • std::iostream supplies both;
  • file-stream classes connect these interfaces to files;
  • string-stream classes connect them to strings.

A stream object also stores formatting settings, an error state and a position when the underlying sequence supports positioning.

2. Standard Console Streams

The iostream header defines four standard objects. std::cin reads standard input. std::cout writes ordinary standard output. std::cerr writes diagnostic output and is configured for prompt delivery. std::clog also writes diagnostics but may remain buffered.

int age;
std::cout << "Age: ";
std::cin >> age;

The insertion operator writes a value to an output stream. The extraction operator reads and converts a value from an input stream. Both return the stream by reference, enabling a chain:

std::cin >> width >> height;
std::cout << "Area: " << width * height << '\n';

Each operation in the chain receives the state produced by the preceding operation.

3. Formatted Extraction

Formatted extraction interprets characters according to the destination type. Integer extraction recognizes an integer representation, floating extraction recognizes a floating value and string extraction reads a whitespace-delimited word.

Leading whitespace is normally skipped. Reading into std::string with >> stops at the next whitespace. Reading a char also skips leading whitespace unless std::noskipws is active.

int quantity;
double price;
std::string code;

if (std::cin >> code >> quantity >> price) {
    // every extraction succeeded
}

The stream's conversion to a condition reflects whether it remains usable. Test the extraction itself so data is processed only after success.

4. Reading Complete Lines

std::getline reads characters into a string until a delimiter, normally newline:

std::string name;
std::getline(std::cin, name);

It removes the delimiter but does not store it. A third argument selects another delimiter:

std::getline(input, field, ',');

After formatted numeric extraction, the newline that ended the number often remains in the input buffer. An immediate getline then consumes that newline and produces an empty string.

One solution is to discard leading whitespace:

int age;
std::string name;
std::cin >> age;
std::getline(std::cin >> std::ws, name);

std::ws consumes leading whitespace. Use it only when leading whitespace is not meaningful input. Another design reads every record as a complete line and parses the line with a string stream.

5. Unformatted Input and Output

Unformatted operations transfer characters or blocks with little interpretation. Important input operations include get, getline for character arrays, read, peek, ignore and gcount. Output operations include put and write.

char ch;
input.get(ch);                    // includes whitespace

std::array<char, 128> block{};
input.read(block.data(), block.size());
std::streamsize n = input.gcount();

read attempts to obtain the requested number of characters. gcount reports how many characters the last unformatted input operation actually extracted. write outputs an explicit number of bytes and does not stop at a null character.

6. Stream State

A stream records several state flags:

  • goodbit means no error flag is set;
  • eofbit means an operation encountered end of input;
  • failbit means an operation could not perform the requested logical conversion or operation;
  • badbit means a serious loss of stream integrity, such as an underlying device error.

The member functions good, eof, fail and bad inspect these states. fail is true when failbit or badbit is set. The stream condition used in if and while is effectively a usability test.

End of file is detected only after an input operation attempts to read beyond available data. Therefore this is unsafe:

while (!input.eof()) {
    input >> value;
    use(value);                   // may use stale value
}

The correct form is:

while (input >> value) {
    use(value);
}

7. Recovering from Invalid Input

Once failbit is set, further formatted extraction does nothing until the state is cleared. clear resets state flags, but it does not remove the characters that caused failure.

int value;
while (!(std::cin >> value)) {
    if (std::cin.eof()) {
        break;
    }
    std::cin.clear();
    std::cin.ignore(
        std::numeric_limits<std::streamsize>::max(), '\n');
    std::cout << "Enter an integer: ";
}

ignore discards the invalid remainder through the newline. State and buffered data are separate concerns: reset the state first, then remove input that would immediately fail again.

8. Output Buffering and Flushing

Output is often buffered for efficiency. A newline character writes a newline but does not universally promise an immediate flush. std::endl writes a newline and flushes. std::flush flushes without adding a character.

Frequent flushing can slow output. Use '\n' for ordinary lines and flush only when another process or user must see output immediately. Streams flush during normal destruction or program shutdown where specified, but abnormal termination may lose buffered output.

Input and output streams may be tied. std::cin is normally tied to std::cout, so an input operation flushes a pending prompt first.

9. Formatting Integers

Manipulators in iostream and iomanip change representation:

std::cout << std::dec << 255 << '\n';
std::cout << std::hex << 255 << '\n';
std::cout << std::oct << 255 << '\n';
std::cout << std::showbase << std::hex << 255 << '\n';

std::dec, std::hex and std::oct select the integer base. showbase requests a prefix where applicable, showpos displays a plus sign for positive numbers and uppercase changes letters used in representations.

Most settings remain active until changed. This persistent state can affect later output. Restore a known setting or save and restore stream flags in reusable formatting functions.

10. Field Width, Fill and Alignment

std::setw specifies the minimum width of the next formatted field:

std::cout << std::left  << std::setw(20) << name
          << std::right << std::setw(8) << score;

Unlike most flags, width normally applies to one following insertion and then resets. std::setfill chooses padding characters. std::left, std::right and std::internal select alignment. Width does not truncate a value that needs more space; it is a minimum.

11. Floating-Point Formatting

The default floating format chooses a representation based on magnitude and precision. std::fixed selects fixed-point form, while std::scientific selects exponent form.

std::cout << std::fixed
          << std::setprecision(2)
          << 12.3456;

With fixed or scientific, setprecision controls digits after the decimal point. In the default format it generally controls significant digits. showpoint can display a decimal point and trailing zeros.

Formatting does not change the stored number. It changes only its textual representation. Decimal output may reveal rounding because many decimal fractions have no exact binary floating representation.

12. Boolean and Quoted Text Formatting

By default, booleans print as 0 and 1. std::boolalpha prints true and false and also affects boolean input:

std::cout << std::boolalpha << enabled;

std::quoted from iomanip helps read and write strings with delimiters and escape characters:

std::cout << std::quoted(title);
input >> std::quoted(title);

It is useful for simple textual formats but does not replace a fully specified serialization format when compatibility and validation matter.

13. String Streams

std::istringstream parses an existing string, std::ostringstream builds a string and std::stringstream supports both.

std::string line = "42 19.5";
std::istringstream parser(line);
int id;
double amount;

if (parser >> id >> amount) {
    parser >> std::ws;
    if (parser.eof()) {
        // entire line was valid
    }
}

Line-oriented input plus string-stream parsing isolates malformed records and avoids leaving a shared console stream midway through a record. An output string stream can build a formatted diagnostic before returning parser.str().

14. File Stream Classes

The fstream header provides:

  • std::ifstream for file input;

  • std::ofstream for file output;

  • std::fstream for combined input and output.

    std::ifstream input("records.txt"); if (!input) { throw std::runtime_error("cannot open records.txt"); }

    std::string line; while (std::getline(input, line)) { process(line); }

Construction opens the file. The destructor closes it, so file handles follow RAII. A stream can also be default-constructed, opened later with open, tested with is_open and explicitly closed with close.

15. File Open Modes

Open modes are combined with bitwise OR:

  • ios::in opens for reading;

  • ios::out opens for writing;

  • ios::app places every write at the end;

  • ios::ate initially positions at the end but permits later seeking;

  • ios::trunc discards existing content when opening;

  • ios::binary disables text-mode translation.

    std::ofstream log("application.log", std::ios::out | std::ios::app);

The class supplies default modes: ifstream includes input and ofstream includes output and commonly truncates unless another appropriate mode is chosen. Select modes deliberately when existing data must be preserved.

16. Text and Binary Files

Text I/O represents values as characters. It is readable and can be portable when encoding, separators, locale and numeric rules are specified. Binary I/O transfers bytes and can be compact and fast.

Binary mode prevents platform text transformations such as newline conversion. It does not define a portable data format. Raw object bytes may contain padding, implementation-dependent layout, byte-order differences, pointers, virtual-table machinery and representations that differ between systems or builds.

A sound binary format explicitly defines field sizes, byte order, encoding, record boundaries, version information and validation. Serialize pointed-to data, not process addresses.

17. Binary read and write

For byte-oriented data:

std::ofstream output("image.bin", std::ios::binary);
output.write(reinterpret_cast<const char*>(bytes.data()),
             static_cast<std::streamsize>(bytes.size()));

std::ifstream input("image.bin", std::ios::binary);
input.read(reinterpret_cast<char*>(bytes.data()),
           static_cast<std::streamsize>(bytes.size()));

After reading, inspect the stream or gcount to determine whether the expected bytes arrived. A short read may represent normal end of file, a truncated record or an I/O error depending on the format.

18. Random Access

Input and output sequences have separate positions. tellg reports the input position and seekg changes it. tellp and seekp perform the corresponding output operations.

input.seekg(0, std::ios::end);
std::streampos size = input.tellg();
input.seekg(0, std::ios::beg);

Offsets can be relative to ios::beg, ios::cur or ios::end. Clear a failure state before seeking when a previous operation set it. Text-mode seeking has platform restrictions; binary files with fixed-size records offer more predictable byte positioning.

19. Stream Exceptions

Streams normally report errors through state flags. A program may request exceptions for selected flags:

input.exceptions(std::ios::badbit);

Then setting badbit throws std::ios_base::failure. Throwing for every failbit is often inconvenient because normal parsing uses failure to end loops. Choose a policy suited to the boundary: state-based parsing for expected invalid input and exceptions for failures that cannot be handled locally.

20. Reliable File Updates

Writing directly over an important file can leave partial content if serialization or I/O fails. A safer update writes complete content to a temporary file, closes and checks it and then replaces the destination using suitable filesystem operations.

Always check the final stream state. Successful open does not guarantee every later write succeeded. File formats should reject invalid lengths, unexpected tags, incomplete records and unreasonable allocation sizes before using the data.

Streams provide a common vocabulary for console, file and memory I/O. Reliable use depends on understanding formatted versus unformatted transfer, testing the operation that reads data, managing stream state explicitly and defining persistent file formats independently of in-memory object layout.

Continue learning

Related notes

Put this topic into timed practice

Open mock tests when you want full-exam pacing, or keep drilling in practice mode.