Courses/CS 2114/Lab Manual/Input and Output

From A-State Computer Science Wiki
< Courses‎ | CS 2114‎ | Lab Manual
Revision as of 07:28, 29 January 2018 by Gidget (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Input and Output

Introduction

The purpose of this lab is to complete the discussion on console input and output and introduce the use of files for input and output of information.

Topics Covered in this Lab:
  • input and output streams
  • formatting of output
Questions Answered in this Lab:
  • How can files be used in a program to read and write information?
  • What is a stream and how do streams relate files to keyboard input and monitor output?
  • What must be included to do I/O in C++?
  • How can the output be formatted?
Demonstrable Skills Acquired in this Lab:
  • ability to manage input and output streams
  • ability to format output
  • comprehension of basic sequential file access

Input and Output

As you have seen, a program that has a practical purpose must be able to receive data from outside of itself as well as send results to the outside world. The data obtained by the program is termed input; if you were to imagine a diagram, it would show the data moving into the program. In this same diagram, the results sent to the outside would be shown as moving out of the program; hence the term output.

I/O Streams

In C++, input and output is done by way of streams of bytes, where a byte can be thought of as equivalent to a single keystroke. These streams are sequential in nature. If the user of a program types '1', '2', followed by '3', the stream will keep the keystrokes in that same order. Likewise, the stream will display the output in the exact order written by the programmer such as the example shown below and seen in the previous lab.

Examplecode.pngExample Code

CS2114MoreMeaningfulOutput.png

Recall the stream extraction operator (>>) and the stream insertion operator (<<) "point" to direct the flow of the data.

Consider the include and using statements of each program to date.

Examplecode.pngExample Code

CS2114iostream cin cout.png

The iostream header file associates an input stream from the keyboard with cin (console input) and an output stream to the display with cout (console output). The user can then move characters from the keyboard into program variables or move characters from the program to the display without concern to the details of how these actions occur.

It is also possible to define additional streams in a program. Such streams might be associated with a file on the disk drive. The files we will examine will be text files.

The fstream library contains three different ways to define a stream associated with a text file.

Stream Default Purpose
fstream provides the ability to both read from and write to a file
ifstream provides the ability to read from a file
ofstream provides the ability to write to a file

File stream variables, often referred to as file handles, are used in four logical contexts in a program:

define
set aside storage for the stream and label it
open
associate (or bind) the logical stream identifier with the physical file
use
move characters to or from the file stream
close
end the association of the logical file with the physical file; release computer resources

When dealing with file streams, it is important to note that the program does not directly read from or write to a file. Instead, the computer hardware and device drivers take on this role. The program will actually deal with a buffer, which is just an area in memory where the computer holds a block of read data from a file or a block of data to be written to a file. The file streams are sequential just as the console streams are.

When a read occurs, the computer will read enough data to fill the buffer; it will "remember" where it finished reading and will begin from there on the next read. When the program performs a read, it can rely on the computer to supply it with the next available data in the buffer. There is no need for the program to keep track of where it is in the data.

When the program executes a write statement, the data is actually written to the buffer. When the buffer becomes full, the computer will write the information contained in the buffer and, again, "remember" where it wrote the last byte so it can begin writing there the next time the buffer is filled or when it is flushed. When a file opened for writing is closed, the buffer is flushed, or written, before the association between the logical file and the physical file is broken.

User-Defined Output Streams

Let's begin by examining ofstream.

ofstream

Createproject.pngCreate a new directory sp03, change into it, and create a new program in file sp03t1.cpp with an include and using for ofstream.
Verbatimcode.pngCode Illustration

CS2114include fstream.png

File stream variables of type ofstream may now be created in the program, just as integer variables of type int are; such a variable is more complex than an integer, but it is a variable nevertheless.

Add the following sequence to function main to illustrate the four logical contexts of file streams.

Verbatimcode.pngCode Illustration

CS2114StreamContexts.png

Defining a stream variable is very much like defining an integer. Opening a stream variable is said to bind a logical file to a physical file; in the sequence above, outfile is bound to "out1.txt." Using a user-defined stream variable is like using cout (or cin). Closing a stream variable "unbinds" it from a physical file. Execute the program and examine the directory containing the program file sp03t1.cpp. It should now contain the file out1.txt with the "hello, world" contents specified.

A shorthand notation is sometimes used to combine the definition and opening of a file stream. Add the following to the program.

Verbatimcode.pngCode Illustration

CS2114StreamContextShorthand.png

Execute the program and inspect the current directory for file out2.txt.

Note that a full path name such as c:\My Documents\cs2114\sp03\out2.txt can be specified when opening a file; without a path, the current directory is used. When specifying a Windows path in a filename, it must be remembered that the first backslash in a character string starts an escape sequence and a second backslash will complete the sequence for the desired backslash; consequently, "c:\\My Documents\\cs2114\\sp03\\out2.txt" could specify the example file in C++ program code on a Windows machine. On a Unix-base machine, the slash used in a path name is a forward slash; nothing special is required to include a forward slash in a path name. To create out2.txt in a different directory, such as the home directory on the class server, "/home/username/out2.txt", where username is your username, could specify the example file in C++ program code. Try this now.

Notice that out2.txt always contains the same data, regardless of the number of times the program is executed. Take a moment to verify this fact. By default, when ofstream opens a file for writing, if the file already exists, it will be completely overwritten. However, it is also possible to add to the end of an existing file; this is referred to as appending. Use vi Editor or similar text editor to create a new text file named out3.txt containing the single line "This line was entered manually." Verify the file is in the sp03 directory. In the program, following the include for fstream, add a using statement for std::ios (more about this in a moment) and add the following to function main.

Verbatimcode.pngCode Illustration

CS2114ofstream app.png

Execute the program multiple times opening and closing out3.txt each time; it should be observed to grow by one line on each execution.

app is an ios flag. A flag is something that is used to indicate mode or specify behavior; in this instance, app is used to indicate that the file should be opened in append mode. The following are the flags that we will use with text files.

ios Flag Opening Mode
app open the file for writing and place the position indicator at the end of the file
in open the file for reading; position indicator placed at the beginning of the file
out open the file for writing; any existing file with same name is erased; as file is empty, the position indicator is at the beginning

fstream

Occasionally, it is useful to be able to decide whether a file stream is to be used for input or output during program execution; in this case, declare the file variable to be of type fstream. (fstream is a class found in the fstream library.) Such a file can be used for output at one point and input at another in a program. Add the necessary using statement for fstream then add the following to function main, noting the specification of stream direction, or file mode, when the file is opened.

Verbatimcode.pngCode Illustration

CS2114fstreamOutput.png

Execute the program again and verify that out4.txt has been created as expected.

It is possible to use fstream to open a file for appending. Create a new text file named out5.txt containing the single line "This line was also entered manually." Close the file and add the following to function main.

Verbatimcode.pngCode Illustration

CS2114fstreamOutput app.png

Note that the file mode is set as the bitwise or of file modes out and app (append). To avoid explaining the bitwise operators further at this time, suffice it to say that or means both in this instance; thus, characters sent to the stream of outfile5 will be appended to the end of the existing file. Execute the program several times, opening and closing out5.txt each time; it should be observed to grow by one line on each execution. Note that file mode ios::out used with an existing file causes the file to be destructively overwritten. Remove ios::app from the definition, execute the program again and verify the file contains a single line.


User-Defined Input Streams

The preceding examples all dealt with file output. To illustrate file input, begin by creating file data03iL.txt in the sp03 directory with the following contents.

80
74
87

The contents of file data03iL.txt could represent an exam average, a final exam average and a lab average. Experiment with file input by adding a using statement for ifstream then adding the following to function main to read the first of these averages.

Verbatimcode.pngCode Illustration

CS2114ReadFromFile.png

Execute the program and verify that the first average is indeed read. The stream variable infile functions much like cin; note however that when cin moves data from its stream (the keyboard) to the designated variable, it also copies that data to the display. No such copy takes place for a stream variable such as infile.

It should also be pointed out that the integer representing 80 is stored in the input file as two separate characters, '8' and '0'. On input, these characters are removed from the stream and converted into a single integer value. The same thing happens in reverse when the integer is subsequently written to the screen via cout; the single integer is transformed into two characters for display.

Verifying Existence of Input File

Extra care needs to be taken with an input stream. It is an easy mistake to attempt to read from a file which does not exist; for example, the file name data03iL.txt might have been spelled incorrectly or it might have been created in the wrong directory. File streams have the ability to report whether or not they have been opened successfully. The file stream's is_open() function will return a value of true if the stream has been successfully opened, so this value can be used to verify at run-time that the file is indeed present. Include cstdlib at the top of the program, then modify your main function beginning at the location currently occupied by the first statement shown below so that the lines beginning with if and ending with the closing brace are inserted exactly as shown.

Verbatimcode.pngCode Illustration

CS2114ifstreamVerify.png

Function exit, which is located in cstdlib, the C Standard Library, causes much the same thing to occur as the return in main; note that where 0 is indicated by return to signal a program's successful completion, the 1 parameter of exit indicates failure. Execute the program to verify that data03iL.txt is present; change the program's spelling of the filename to dataiL.txt and re-execute the program to verify that the program performs correctly when the file cannot be found.

Correct the spelling of the input filename before continuing.

Formatted Output

In the same way as the iostream library provides the ability to obtain and display information via the console and fstream library provides the ability to obtain and store information via a file, the iomanip library provides the ability to control the way the output appears both on the display and in a file. The following is a list of manipulators provided by iomanip that can be used to alter the way data is output by default.

Manipulator Default Purpose Use
setw set width in which to display data setw(17)
fixed use fixed-point (decimal) notation fixed
setprecision set decimal precision; when used in conjunction with fixed, set number of decimal positions setprecision(3)
left set left alignment left
right set right alignment right

endl, found in iostream, is one other stream manipulator we will use often. It inserts a newline and then flushes the buffer.

When using the setw manipulator, if the number indicated is not large enough, the data will be displayed in its entirety using as many positions as necessary. This will result in data values being written side-by-side with no spacing between. By default, C++ will display very small and very large floating-point numbers in scientific notation; fixed will alter this behavior. By default, C++ will display six decimals of precision; setprecision modifies this behavior. Once fixed and setprecision have been set, they remain set until changed or reset; setw must be used each time the field width is to be set.

Include the iomanip library and add using statements for each of the above manipulators. Copy and paste the following to function main and examine the code and the results on the display and in the file.

Verbatimcode.pngCode Illustration
    // demonstrate precision and fixed notation
    double number = 1234.56789;
    cout << "number with no manipulators:  " << number << endl;
    cout << setprecision(5) << "number with 5 decimals of precision:  "
         << number << endl;
    cout << fixed
         << "number in decimal notation "
         << "(5 decimals of precision still set):  "
         << number << endl;
    cout << setprecision(3)
         << "number in decimal notation with 3 decimals of precision:  "
         << number << endl;

    cout << endl << endl;

    // demonstrate setw and alignment
    // setw only
    cout << setw(10) << "Name" << setw(6) << "Exam1" << setw(6) << "Exam2"
         << setw(6) << "Exam3" << setw(6) << "Exam4" << endl;

    cout << setw(10) << "Buggs" << setw(6) << 77 << setw(6) << 81
         << setw(6) << 74 << setw(6) << 89 << endl;

    // precision still set, note run-on due to insufficient width
    cout << setw(10) << "Daffy" << setw(6) << 89.5 << setw(6) << 93.4
         << setw(6) << 88 << setw(6) << 91.3 << endl;

    // attempt to left align only the name
    cout << left << setw(10) << "Donald" << setw(6) << 85 << setw(6) << 89
         << setw(6) << 93 << setw(6) << 100 << endl;

    // reset precision, left align name, right align scores
    cout << setprecision(1);
    cout << left << setw(10) << "Elmer" << right << setw(6) << 98.7
         << setw(6) << 79 << setw(6) << 68 << setw(6) << 75.5 << endl;

    // demonstrate setw does not remain in effect
    cout << setw(10) << "Taz" << setw(6) << 73 << 76 << 79 << 83 << endl;

    cout << endl << endl;

    // correct table output sent to file
    ofstream fout("table.txt");

    fout << left << setw(10) << "Name" << right << setw(6) << "Exam1" << setw(6)
         << "Exam2" << setw(6) << "Exam3" << setw(6) << "Exam4" << endl;
    fout << left << setw(10) << "Buggs" << right << setw(6) << 77 << setw(6)
         << 81 << setw(6) << 74 << setw(6) << 89 << endl;
    fout << left << setw(10) << "Daffy" << right << setw(6) << 89.5 << setw(6)
         << 93.4 << setw(6) << 88 << setw(6) << 91.3 << endl;
    fout << left << setw(10) << "Donald" << right << setw(6) << 85 << setw(6)
         << 89 << setw(6) << 93 << setw(6) << 100 << endl;
    fout << left << setw(10) << "Elmer" << right << setw(6) << 98.7
         << setw(6) << 79 << setw(6) << 68 << setw(6) << 75.5 << endl;
    fout << left << setw(10) << "Taz" << right << setw(6) << 73 << setw(6)
         << 76 << setw(6) << 79 << setw(6) << 83 << endl;

    fout.close();

Notice that although number is rounded on the display, the actual value stored in number does not change as evidenced by the output following the rounding. Output formatting is just that - formatting. No changes are made to the values stored in variables.


Reading Characters and Strings

Createproject.pngCreate a new program in file sp03t2.cpp with an include and using for cin.

cin works with any data type, including char (character), as well as user defined objects such as string. Recall that string is available through the string library and will hold a series of characters.

  • create a char variable named ch and a string variable called name
  • prompt the user to enter a character and then his/her name
  • read the character into ch and the name into name
  • echo the character back to the screen and greet the user by name

Execute the program and test this code with

Data icon.pngTest your code with the following data:
the letter B followed by a space followed by the name Buggs

The program should work as expected with this input. Execute the program again and test it with

Data icon.pngTest your code with the following data:
the letter B followed by the Enter key followed by the name Buggs

Again, the program should work as expected with this input. Execute the program again and test it with

Data icon.pngTest your code with the following data:
the tab character (press the Tab key) followed by Enter followed by the name Buggs

A problem occurs with this test data. What happened when the program attempted to read the tab character? Recall that cin uses whitespace to separate pieces of data. Since the tab character is considered whitespace, it was ignored by cin, so the B from the name Buggs was read for the character ch and the remaining characters, uggs, were read as the string name.

cin.get()

Whitespace cannot be read by cin as it is used as a data delimiter. To read whitespace as a character, a programmer should employ the cin object's get() method. cin.get() will read a single character including any considered to be whitespace. The syntax of cin.get() is as follows.

Information Icon.pngSyntax
cin.get(ch);

A less commonly seen syntax is

Information Icon.pngSyntax
ch = cin.get();

In either usage shown above, ch is the variable in which the character is stored.

Modify your program as follows

  • remove the input statement from your code
  • add a character input statement implementing cin.get()
  • immediately following the character input statement, add a cin statement to read the name

Execute the program and test it with

Data icon.pngTest your code with the following data:
the tab character (press the Tab key) followed by a space followed by the name Buggs

The program should work as expected with this input. Execute the program again and test it with

Data icon.pngTest your code with the following data:
the letter B followed by a space followed by the name Buggs Bunny

A problem occurs with this test data. As whitespace is used to delimit data, cin read Buggs then stopped. The result is that Buggs is stored in the string name and Bunny remains in the buffer until the program ends execution.


getline()

getline() will read whitespace within a series of characters. The syntax for the getline() function is

Information Icon.pngSyntax
getline(istream, stringIdentifier);

or, optionally,

Information Icon.pngSyntax
getline(istream, stringIdentifier, delimiter);

where istream is the input stream, such as cin, stringIdentifier is the variable which will store the string. Optionally, the programmer can specify a delimiter, a single character, that is used to separate data. By default, the delimiter is the newline character. The newline character is introduced into the stream whenever the Enter key is pressed.

Modify your program

  • delete the input of the string from your code
  • use the getline() function to read the name using a default delimiter of the newline character
    getline(cin,name);
    

The geline() call used above will read any characters that were typed up until the Enter key was pressed and store those character in the string called name.

Information Icon.png If strings are separated by a character, such as a comma, as in the line
Santa Fe,New Mexico\n
getline() should be utilized with the delimiter specified. To read the city name specified in the above line into the string named city use the statement
getline(cin, city, ',');
Santa Fe will be stored in city, the comma delimiter will be discarded and the characters New Mexico\n will still be waiting in the buffer.


Execute the program again and test it with

Data icon.pngTest your code with the following data:
the letter B followed by a space followed by the name Buggs Bunny

The program should work as expected with this input. Execute the program again and test it with

Data icon.pngTest your code with the following data:
ABC followed by a space followed by the name Buggs Bunny

A problem occurs with this test data. The single character A is read into the character ch but the string name contains BC Buggs Bunny rather than Buggs Bunny.

cin.ignore()

The need to discard input has been demonstrated. cin's ignore() method is capable of discarding unwanted input. The syntax for cin.ignore() is

Information Icon.pngSyntax
cin.ignore(number_of_characters_to_ignore);

An example call would be

Examplecode.pngExample Code
cin.ignore(); // ignore one character

This call to cin.ignore() will discard the next character in the buffer. To discard the next ten characters in the buffer, execute the call cin.ignore(10);

To resolve the issue previously introduced, we could add

Examplecode.pngExample Code
cin.ignore(2); // ignore two characters

in order to ignore the unwanted BC from our last input. However, the space between BC and Buggs must also be ignored. Additionally, this line of code will not work if ABCD followed by a space followed by Buggs Bunny is entered.

An alternative is to additionally specify a delimiter.

Information Icon.pngSyntax
cin.ignore(number_of_characters_to_ignore, delimiter);

Again, the delimiter is a single character used to separate pieces of data. When a delimiter is specified, either the number_of_characters_to_ignore is ignored, or the number of characters up to and including the delimiter is ignored. So if the number_of_characters_to_ignore is not large enough, there will still be a problem.

One way to resolve this problem is to specify the maximum number of characters the stream can contain at one time.

Examplecode.pngExample Code
// clear the buffer
cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

where std::numeric_limits<std::streamsize>::max() is accessed by including the limits library. The effect of the above call to cin.ignore() is to discard all input up to and including the specified delimiter of \n or to discard all input in the buffer if no delimiter is encountered.

Modify your program to

  • read a character (statement already in place in program) then
  • discard any number of characters until a space is seen then
  • read a name (statement already in place in program)

Execute the program again and test it with

Data icon.pngTest your code with the following data:
ABC followed by a space followed by the name Buggs Bunny

The program should work as expected with this input. Execute the program again and test it with

Data icon.pngTest your code with the following data:
B followed by a space followed by the name Buggs Bunny

The program should work as expected with this input.