11. Streams Input and Output in C++#

With Unix(Linux), streams provide an abstraction for files but also for other devices such as the keyboard (usually, standard input), the console (standard output), errors (stderr), and sockets for network communication. As part of the initial design of C++, Bjarne Stroustrup chose to develop a framework of classes to abstract/model this stream concept.

To use input/output streams within your programs, include the following at the top of your source files:

#include <iostream>

11.1. Output -#

Use cout to send output to standard out and cerr for standard error. Both of these belong to the namespace std.

 1//filename: output.cpp
 2//complile: g++ -std=c++17 -o output output.cpp
 3//execute: ./output
 4#include <iostream>
 5#include <iomanip>
 6
 7using namespace std; 
 8
 9int main(int argc, char *argv[]){
10    double d = 123.456501;
11    cout << "Hello: " << fixed << setprecision(3) << d << "\n";
12    cout << d << std::"\n";
13    cerr << "an error message such as a usage statement\n";
14}

std:endl is a stream manipulator that sends a newline into the stream and flushes the stream (forces output to be written from any buffers). Generally, you will want to just use the “\n” character for output to the terminal.

11.2. Stream Manipulators#

The streams library (as demonstrated with endl, fixed, and setprecision above) uses manipulators to affect the input and output of streams. These two references pages provide a terse overview of the available options to manipulate streams:

Note: With the exception of setw(), all manipulators persist until they are overridden.

 1//filename: manipulator.cpp
 2//complile: g++ -std=c++17 -o manipulator manipulator.cpp
 3//execute: ./manipulator
 4#include <iostream>
 5#include <iomanip>
 6#include <string>
 7using namespace std;
 8
 9int main(int argc, char *argv[]) {
10
11    int num = 1842;
12    double val = 79.861;
13    double negval = -55.78;
14    string name = "Duke University";
15    
16    cout << "Demonstrate field width (requires iomanip include)\n";
17    cout << "01234567890123456789\n";
18    cout << num << "\n";               // normal output
19    cout << setw(20) << num << "\n";    // field large enough, note right justification
20    cout << setw(2) << num << "\n";    // field too small
21    
22    cout << val << "\n";
23    cout << setw(20) << val << "\n";
24    cout << setw(2) << val << "\n";
25    
26    cout << name << "\n";
27    cout << setw(20) << name << "\n";
28    cout << setw(2) << name << "\n";
29
30    cout << "Demonstrate justification and width\n";
31    cout << "01234567890123456789\n";
32    cout << "Default:" << "\n";
33    cout << setw(20) << num << "\n";
34    cout << setw(20) << val << "\n"; 
35    cout << setw(20) << negval << "\n"; 
36    cout << setw(20) << name << "\n";  
37
38    cout << "Using right:" << right << "\n";
39    cout << setw(20) << num << "\n";
40    cout << setw(20) << val << "\n";
41    cout << setw(20) << negval << "\n"; 
42    cout << setw(20) << name << "\n";
43
44    cout << "Using left:" << left << "\n";
45    cout << setw(20) << num << "\n";
46    cout << setw(20) << val << "\n"; 
47    cout << setw(20) << negval << "\n"; 
48    cout << setw(20) << name << "\n";
49
50    cout << "Using internal:" << internal << "\n";
51    cout << setw(20) << num << "\n";
52    cout << setw(20) << val << "\n"; 
53    cout << setw(20) << negval << "\n"; 
54    out << setw(20) << name << "\n";
55
56    return EXIT_SUCCESS;
57}
 1//filename: manip2.cpp
 2//complile: g++ -std=c++17 -o manip2 manip2.cpp
 3//execute: ./manip2
 4#include <iostream>
 5#include <iomanip>
 6#include <string>
 7using namespace std;
 8
 9int main(int argc, char *argv[]) {
10        // Formatting floating Point Numbers
11        double num = 123;
12        double val = 431.12234;
13        double val2 = 1.237829183772;
14        double val3 = 1234567.123;
15
16        // Default output
17        // Notice the default precision
18        // is 6 for double/float that is
19        // 6 digits will be displayed
20        cout << "Default: " << "\n";
21        cout << num  << "\n"
22             << val  << "\n"
23             << val2 << "\n"
24             << val3 << "\n";
25             
26        // setprecision(4) and do some spacing
27        cout << "\n" << "setprecision(4): " << "\n";
28        cout << setprecision(4); // applies to all future outputs
29                             // or until reset
30        cout << num  << "\n"
31             << val  << "\n"
32             << val2 << "\n"
33             << val3 << "\n";
34
35        // setprecision(2) and do some spacing
36        cout << setprecision(2) << "\n" << "setprecision(2): " << "\n";
37
38        cout << num  << "\n"
39         << val  << "\n"
40         << val2 << "\n"
41         << val3 << "\n";
42         
43        // add fixed to the mix
44        cout << fixed << "\n" << "setprecision(2) + fixed: " << "\n";
45        cout << num  << "\n"
46             << val  << "\n"
47             << val2 << "\n"
48             << val3 << "\n";
49
50        // add showpoint to the mix
51        cout << showpoint << "\n" << "setprecision(2) + fixed + showpoint: " << "\n";
52        cout << num  << "\n"
53             << val  << "\n"
54             << val2 << "\n"
55             << val3 << "\n";
56    return 0;
57
58    return EXIT_SUCCESS;
59}
60
 1//filename: manip3.cpp
 2//complile: g++ -std=c++17 -o manip3 manip3.cpp
 3//execute: ./manip3
 4#include <iostream>
 5#include <iomanip>
 6#include <string>
 7using namespace std;
 8
 9int main(int argc, char *argv[]) {
10    // Formatting floating Point Numbers
11        double num = 123;
12        double val = 431.12234;
13        double val2 = 1.237829183772;
14        double val3 = 1234567.123;
15
16        // Default output
17        // Notice the default precision
18        // is 6 for double/float that is
19        // 6 digits will be displayed
20        cout << "Default: " << "\n";
21        cout << num  << "\n"
22             << val  << "\n"
23             << val2 << "\n"
24             << val3 << "\n";
25             
26        // set fixed and do some spacing
27        cout << fixed;
28        cout << "\n" << "fixed: " << "\n";
29        cout << num  << "\n"
30             << val  << "\n"
31             << val2 << "\n"
32             << val3 << "\n";
33
34        // set scientific and do some spacing
35        cout << scientific;
36        cout << "\n" << "scientific: " << "\n";
37        cout << num  << "\n"
38         << val  << "\n"
39         << val2 << "\n"
40         << val3 << "\n";
41    return EXIT_SUCCESS;
42}

