Output with ostream and ios

This lesson explores the ostream class and the powerful formatting capabilities inherited from the ios base classes.

The insertion operator

The insertion operator (<<) places data into output streams. C++ provides predefined insertion operations for all fundamental types, and you've learned to overload the insertion operator for custom classes.

The ios class (along with ios_base) controls output formatting options. You can modify these options through flags or manipulators.

Understanding flags and manipulators

Flags act as boolean switches that can be enabled or disabled. Manipulators are objects inserted into streams to modify their behavior.

To activate a flag, use setf() with the desired flag. For example, C++ normally omits the plus sign for positive numbers. The std::ios::showpos flag changes this:

#include <iostream>

int main()
{
    int temperature{24};

    std::cout.setf(std::ios::showpos);
    std::cout << "Temperature: " << temperature << " degrees\n";

    return 0;
}

Output:

Temperature: +24 degrees

You can activate multiple flags simultaneously using the bitwise OR operator (|):

#include <iostream>

int main()
{
    double voltage{3.14159};

    std::cout.setf(std::ios::showpos | std::ios::uppercase);
    std::cout << "Voltage: " << voltage << "V\n";

    return 0;
}

Output:

Voltage: +3.14159V

To deactivate a flag, use unsetf():

#include <iostream>

int main()
{
    int profit{5000};
    int loss{-2500};

    std::cout.setf(std::ios::showpos);
    std::cout << "Profit: " << profit << '\n';

    std::cout.unsetf(std::ios::showpos);
    std::cout << "Loss: " << loss << '\n';

    return 0;
}

Output:

Profit: +5000
Loss: -2500

Format groups and setf() complexities

Some flags belong to format groups - collections of mutually exclusive options. The "basefield" group contains flags for numeric bases: oct (octal), dec (decimal), and hex (hexadecimal). The decimal flag is active by default.

This code won't work as expected:

#include <iostream>

int main()
{
    int memoryAddress{255};

    std::cout.setf(std::ios::hex);
    std::cout << "Address: 0x" << memoryAddress << '\n';

    return 0;
}

Output:

Address: 0x255

The setf() function only activates flags; it doesn't deactivate mutually exclusive ones. Since std::ios::dec remains active and takes precedence over std::ios::hex, we get decimal output.

Two solutions exist. First, manually deactivate std::ios::dec:

#include <iostream>

int main()
{
    int memoryAddress{255};

    std::cout.unsetf(std::ios::dec);
    std::cout.setf(std::ios::hex);
    std::cout << "Address: 0x" << memoryAddress << '\n';

    return 0;
}

Output:

Address: 0xff

Second, use setf()'s two-parameter form, which deactivates all flags in the specified group before activating the desired flag:

#include <iostream>

int main()
{
    int memoryAddress{255};

    std::cout.setf(std::ios::hex, std::ios::basefield);
    std::cout << "Address: 0x" << memoryAddress << '\n';

    return 0;
}

Output:

Address: 0xff

Manipulators provide simpler formatting

Since setf() and unsetf() can be cumbersome, C++ provides manipulators that automatically manage appropriate flags. Here's the same functionality using manipulators:

#include <iostream>

int main()
{
    int debugValue{255};

    std::cout << "Decimal: " << std::dec << debugValue << '\n';
    std::cout << "Hexadecimal: " << std::hex << debugValue << '\n';
    std::cout << "Octal: " << std::oct << debugValue << '\n';

    return 0;
}

Output:

Decimal: 255
Hexadecimal: ff
Octal: 377

Manipulators are generally more intuitive than managing flags manually. While most formatting options are available through both approaches, some features are exclusive to one method, making it important to understand both.

Common formatting options

Here's a practical reference of frequently used formatting options:

Boolean formatting

#include <iostream>

int main()
{
    bool systemOnline{true};
    bool errorDetected{false};

    std::cout << std::boolalpha;
    std::cout << "System status: " << systemOnline << '\n';
    std::cout << "Error detected: " << errorDetected << '\n';

    std::cout << std::noboolalpha;
    std::cout << "System status (numeric): " << systemOnline << '\n';
    std::cout << "Error detected (numeric): " << errorDetected << '\n';

    return 0;
}

Output:

System status: true
Error detected: false
System status (numeric): 1
Error detected (numeric): 0

Sign display

#include <iostream>

int main()
{
    int accountBalance{15000};

    std::cout << std::showpos << "Balance: $" << accountBalance << '\n';
    std::cout << std::noshowpos << "Balance: $" << accountBalance << '\n';

    return 0;
}

Output:

Balance: $+15000
Balance: $15000

Case formatting

#include <iostream>

