Unit 6: The CS1010 I/O Library
Learning Objectives
After this unit, students should:
- understand the notion of standard input (
stdin
) and output (stdout
) channels in Unix; - be able to use the various print and read functions available in
libcs1010
to perform I/O. - be aware that there exist pitfalls in using
printf
andscanf
functions provided by C, and students should only use these functions if they know what they are doing.
Interacting with Users
Our first C program that computes the hypotenuse doesn't do much -- it simply computes \(\sqrt{3^2 + 4^2}\). The value to be computed is hard-coded, and the result computed is not displayed.
To make this program more general and useful, we need to do two things:
- We need to compute the hypotenuse for any length of the base and the height of a right-angled triangle. Hard-coding the lengths in the program does not make the program useful. Rather, we should read these values from the users.
- We need to output the result of the computation to the users.
In other words, to make the program more general and useful, we need to perform input and output, or I/O, in our program.
Standard Input and Standard Output
Before we talk about how to read the input values and display the output values, we have to first talk about where the input comes from and where the output goes.
In Unix-flavored operating systems, the input is read from an abstract channel called the standard input or stdin
for short, and an output is sent to an abstract channel called the standard output or stdout
for short.
The fact that these channels are abstract is a powerful concept -- when we write our code, we do not have to worry about where the inputs come from and where the outputs go to. It will depend on how the user runs our program. Thus, it allows the users of our program the flexibility to control where the data comes from or goes.
For instance, the standard input, by default, reads from the keyboard. But the user can choose to read from a file, using the redirection <
operator from the command line or the output of another process, using the pipe |
operator from the command line. Similarly, the standard output, by default, writes to the terminal. But the user can choose to write to a file using the redirection >
operator on the command line or to the input of another process, using the pipe |
operator, again, on the command line when invoking the program. You will see how cool these are later. But for C programming, it suffices to know for now that we only need to read from stdin
and write to stdout
in our code, and we let the users decide where they come from / go to.
No printf
and scanf
(yet)
In almost all articles and textbooks on C that I have seen, the scanf
and printf
functions are taught as the standard C library functions to perform the input and output respectively. The function scanf
, however, is tricky to use correctly and securely. The function printf
comes with many nuances, such as having to remember the different conversion specifiers and modifiers.
CS1010 would rather not teach you scanf
and printf
at this stage and instead focus on how to solve computational problems with C. As such, CS1010 provides you with a library to perform I/O. The library provides a small set of essential functions to read and write long
values, double
values, space-separated words, and lines of text.
Use printf
and scanf
only if you know what you are doing
If you insist on using printf
and scanf
for your assignments, make sure you know what you are doing. Otherwise, you will run into strange bugs if you are not using them correctly (and it is non-trivial to use them correctly). If you are an experienced programmer with C, you can skip ahead to take a look at Unit 27 on the pitfalls of using printf
and scanf
.
You can find the documentation for the CS1010 I/O Library here. We will see how to use the library to improve our hypotenuse computation program here.
Using the CS1010 I/O Library
Let's modify our earlier program to now read the base and height from stdin
, compute the hypotenuse, and print the results out to stdout
.
Example: Reading and writing with standard I/O | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
The first change you see (on Line 2) is to include the file cs1010.h
, which includes the declaration of functions provided by the library. On Lines 17 and 18, we introduce two new long
variables named base
and height
, which we initialized with the returned value from cs1010_read_long()
. The function cs1010_read_long
reads a long
value from stdin
and returns the value. For now, we assume that the inputs are correctly passed to the program.
Remember to include ()
C requires that function calls be identified with ()
. It is common for beginners
to skip ()
and call a function like long base = cs1010_read_long;
leading to
compilation errors.
Finally, on Line 20, we print the resulting hypotenuse to stdout
using the library function cs1010_println_double
. Note that there are two versions of functions to print a double
value: cs1010_println_double
and cs1010_print_double
. The one with println
prints a newline character as well.
Refer to the CS1010 Compilation Guide on how to compile a program that uses the CS1010 I/O library.
When to Use Variables
One of the decisions that we need to make when writing programs is when to introduce a new variable into our code. In cases where we need to keep track of a state (e.g., \(i\) and \(m\) from Unit 2), using a variable is necessary. In other instances, we may want to use a variable to store a value so that we do not need to compute the same value repeatedly (e.g., \(k\) from Unit 2).
Sometimes, however, it is useful to introduce a variable for readability. For instance, the main
function above can be written as a single statement without any variable and assignment.
Example: Calculating hypotenuse without declaring any variables | |
---|---|
1 2 3 4 |
|
The resulting code, however, is not necessarily easier to comprehend. On the other hand, the following snippet is easier to comprehend:
1 2 3 4 5 6 7 |
|
Here, we introduce variables with a descriptive name and indicate what each value read from stdin
is meant for, and what the value we are printing to stdout
is. The sequence of execution is also clearer as we read the C statements from top to bottom: we read the two inputs first, then we compute the hypotenuse, then we print.
When writing programs, it is sometimes useful to declare and introduce new variables to temporarily hold a value to make your code more readable.
We, however, need to avoid overdoing it. For instance, consider the code below:
1 2 3 4 5 6 7 8 9 10 11 |
|
The variable to_be_squared
does not improve the readability of the code.