(Code copied/adapted from https://www.cs.mtsu.edu/~rwsmith/2170/examples/formattingOutput.html)

11.2.1. Integer Base Formatting#

The following example shows output of an integer in various bases:

 1//filename: manip_int.cpp
 2//complile: g++ -std=c++17 -o manip_int manip_int.cpp
 3//execute: ./manip_int
 4#include <iostream>
 5#include <string>
 6using namespace std;
 7
 8int main(int argc, char *argv[]) {
 9    cout << 1842 << '\t' << std::hex << 1842 << '\t' << std::oct << 1842 << '\n';
10    cout << showbase << std::dec;          // show bases, start with decimal
11    cout << 1842 << '\t' << std::hex << 1842 << '\t' << std::oct << 1842 << '\n';
12    
13    return EXIT_SUCCESS;
14}

11.3. Boolean Format#

By default, C++ will output 1 or 0 into a stream. By using the boolalpha, you can output true or false instead.

 1//filename: boolean.cpp
 2//complile: g++ -std=c++17 -o boolean boolean.cpp
 3//execute: ./boolean
 4#include <iostream>
 5#include <string>
 6using namespace std;
 7
 8int main(int argc, char *argv[]) {
 9    bool value = true;
10    
11    std::cout << "boolean: " << value << "\n";
12    std::cout << "boolean (after manipulator): " << boolalpha << value << "\n";
13
14    value = false; 
15    std::cout << "boolean (manipulator persists): " << value << "\n";
16    std::cout << "boolean (reset noboolalpha): " << noboolalpha << value << "\n";
17
18    return EXIT_SUCCESS;
19}

11.4. Reading from an Input File#

The following program is a simplified version of cat. The program execution has been set to execute with “cat cat.cpp” as the command-line - i.e., the execution displays cat.cpp.

 1//filename: cat.cpp
 2//complile: g++ -std=c++17 -o cat cat.cpp
 3//execute: ./cat
 4#include <iostream>
 5#include <fstream>
 6#include <string>
 7
 8std::ifstream openFile(std::string name) { // abstract into a function
 9    std::ifstream in(name);
10    if (!in.is_open()) {
11        std::cerr << "Unable to open " << name << std::endl;
12        exit(EXIT_FAILURE);  // since we are in function, return would go back to main
13    }
14    return in;
15}
16
17int main(int argc, char *argv[]) {
18    if (argc < 2) {
19        std::cerr << "Usage: " << argv[0] << " inputFile ..." << std::endl;
20        return EXIT_FAILURE;
21    }
22
23    for (int i = 1; i < argc; i++) {
24        std::ifstream in = openFile(argv[1]);
25        
26        std::string line;
27        while (getline(in,line)) {        //continually reads until end of file
28            std::cout << line << "\n";    // line does not include a newline
29        }
30
31        in.close();   // allocate a resource, use it, release it.
32    }
33}

11.5. Writing to a File#

 1//filename: output.cpp
 2//complile: g++ -std=c++17 -o output output.cpp
 3//execute: ./output
 4#include <iostream>
 5#include <fstream>
 6#include <string>
 7
 8#include <iostream>
 9#include <fstream>
10using namespace std;
11
12
13int main(int argc, char *argv[]) {
14    std::string filename = "file_name.txt";
15
16    std::ofstream myFile(filename);
17    if (!myFile.is_open()) {
18        std::cerr << "Unable to open file: " << filename << std::endl;
19        return EXIT_FAILURE;
20    }
21
22    myFile << "It was the best of times, it was the worst of times, \n";
23    myFile << "it was the age of wisdom, it was the age of foolishness, \n";
24    myFile << "it was the epoch of belief, it was the epoch of incredulity, \n";
25    myFile << "it was the season of light, it was the season of darkness, \n";
26    myFile << "it was the spring of hope, it was the winter of despair. - Charles Dickens\n";
27
28    myFile.close();
29
30    return EXIT_SUCCESS;
31}

Now, find the file and display the contents in the terminal:
ls
cat file_name.txt

11.6. Using sstream#

sstream is a special stream that allows us to send data into a “string buffer”, which we can then convert to string object.

 1//filename: sstream.cpp
 2//complile: g++ -std=c++17 -o sstream sstream.cpp
 3//execute: ./sstream
 4#include <sstream>
 5#include <string>
 6#include <iostream>
 7
 8int main(int argc, char *argv[]) {
 9    std::stringstream ss;
10    double d = 3.14;
11    int r = 2;
12
13    ss << "Circle: (radius=" << r << ") has area: " << d * r * r;
14    
15    std::string output = ss.str();
16    std::cout << output << std::endl;
17}

11.7. Stream Error States#

Input streams such as as cin and those created for reading from files (ifstream) can encounter various error states reading data. These error states are typically represented using the stream’s state flags, which are part of the stream’s internal state.

  1. std::ios::eofbit (End of File Bit): This error state is set when an attempt to read past the end of the input stream is made - no more data is available to read from the stream.

  2. 1std::ios::failbit` (Fail Bit): The fail bit is set when an error occurs during input operations that is not related to the end of the file. Occurs in such situations such as attempting to read data of the wrong type (e.g., reading an integer when the input is not an integer). Overflow or underflow of numeric values during input.

  3. std::ios::badbit (Bad Bit): The bad bit is set when a severe error occurs while attempting input. This typically indicates a problem with the underlying input source or stream. Examples of conditions that can set the bad bit include hardware failures, I/O errors, or invalid stream operations.

To check for and handle errors, you can use member functions provided by the C++ standard library:

  • good(): Returns true if none of the error flags (eofbit, failbit, or badbit) are set.

  • eof(): Returns true if the eofbit is set.

  • fail(): Returns true if the failbit or badbit is set.

  • bad(): Returns true if the badbit is set.

Use clear() to clear error flags.

 1//filename: errors.cpp
 2//complile: g++ -std=c++17 -o errors errors.cpp
 3//execute: ./errors
 4#include <iostream>
 5
 6int main() {
 7    int num = -1;
 8    
 9    std::cout << "Enter a bad integer: ";
10
11    std::cin >> num;
12
13    if (std::cin.eof()) {
14        std::cout << "End of file received\n";
15    }
16    else if (std::cin.bad()) {
17         std::cerr << "Failed to read an integer - bad" << std::endl;
18         std::cin.clear(); // Clear the error flags
19        // Handle the error or recover as needed.
20    }
21    else if (std::cin.fail()) {
22        std::cerr << "Failed to read an integer." << std::endl;
23        std::cin.clear(); // Clear the error flags
24        // Handle the error or recover as needed.
25    }
26    
27    return EXIT_SUCCESS;
28}

11.8. Saving and Restoring Stream State#

One of the difficulties with the stream manipulators is that some of the manipulators are persistent while others just apply to the next value placed onto the stream. As such, it may be useful to save the stream state and then restore that state once you are complete. The most complete way for doing this is to use .copyfmt() source.

Implementation Note: If you override the << operator and use a stream manipulator, you should restore the stream back to the prior without that manipulator set.

 1//filename: state.cpp
 2//complile: g++ -std=c++17 -o state state.cpp
 3//execute: ./state
 4#include <iostream>
 5#include <iomanip>
 6
 7int main() {
 8    // Save the stream state
 9    std::ios oldState(nullptr);
10    oldState.copyfmt(std::cout);
11 
12    std::cout << std::hex << std::setw(8)  << std::setfill('0') << 0xDECEA5ED << std::endl;
13
14    // restore the stream state
15    std::cout.copyfmt(oldState);
16
17    std::cout << std::setw(15)  << std::left << "case closed" << std::endl;
18
19    // .flags() is insufficient
20    std::ios_base::fmtflags f( std::cout.flags() ); // save flags in f
21    std::cout << std::hex << std::setw(8)  << std::setfill('0') << 0xDECEA5ED << std::endl;
22    std::cout.flags( f ); // restore flags
23    // setfill is still active
24    std::cout << std::setw(15)  << std::left << "case closed" << std::endl;
25}

11.9. C++20: format#

While the Streams IO approach does have an advantage over the printf and scanf capabilities within C with regard to type safety, the need to use multiple operator instances of << and stream manipulators have led many to criticize the design for the resulting “chevron hell”.

// C++ Streams I/O
std::cout << "Value: " << std::hex << std::uppercase << std::setw(8) << 
              value << std::endl;


// C - printf (not type safe)
printf("%08X\n", value);


// C++ Format Library (requires C++ 20)
d::cout << std::format("{:08X}", value);

// C++ Format and Print Library (requires C++ 23)
std::println("{:08X}", value);

In response, many developers simply resorted to using the C facilities (printf), which is not type-safe, or using third party developed libraries. One of the most successful libraries has been fmt.dev, which provides type-safe formatting comparable to that in Python. In response to developer requests for better string formatting capabilities, the C++ standards committee adopted that library (with various improvements throughout the approval process - Text Formatting Proposal Timeline). Using the format library requires using newer versions of compilers than those currently distributed with most operating systems.

Victor Zverovich, the primary author of the fmt.dev library, wrote this blog post highlighting some of the issues with streams and formatting.

Note: The C++ format specification was based upon Python’s f-string specification: C++ Standard format Specification

 1//filename: format.cpp
 2//complile - MacOS: g++ -std=c++2b -o format format.cpp
 3//         - Ubuntu 24.04: g++ -std=c++20 -o format format.cpp    (requires 2 code changes)
 4//execute: ./manip_int
 5#include <iostream>
 6#include <format>    // Include the <format> header for C++20 formatting
 7#include <print>     // Requires C++23.
 8                     // Need to remove on Ubuntu 24.04 and change "std::print" to "std::cout << std::format"
 9
10int main() {
11    int value = 42;
12    double pi = 3.14159;
13
14    // Basic formatting using std::format
15    std::string formatted_string = std::format("The value is {} and pi is {}", value, pi);
16    std::cout << formatted_string << std::endl;
17
18    // Formatting with width and precision
19    std::cout << std::format("Value: {:5} Pi: {:.2f}\n", value, pi);
20
21    // Aligning text to the left
22    std::cout << std::format("Value: {:<5} Pi: {:.2f}\n", value, pi);
23
24    // Formatting with padding and precision
25    std::print("Value: {:-^10} Pi: {:.3f}\n", value, pi);   //std::println() is also available
26
27    return 0;
28}