int main()
{
    double largeNumber{1234567.89};

    std::cout << std::uppercase << "Scientific: " << largeNumber << '\n';
    std::cout << std::nouppercase << "Scientific: " << largeNumber << '\n';

    return 0;
}

Output:

Scientific: 1.23457E+06
Scientific: 1.23457e+06

Numeric base formatting

#include <iostream>

int main()
{
    int port{8080};

    std::cout << "Decimal: " << std::dec << port << '\n';
    std::cout << "Hexadecimal: " << std::hex << port << '\n';
    std::cout << "Octal: " << std::oct << port << '\n';

    return 0;
}

Output:

Decimal: 8080
Hexadecimal: 1f90
Octal: 17620

Floating-point precision and notation

Precision and notation control how floating-point numbers display. The behavior of precision depends on which notation is active:

  • Fixed notation: precision determines decimal places after the point
  • Scientific notation: precision determines decimal places after the point
  • Default notation: precision determines significant digits
#include <iomanip>
#include <iostream>

int main()
{
    double sensorReading{123.456789};

    std::cout << std::fixed;
    std::cout << std::setprecision(2) << "Reading: " << sensorReading << "V\n";
    std::cout << std::setprecision(4) << "Reading: " << sensorReading << "V\n";

    std::cout << std::scientific;
    std::cout << std::setprecision(2) << "Reading: " << sensorReading << "V\n";
    std::cout << std::setprecision(4) << "Reading: " << sensorReading << "V\n";

    return 0;
}

Output:

Reading: 123.46V
Reading: 123.4568V
Reading: 1.23e+02V
Reading: 1.2346e+02V

Default notation (neither fixed nor scientific) displays numbers with the specified number of significant digits:

#include <iomanip>
#include <iostream>

int main()
{
    double measurement{123.456789};

    // Reset to default notation
    std::cout.unsetf(std::ios::floatfield);

    std::cout << std::setprecision(3) << measurement << '\n';
    std::cout << std::setprecision(5) << measurement << '\n';
    std::cout << std::setprecision(7) << measurement << '\n';

    return 0;
}

Output:

123
123.46
123.4568

Field width, fill characters, and alignment

When displaying columnar data, you'll want to control field width and alignment. The setw() manipulator defines how many character positions a value occupies:

#include <iomanip>
#include <iostream>

int main()
{
    int quantity{42};

    std::cout << std::setw(10) << quantity << " units\n";
    std::cout << std::setw(10) << std::left << quantity << " units\n";
    std::cout << std::setw(10) << std::right << quantity << " units\n";
    std::cout << std::setw(10) << std::internal << -quantity << " units\n";

    return 0;
}

Output:

        42 units
42         units
        42 units
-       42 units

Note: setw() only affects the next output operation. It's not persistent like other manipulators.

You can specify a fill character to replace spaces:

#include <iomanip>
#include <iostream>

int main()
{
    int orderNumber{42};

    std::cout.fill('0');
    std::cout << "Order #" << std::setw(6) << orderNumber << '\n';
    std::cout << "Order #" << std::setw(6) << std::left << orderNumber << '\n';
    std::cout << "Order #" << std::setw(6) << std::right << orderNumber << '\n';

    return 0;
}

Output:

Order #000042
Order #420000
Order #000042

The ostream class provides many additional formatting options. Consult standard library references when you need specialized formatting capabilities beyond what we've covered here.

Summary

Insertion operator: The insertion operator (<<) places data into output streams. C++ provides predefined operations for fundamental types, and supports custom overloads for user-defined classes.

Flags and manipulators: Flags are boolean switches that control formatting behavior, activated with setf() and deactivated with unsetf(). Manipulators are objects inserted into streams that modify behavior more intuitively than managing flags manually.

Format groups: Some flags belong to mutually exclusive groups (like basefield for numeric bases). Use the two-parameter form of setf() or manipulators to properly handle group membership.

Common manipulators: std::boolalpha/noboolalpha controls boolean output format, std::showpos/noshowpos controls sign display, std::uppercase/nouppercase controls letter case, and std::dec/hex/oct controls numeric base.

Floating-point formatting: Precision behaves differently depending on notation mode—in fixed notation it determines decimal places, in scientific notation it determines decimal places, and in default notation it determines significant digits.

Field width and alignment: std::setw() defines field width (applies only to the next output operation), std::left/right/internal controls alignment, and fill() or std::setfill() specifies the character used for padding.

Manipulator advantages: Manipulators are generally more intuitive and less error-prone than managing flags manually, automatically handling format groups and flag combinations correctly.

Understanding ostream formatting capabilities enables creating well-formatted output for reports, tables, logs, and user interfaces with precise control over appearance and alignment.