ICS 45C Spring 2022
Notes and Examples: Standard Input and Output

Includes a code example with the moniker StandardIO


Background

Like most programming languages, C++ provides some built-in mechanisms in its standard library for getting input into a program and output out of it. There are fewer of these mechanisms built into C++ than you might be used to — some, like network-based I/O or graphical user interfaces, are supported only in third-party libraries — but there are some. Most notably, C++ provides a way to perform I/O via the standard input and standard output of a program, along with file-based I/O. This example focuses on the standard input, though many of the concepts are also used when dealing with files (except that you also have to deal with issues such as reaching the end of the file or the inability to open a file).

What are the "standard input" and "standard output"?

On most operating systems, when a program is started, it has a handful of input and output streams associated with it by default. Traditionally, the simplest way to send input into a program is to send it to the program's standard input, while the simplest way to get output from it is for the program to write it to its standard output. From the program's perspective, the standard input is simply a stream of bytes that the program can consume, and the standard output is simply a stream of bytes that the program can write to.

The most common arrangement when you run a program on Linux via the shell (e.g., on the ICS 45C VM) is for the standard input and standard output to also be connected to the shell. On the ICS 45C VM, when you execute a program via the shell prompt by executing the ./run script, anything sent to the program's standard output will appear in your shell window, while an attempt by your program to read from its standard input may require you to type something in the shell window with the keyboard. (Note that it doesn't have to be this way. When you run a program, you can use a technique called redirection to connect standard input or standard output to other places. For example, you can have a program read its standard input from a file while writing its standard output to the shell prompt. Even more powerful is the technique of piping, which allows you to send the standard output of one program into the standard input of another automatically, which enables programs to be connected together in many useful ways, including ways that the original programmers never thought of.)

In the C++ Standard Library, the standard header <iostream> contains declarations of a variety of things you'd need in order to perform I/O using the standard input and standard output. Having included that header, you'll have access to the objects and functions described in this example; most notably, you'll have:

Writing some simple output to std::cout

Output streams like std::cout provide the capability to write formatted output, and know intrinsically how to format the built-in types (not just strings, but also integers, doubles, etc.). As we'll see later this quarter, you can also specify how formatting of your own types is to be done, though we'll need to learn more about specifying our own types before we can go down that road.

Writing an object to std::cout is most easily done using the << operator, which, when used on an object stream, is sometimes called the "put" operator, because it lets you "put" an object on to the stream. Depending on the object's type, it will be formatted to an appropriate textual representation, then the text will be written to the standard output. A subsequent use of the << operator on std::cout will write another object directly afterward, with no spaces included in between them.

Uses of the << operator can be strung together into a single expression, so it's not necessary to write a separate line of code for each object you want to output. You'll want to exercise some judgment about how long your expressions are; a good rule of thumb is not to write lines of code longer than about 80 characters. (My general rule is not to write code that will require me to horizontally scroll in an editor if I want to read it. Because different people have editor windows of different width, I tend to aim for about 80-100 characters at the most.)

To write an end-of-line sequence to std::cout, so that subsequent output will appear at the beginning of the next line, you'd put the object std::endl on to the standard output.

Putting these ideas together, consider the following code fragment.

int i = 3;
double d = 12.75;
std::string s = "Boo";

std::cout << i << d << s << std::endl;
std::cout << s << " " << d << std::endl;

This code fragment would write the following text to the standard output:

312.75Boo
Boo 12.75

A couple of things to note about this:

Reading input from std::cin

Just as there is an object, std::cout, that represents a program's standard output, the object std::cin represents a program's standard input. Reading input from std::cin is relatively straightforward, but there are a few things you need to know in order to do so effectively.

By default, a program's standard input is generally connected to a text-based input device. For example, when you run a program from a Linux shell prompt, as you would when using the ICS 45C VM, input is typed into the shell window when required. But you'll need a mental model of how the typed input is processed. Depending on what languages you've programmed in previously, you may find that you have a mental model, but that it doesn't match the reality in C++. Reading from the standard input essentially works like this:

There is a >> operator — in this context, it's often called a get operator — that allows you to consume a single object (e.g., an integer, a double) from an input stream such as std::cin. Depending on the type of object you're trying to consume, things will work a little bit differently, but the general rule is that this will skip initial whitespace (like spaces, tabs, and newlines), then greedily consume characters until one is found that isn't intended to be part of the object being built. If the input buffer is exhausted before the object is fully built, the user will need to enter another line of input into the shell window and press Enter.

The precise rules for what is consumed depends on the type of variable you're reading input into. A few examples (with some more esoteric details sometimes left out for brevity) are:

Putting all of these ideas together into a short example, consider this code fragment:

int i;
double d;
std::string s;

std::cin >> i >> s >> d;

If you executed this code fragment and this was the first use of std::cin (or the input buffer is empty), the user would need to type a line of input into the shell window and hit Enter. Suppose the user typed 100 CMG 475.875 and hit Enter. Here's what would happen:

Additionally, the function std::getline(an input stream, a string variable) is a handy tool, albeit one that's a little bit different than the one above. It consumes all of the text in the input buffer, up until it reaches a newline character, storing all of the text in the given string variable. Only if the input buffer was completely empty (i.e., nothing, not even a newline character!) will this require a user to type input into the shell window and press Enter. It's important to understand that this function may store the empty string into the string variable you give it if there is nothing but a newline character in the buffer; this is an important nuance to get right when you mix uses of the >> operator with the std::getline function, but not particularly hard to get right once you understand how things work. (One important detail to understand is that the newline character appearing at the end of each line of input is considered whitespace, so you'll quite often have a newline character in the input buffer in cases where you might intuitively expect it to be empty.)

One last tool that can be helpful is a member function you can call on std::cin called ignore, which you can use to skip a certain number of characters in the input buffer. For example, std::cin.ignore(1) would skip one character in the input buffer (i.e., move the input buffer's cursor forward one character).

For the time being, we'll ignore input that is erroneous with respect to type, such as the word Boo where an integer is expected, as it's not a particularly interesting problem to solve and requires techniques that can be tedious to get right, though you can certainly feel free to research parsing and error-checking techniques if you're interested in them. Practical programs are generally written to handle erroneous input in a graceful way, but we need to pick our battles in this course — we only have ten weeks! — so we'll leave this one to be fought another time.


The code

Some of the Notes and Examples pages this quarter will include a code example that you download and use on your ICS 45C VM. These code examples include not only code but comments that explain what it's doing and why, so they can be instructive in understanding the concepts that we're learning about.

When there is a code example, it will have an official moniker, a name that uniquely identifies it and hooks into some automation tools in the ICS 45C VM, which will be listed at the top of the page. The official moniker of this one is StandardIO.

Downloading code examples for use on the ICS 45C VM

If you want to view, compile, and run the code examples on the ICS 45C VM, I've set up some automated tools to make that job easier.

First of all, you may need to refresh your ICS 45C VM environment, to make sure that you have the necessary project template. You can do that by issuing this command:

ics45c refresh

Once you've done that, you will have a project template called example, which includes a script called download that can be used to download a code example from the course web site and install it into your project automatically. Choose a name for your new project, then issue the following commands to start a new project and download the code example into it.

ics45c start YOUR_CHOSEN_PROJECT_NAME example
cd ~/projects/YOUR_CHOSEN_PROJECT_NAME
./download StandardIO

Having issued these three commands, the app, exp, and gtest directories in your new project directory should contain all of the header and source files that comprise the code example. You can use the ./build and ./run scripts, as usual, to compile and run the example program, and you can use any editor to view it.

Alternatively, I'll provide a link that will let you download the code example manually, if you'd prefer to view it outside of the ICS 45C VM. The complete, commented code for this example can be downloaded by clicking the link to the tarball below: