10: Files and Streams

What We Will Cover


Continuations

Questions from last class?

Homework Questions?

10.1: Streams and File I/O

Learner Outcomes

At the end of the lesson the student will be able to:

  • List the types of files
  • Describe the purpose of a stream
  • Read and write text files

10.1.1: About Program I/O

  • Program I/O = Program Input/Output
    • Input to and output from programs
  • Input can be from a keyboard, mouse or file
  • Output can be to a display screen, printer or file
  • Note that files can be both input and output devices for programs
  • Advantages of file I/O:
    • Data still exists after the program ends
    • Input can be automated (rather than entered manually)
    • Output from one program can be input to another
  • To store and retrieve data in a file, we need two items:
    • A file
    • A file stream object
  • We will look at files first

10.1.2: Files

File: a collection of data stored under a common name on a storage medium.

  • Files provide long-term storage of large amounts of data
  • Usually, you store files on non-volatile storage mediums
    • Magnetic disks
    • Optical disks
    • Flash storage, like USB storage devices
  • File are a single sequence of bytes

    Byte 0 Byte 1 Byte 2 ... Byte n−1 End-of-file marker

  • The operating system keeps track of the number of bytes in a file
  • Files must have a name
    • Naming requirements depend on the underlying operating system (OS)
  • The operating system organizes files into directories

10.1.3: Types of Files

  • All data in a file is ultimately just zeros and ones
  • Each binary digit can have one of two values: 0 or 1
  • A bit is one binary digit
  • A byte is a group of eight bits
  • These binary digits may represent integer values or text characters
  • It is up to the program using the file to understand the meaning and internal format of the data
  • In general, programs interpret data using two broad categories: text and binary

Text Files

  • In text files, the bits represent printable characters
  • Files are usually stored as one byte per character (ASCII)
  • Each line is delimited by end-of-line characters:
    • Macintosh (before OS-X): "\r"
    • Unix: "\n"
    • Windows: "\r\n"
  • An example of a text file is source code
  • You can read text files because each byte is interpreted by a program as textual characters
  • Some of these programs, like TextPad, then display the textual data to your computer's screen
  • Since there are many programs that read and display text, text files are called human readable

Binary Files

  • Data other than text is usually referred to as binary data
  • Each bit represents some type of encoded information
    • Such as program instructions or integer data
  • Binary files are easily read by the computer but not by humans
  • The following table compares binary and text values saved in a file
  • First we consider the value "1234" as ASCII codes and compare these bits to a binary value of 1234
  • As we can see, the bit patterns are different for the same data when stored as text and binary

Comparing Binary and Textual Data

Description Byte 0 Byte 1 Byte 2 Byte 3
"1234" as char's '1' '2' '3' '4'
"1234" as ASCII codes (bytes) 49 50 51 52
"1234" as ASCII codes (bits) 00110001 00110010 00110011 00110100
(int) 1234 as binary bits 00000000 00000000 00000100 11010010

10.1.4: Streams

Stream: a one-way transmission path that either delivers data to a destination (screen, file, etc.) or that takes data from a source (keyboard, file, etc.)

  • A stream connects a program to an I/O object
  • Input stream: an object that provides a sequence of bytes to a program

    input stream

  • Output stream: an object that accepts a sequence of bytes from a program

    output stream

  • cin and cout are input and output streams

File Streams

File stream: a one-way transmission path used to connect a program to a file.

  • File streams can be either input or output streams
  • File input streams receive data from a file
  • File output streams send data to a file
  • File I/O uses streams of type ifstream and ofstream
  • Each file your program uses will need a separate file stream object

Streams and objects

  • Streams are objects and thus cin and cout are objects
  • Objects are special variables that can have a function associated with them
  • To call a function of an object, you use the dot operator
  • An example of using the dot operator is with cout is shown below
cout.setf(ios::fixed);     // fixed notation, not scientific
cout.setf(ios::showpoint); // show decimal point
cout.precision(2);         // show 2 decimal places

10.1.5: Example of File I/O

  • Let us consider an example that reads from a file and writes to a file
  • The program reads from a file named infile.txt, which contains the following values:
    10
    20
    30
    
  • After summing the values, the program writes them to a file named outfile.txt
  • Consider the following code and try to identify:
    1. What is the name of the input stream?
    2. Which line opens a file for reading
    3. What is the name of the output stream?
    4. Which line opens a file for writing
    5. Which line reads data from the input stream?
    6. Which lines write data to the output stream?

Example Program to Read and Write Files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
    Reads three numbers from the file infile.txt,
    sums the numbers, and writes the sum to the
    file outfile.txt.
*/
#include <fstream>   // for file I/O
#include <iostream>
using namespace std;

int main() {
    ifstream fin;
    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file opening failed.\n";
        exit(-1);
    }

    ofstream fout;
    fout.open("outfile.txt");
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(-1);
    }

    int first, second, third;
    fin >> first >> second >> third;
    fout << "The sum of the first 3\n"
         << "numbers in infile.txt\n"
         << "is " << (first + second + third)
         << endl;

    fin.close();
    fout.close();

    cout << "Processing completed\n";

    return 0;
}

Closing a Stream

  • After finishing reading and writing you should close the file streams
  • If you do not close an output stream, you may lose data stored in the output buffer
  • In addition, streams consume system resources and you should not keep open any more streams than needed

10.1.6: Procedure For File I/O

  1. Place the following include directives in your program file:
    #include <fstream>   // for file I/O
    #include <iostream>  // for cout
    using namespace std;
    
  2. Declare names for input and output streams:
    ifstream fin;
    ofstream fout;
    
  3. Connect each stream to a file using open() and check for failure:
    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }
    
    fout.open("outfile.txt");
    if (fout.fail()) {
        cout << "Output file failed to open.\n";
        exit(-1);
    }
    
  4. Read or write the data:
    • Read from a file with fin like using cin:
      fin >> first >> second >> third;
      
    • Write to a file with fout like using cout:
      fout << "first = " << first << endl;
      
  5. Close the streams when finished reading and writing:
    fin.close();
    fout.close();
    

More Information

Exercise 10.1

In this exercise we write a program that copies two numbers from an input stream to an output stream.

Specifications

  1. Copy the following program into a text editor, save it as copytwo.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <iostream>
    using namespace std;
    
    int main() {
        // Enter your code here
    
        return 0;
    }
    
  2. Save the file infile.txt to the same directory as your program source code.

    We will read from this file after writing our program.

  3. Place the following include directives in your source code file:
    #include <fstream>   // for file I/O
    #include <iostream>  // for cout
    using namespace std;
    
  4. Inside main(), declare names for the input and output streams:
    ifstream fin;
    ofstream fout;
    
  5. Add code to connect each stream to a file using open() and check for failure:
    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }
    
    fout.open("outfile.txt");
    if (fout.fail()) {
        cout << "Output file failed to open.\n";
        exit(-1);
    }
    
  6. Add statements to read two numbers from the input stream. For example, here is possible code for reading the first number:
    int first;
    fin >> first;
    
  7. Add statements to write the two numbers to the output stream. For example, here is possible code for writing the first number:
    fout << "first = " << first << endl;
    
  8. Close the streams when finished reading and writing:
    fin.close();
    fout.close();
    
  9. Compile and run your modified program to make sure you made the changes correctly.

    Notice that you do not see any output on the screen for file reading or writing. The output stream wrote the program output to the output file.

  10. Using TextPad or WordPad, open the output file you created and verify that two numbers were copied. Your output file should look like:
    first = 10
    second = 20
    
  11. Using Notepad, open the output file you created and compare the output with what you saw in TextPad or WordPad.

    Notice how Notepad cannot display the end of line characters for Unix. However, TextPad and WordPad can display Unix end-of-line characters correctly.

  12. Submit your program source code to Blackboard as part of assignment 10.

Listing of copytwo.cpp

Listing of copytwo.cpp

Check Yourself

As time permits, be prepared to answer these questions. You can find more information by following the links after the question.

  1. What two items are needed to connect a file to a program? (10.1.1)
  2. What is a file? (10.1.2)
  3. What are the two types of files? (10.1.3)
  4. What is used to connect a program to a file? (10.1.4)
  5. What library is required for file I/O? (10.1.6)
  6. How are input and output streams declared? (10.1.6)
  7. What stream function is used to open a file? (10.1.6)
  8. What stream function is used to check that a file opened correctly? (10.1.6)
  9. How do you read and write data using a stream? (10.1.6)
  10. Why should you close a stream after you are done using it in your program? (10.1.5)

10.1.7: Summary

  • Programs use streams for input and output
  • cin and cout are types of streams
  • File I/O also uses streams of type ifstream and ofstream
  • Streams are objects and have functions associated with them
  • Some of the functions we call for file I/O include:
    • open("fileName"): establishes a connection from the stream to the file
    • fail(): tests the stream for errors
    • close(): closes the connection of the stream to the file
  • You use input streams like you use cin:
    fin >> first >> second >> third;
    
  • You use output streams like you use cout:
    fout << "first = " << first << endl;
    
  • To halt execution of your program on error, use the exit function:
    exit(-1);

10.2: Reading Data From Files

Learner Outcomes

At the end of the lesson the student will be able to:

  • Read data of various types from a file
  • Check for end-of-file conditions
  • Read data from a file in a loop

10.2.1: Reading Data Using an Input Stream

  • You normally use the extraction operator >> to input data from a file
    ifstream fin;
    // ... more code here
    double num;
    fin >> num;
    cout << "Read data: " << num << endl;
    
  • The variable fin is a stream operator and works just like cin:
    1. Skips whitespace
    2. Reads non-whitespace characters
    3. Stops reading when whitespace is found
  • This works for all data types including characters and strings
  • You can see this behavior in the following program
  • If we change the data type, we can still read data from infile.txt
  • The input stream changes the data into the type needed for the variable

Reading From a File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <fstream>
#include <iostream>
using namespace std;

int main() {
    ifstream fin;

    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file opening failed.\n";
        exit(-1);
    }

    double num;
    fin >> num;
    cout << "Read data: " << num << endl;

    fin.close();

    return 0;
}

10.2.2: Using Loops to Read Files

  • Sometimes you do not know how many data items are in a file
  • To solve this, the typical approach is to use a loop to process the file
  • When reading input from the console, we can read the input in the test condition
  • While the read is successful, the input stream returns a value interpreted as true
  • If the stream fails or closes, then the test condition fails
  • This failing condition returns a value interpreted as false by the loop
  • Thus the loop stops and the program continues after the loop
  • We can use this behavior to read from a file as shown below

Example Program Reading a File Using a Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fstream>   // for file I/O
#include <iostream>
using namespace std;

int main() {
    ifstream fin;
    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }

    double nextNum, sum = 0;
    int count = 0;
    while(fin >> nextNum) {
        cout << "Read: " << nextNum << endl;
        sum = sum + nextNum;
        count++;
    }
    cout << "average = " << (sum / count) << endl;
    fin.close();

    return 0;
}

10.2.3: Controlling File Loops with Boolean Variables

  • Another way to control loop termination is to use a boolean variable
  • To use boolean variables as a test condition for file loops, we code something like:
    bool more = true;
    while (more) {
        fin >> next;
        if (fin.fail()) {
            more = false;
        } else {
            // process input
        }
    }
    
  • The advantage of using a boolean variable is we can code multiple conditions for exiting a loop
  • Note that the following conditions have extraneous information:
    while (more == true)  // instead use: while (more)
    
    while (more == false) // instead use: while (!more)
    
  • The reason is that == true and != false are not needed for correct operation
  • You can see an example use of a boolean variable for reading files in the following program

Example Program Using a Boolean Variable for the Loop Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <fstream>   // for file I/O
#include <iostream>
using namespace std;

int main() {
    ifstream fin;
    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }

    double next = 0;
    double highest = 0;
    if (fin >> next) {
        highest = next;
    } else {
        cout << "Error: no data!\n";
        exit(-1);
    }

    bool more = true;
    while (more) {
        fin >> next;
        if (fin.fail()) {
            more = false;
        } else if (next > highest) {
            highest = next;
        }
    }

    cout << "Highest value: " << highest << endl;

    fin.close();

    return 0;
}

10.2.4: Reading File Data into a Vector

  • Sometimes you want to process the data in a file several times
  • One way to do this is to load the data into a vector
  • Then you can process the data as a list
  • For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

int main() {
    ifstream fin("infile.txt");
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }

    // Load data into a vector
    vector<int> data;
    int value;
    while(fin >> value) {
        cout << "Read: " << value << endl;
        data.push_back(value);
    }
    fin.close();

    // Process vector data
    double sum = 0;
    int count = data.size();
    for (int i = 0; i < count; i++) {
        sum = sum + data[i];
    }

    cout << "average = " << (sum / count) << endl;

    return 0;
}

10.2.5: Reading Files using getline()

  • Recall that you can read text using a loop and an input stream like fin:
    int count = 0;
    string word;
    while (fin >> word) {
        count++;
    }
    cout << count << " words.\n";
    
  • However, just like with cin, there are complications when you want to read words with spaces between them
  • >> skips whitespace and stops on encountering more whitespace
  • Thus, you only get a single word for each input variable
  • If you want to read a complete line of text like "Hello Mom!", you need to use getline()
  • For example:
    ifstream fin;
    // ... more code here
    string line;
    getline(fin, line);
    cout << "Read data: " << line << endl;
    
  • Recall that getline() stops reading when it encounters a '\n'
  • By contrast, fin >> variable operates as follows:
    1. Skips whitespace
    2. Reads characters
    3. Stops reading when whitespace is found
  • Thus if you mix fin >> variable followed by getline(fin, line), you get mysterious results
  • Just like with cin, you get around this problem you by either:
    1. Only use getline() before using fin (and never after using fin)
    2. Use fin >> ws; before using getline()

10.2.6: Using Loops with getline()

  • Sometimes you need to read and process an unknown number of lines in a file
  • For this you can use the getline() function in a loop as well:
    while (getline(fin, line)) {
        // process input
    }
    
  • While the read is successful, the getline() function returns a value interpreted as true
  • If the stream fails or closes, then the test condition fails
  • You can see an example of using getline() to read lines from a file in the following program

Example Program Reading a File Using getline() in a Loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fstream>   // for file I/O
#include <iostream>
using namespace std;

int main() {
    ifstream fin;
    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }

    string line;
    int count = 1;
    while(getline(fin, line)) {
        cout << "Line " << count << ": "
             << line << endl;
        count++;
    }

    fin.close();

    return 0;
}

Exercise 10.2

In this exercise we explore reading all the lines of a file using a loop.

Specifications

  1. Copy the following program into a text editor, save it as readwrite.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <fstream>   // for file I/O
    #include <iostream>
    using namespace std;
    
    int main() {
        // Enter code here
    
        return 0;
    }
    
  2. Save the file rawdata.txt to the same directory as your program source code.

    We will read from this file after writing our program.

  3. In main(), add code to declare an input stream named fin and to connect the stream to the input file. In addition, make sure you use open() and check for failure.

    For more information see section 10.1.6: Procedure For File I/O.

  4. In main(), declare a vector of type double named data.

    For more information see lesson 9.1.2: Defining Vectors.

  5. Add code to read all the values from the input file using a loop, print each input value to the screen, and save the input values in the vector.

    For more information see section 10.2.4: Reading File Data into a Vector.

  6. Add the following code to sum the input data and calculate the average:
    double sum = 0;
    int count = data.size();
    for (int i = 0; i < count; i++) {
        sum = sum + data[i];
    }
    double average = sum / count;
    cout << "average = " << average << endl;
    
  7. Compile and run your program and verify the screen output looks like:
    Read: 12.34
    Read: -9.87654
    Read: 2.3131
    Read: -89.506
    Read: 12.3333
    Read: 92.8765
    Read: -123.457
    average = -14.7109
    

    If there are any problems, compare your source code to the listing below.

  8. After calculating the average, add code to declare an output stream named fout and to connect the stream to an output file named output.txt. In addition, make sure you use open() and check for failure.

    For more information see section 10.1.6: Procedure For File I/O.

  9. Add the following code to write the vector data and average to the output stream:
    for (int i = 0; i < count; i++) {
        fout << data[i] << endl;
    }
    fout << "average = " << average << endl;
    fout.close();
    
  10. Open the output file you created and verify the vector data and average were written to the file. Your output file should look like:
    12.34
    -9.87654
    2.3131
    -89.506
    12.3333
    92.8765
    -123.457
    average = -14.7109
    

    If there are any problems, compare your source code to the listing below.

  11. Submit your program source code to Blackboard as part of assignment 10.

Listing of readwrite.cpp

Listing of readwrite.cpp

Check Yourself

As time permits, be prepared to answer these questions. You can find more information by following the links after the question.

  1. After opening a stream named fin, what code will read the first item in the file into the variable named first? (10.2.1)
  2. What control-flow statement do you use to read every number in a file when you do not know how many numbers are in the file? (10.2.2)
  3. What is a loop condition you can use to read every number in a file? (10.2.2)
  4. What is the advantage of using a boolean variable to control a loop reading from a file? (10.2.3)
  5. What is the advantage of reading file data into a vector? (10.2.4)
  6. How do you code a statement to read a string from a file that includes spaces? (10.2.5)
  7. What function do you use to clear the input buffer after reading with fin >> and before using getline()? (10.2.5)
  8. What is a loop condition you can use to read every line in a file? (10.2.6)

10.2.7: Summary

  • You can use the extraction operator to read data from a file:
    ifstream fin;
    // ... more code here
    double num;
    fin >> num;
    cout << "Read data: " << num << endl;
    
  • The variable fin is a stream operator and works just like cin
  • Similarly, you use the getline() function to read complete lines of text with spaces between words:
    ifstream fin;
    // ... more code here
    string line;
    getline(fin, line);
    cout << "Read data: " << line << endl;
    
  • Sometimes you do not know how many lines are in a file
  • To solve this, the typical approach is to use a loop to process the file:
    while(fin >> next) {
        // process input
    }
    
  • Another way is to use a boolean variable to control the loop:
    bool more = true;
    while (more) {
        fin >> next;
        if (fin.fail()) {
            more = false;
        } else {
            // process input
        }
    }
    
  • We can read entire lines in a loop as well:
    while(getline(fin, line)) {
        // process input
    }
    
  • In addition, we can read file data into vectors or arrays

10.3: More I/O Topics

Learner Outcomes

At the end of the lesson the student will be able to:

  • Append data to files
  • Code user input as file names
  • Code stream parameters in functions

10.3.1: Alternative Syntax for Open

  • It is possible to construct a stream and open a file in one step
  • Rather than:
    ofstream fout;
    fout.open("messages.txt");
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(-1);
    }
    
  • You can use:
    ofstream fout("messages.txt");
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(-1);
    }
    

10.3.2: Appending to a File

  • The standard open operation for writing begins with an empty file
  • Even if the file exists you loose all the contents
  • To prevent loosing the information, you must open for appending to a file:
    ofstream fout;
    fout.open("important.txt", ios::app);
    
  • If the file doesn't exist then ofstream creates it
  • If the file exists then ofstream positions itself to append to the end
  • The second argument is a constant defined in class ios
  • The following program will add a message to the file every time we run it

Example Appending to a File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <fstream>
#include <iostream>
using namespace std;

int main() {
    string message;
    cout << "Enter a message to add to the file: ";
    getline(cin, message);

    ofstream fout;
    fout.open("messages.txt", ios::app);
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(-1);
    }
    fout << message << endl;
    fout.close();

    cout << "See you later!\n";

    return 0;
}

10.3.3: File Names as Strings

  • Note that the argument to open() is a string:
    fin.open("infile.txt");
    
  • Instead of a literal string, we can use a string variable
  • However, we must use the c_str() function to convert to a C-string

Example Using a string for File Names

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <fstream>   // for file I/O
#include <iostream>
using namespace std;

int main() {
    string filename, line;

    cout << "Enter a file name: ";
    cin >> filename;

    ifstream fin;
    fin.open(filename.c_str());
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }

    while(getline(fin, line)) {
        cout << line << endl;
    }

    fin.close();

    return 0;
}

About C-strings

  • C++ offers a special kind of char array known as a C-string
  • A C-string is a hold over from the C programming language
  • However, many library functions still use C-strings and not string variables
  • Thus we need to convert string variables to C-strings in these cases
  • For more information see lesson 9.3.4: Character Arrays

10.3.4: Stream Parameters

  • Stream types can be formal parameters in functions
  • However, they must be call-by-reference parameters:
    void sayHello(ofstream& aStream) {
        aStream << "hello\n";
    }
    
  • Note that it is possible to leave out the 'f' in the stream parameter
  • This would give us a function like:
    void sayHello(ostream& aStream) {
        aStream << "hello\n";
    }
    
  • An ostream (no 'f') parameter accepts either cout or ofstream objects as arguments
  • Similarly, an istream (no 'f') parameter accepts either cin or ifstream objects as arguments
  • Using this feature, we can make functions work for both files and user I/O

Example Using Stream Parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <fstream>
#include <iostream>
using namespace std;

/**
    Outputs a line to the ostream.

    @param aStream the output stream
    @param line The string to output
*/
void output(ostream& aStream, string line) {
    aStream << line << endl;
}

int main() {
    ifstream fin;
    fin.open("infile.txt");
    if (fin.fail()) {
        cout << "Input file opening failed.\n";
        exit(-1);
    }

    ofstream fout;
    fout.open("outfile.txt");
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(-1);
    }

    string line;
    while(getline(fin, line)) {
        output(cout, line);
        output(fout, line);
    }

    fin.close();
    fout.close();

    return 0;
}

10.3.5: Reading File Data into a Vector of Objects

  • When we have a vector of objects, we can read data from a file into the vector
  • To accomplish this, we need to read the values for each object
  • Object-oriented deisgn principles say that an object should know how to read and write it's own data
  • Thus a good way to approach this problem is to add a read() function to the class that reads from a file stream like:
    void Product::read(ifstream& fin) {
        fin >> name;
        fin >> price;
    }
    
  • We want the file to read from a stream so we can use one file for many products
  • We open the input stream in main() and pass the stream in the function call:
    ifstream fin("products.txt");
    ...
    Product temp;
    temp.read(fin);
    
  • To read all the product data into a vector of objects, we use a loop as shown in the following example
  • To run the example, we read from the file: products.txt
  • Note that fin.good() returns true if no errors have occurred with the stream

Example Reading a File into a Vector of Objects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

class Product {
public:
    Product();
    Product(string newName, double newPrice);
    void read(ifstream& fin);
    void print() const;
private:
    string name;
    double price;
};

Product::Product() {
    name = "Unknown";
    price = 0.0;
}

Product::Product(string newName, double newPrice) {
    name = newName;
    price = newPrice;
    if (price < 0) {
        cout << "Error: negative price!\n"
             << "Setting price to 0.\n";
        price = 0;
    }
}

void Product::print() const {
    cout <<  name << " @ " << price << endl;
}

void Product::read(ifstream& fin) {
    fin >> name;
    fin >> price;
}

// Read from filename into the vector
void readFile(vector<Product>& list, string filename);

// Display vector data
void listProducts(const vector<Product>& list);

int main() {
    vector<Product> list;
    readFile(list, "products.txt");

    cout << "\nProducts in my store:\n";
    listProducts(list);

    return 0;
}

void readFile(vector<Product>& list, string filename) {
    ifstream fin(filename.c_str());
    if (fin.fail()) {
        cout << "Input file failed to open.\n";
        exit(-1);
    }

    while(fin.good()) {
        Product temp;
        temp.read(fin);
        if (fin.good()) {
            list.push_back(temp);
        }
    }
    fin.close();
}

void listProducts(const vector<Product>& list) {
    for (unsigned i = 0; i < list.size(); i++) {
        Product temp = list[i];
        temp.print();
    }
}

10.3.6: Converting Between Strings and Numbers

  • You can read or write to strings, rather than files, using string streams
  • This is useful for converting between numbers and strings
  • The istringstream class reads characters from a string
  • Similarly, the ostringstream class writes characters to a string
  • To make use of these string streams, you must include the sstream library:
    #include<sstream>
  • Using an istringstream, we can read numbers that are stored in a string by using the >> operator:
    string str = "123.4567";
    istringstream instr(str);
    double number;
    instr >> number;
    cout << number << endl;
    
  • Similarly, by writing to a string stream, you can convert numbers to strings:
    ostringstream outstr;
    outstr << 12.345;
    string strVal = outstr.str();
    cout << strVal << endl;
    
  • Since string conversion is common, it is useful to have helper functions for these tasks
  • The following program shows such helper functions

Example Using String Streams to Convert Between Strings and Numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <sstream>
#include <iostream>
using namespace std;

/**
    Convert a string to a double.
*/
double stringToDouble(string s) {
   istringstream instr(s);
   double number;
   instr >> number;
   return number;
}

/**
    Convert a double to a string.
*/
string doubleToString(double num) {
    ostringstream outstr;
    outstr << num;
    return outstr.str();
}

int main() {
    string str = "123.4567";
    double x = stringToDouble(str);
    cout.setf(ios::fixed);
    cout.setf(ios::showpoint);
    cout.precision(2);
    cout << "Numerical value: " << x * 2 << endl;

    string strVal = doubleToString(x * 2);
    string msg = "String value: " + strVal + "\n";
    cout << msg;

    return 0;
}

10.3.7: Other File Operations

  • Some file operations are not supported by C++ streams
  • In these cases, you use the C-style functions
  • These functions are part of the cstdio library:
    #include <cstdio>
  • This library is automatically included in our compiler
  • Note that these functions take C-string arguments
  • Thus you must use the c_str() function when using string variables

Commonly Used C-Functions for File Manipulation

Function Description
remove(fileName) Deletes the file specified by the C-string fileName.
rename(oldName, newName) Changes the file or directory name specified by the C-string oldName to the newName.
perror(message) Print the error message specified by the C-string along with the system error message.
system(command) Executes a command specified by the C-string like you were using the command line.

Example to Remove a File

string fileName;
cout << "File to remove: ";
cin >> fileName;
int result = remove(fileName.c_str());
if (result == 0) {
    cout << "File successfully removed\n";
} else {
    perror("Error removing file\n");
}

Example to Rename a File

int result;
string oldName, newName;
cout << "Old file name: ";
cin >> oldName;
cout << "New file name: ";
cin >> newName;
result = rename(oldName.c_str(), newName.c_str());
if (result != 0 ) {
    perror( "Error renaming file" );
}

Examples Using system() Function

system("ls");     // list files
system("clear");  // clear the screen
system("cmd.exe /c color 1E"); // change colors

Changing Console Colors

  • Since we are running Cygwin on Windows, we can use Windows to change the console colors
  • The Windows command function is a program named cmd
  • You can set the console colors using the COLOR command using:
    system("cmd.exe /c color attr");
  • Where attr is TWO hex digits
    • The first specifies the background
    • The second specifies the foreground
  • Each digit can be any of the following values:
    0 = Black       8 = Gray
    1 = Blue        9 = Light Blue
    2 = Green       A = Light Green
    3 = Aqua        B = Light Aqua
    4 = Red         C = Light Red
    5 = Purple      D = Light Purple
    6 = Yellow      E = Light Yellow
    7 = White       F = Bright White
    

Exercise 10.3

In this exercise we explore passing an input stream to a function.

Specifications

  1. Copy the following program into a text editor, save it as showfile.cpp, and then compile and run the starter program to make sure you copied it correctly.
    #include <fstream>   // for file I/O
    #include <iostream>
    using namespace std;
    
    void toScreen(ifstream& fin);
    
    int main() {
        string filename, line;
    
        cout << "Enter a file name: ";
        cin >> filename;
    
        // Start adding code here
    
        return 0;
    }
    
    // Add new function toScreen() here
    
  2. In main(), add code to declare an input stream named fin and to connect the stream to the input file entered by the user into the string variable filename. In addition, make sure you check for failure.

    For more information see section 10.3.3: File Names as Strings.

  3. Using the function prototype shown in the starter code, write the definition of the toScreen() function.

    The toScreen() function reads one line at a time from the input stream parameter using function getline(). Then the function displays the count of each line and the line read to the screen using cout. For more information see section 10.2.6: Using Loops with getline().

  4. Call the toScreen() function from main() using the input stream fin as an argument.
  5. Compile and run your program and verify the screen output looks like:
    Enter a file name: showfile.cpp
    1: #include <fstream>   // for file I/O
    2: #include <iostream>
    3: using namespace std;
    4:
    5: void toScreen(ifstream& fin);
    6:
    7: int main() {
    8:     string filename, line;
    9:
    10:     cout << "Enter a file name: ";
    11:     cin >> filename;
    12:     ifstream fin(filename.c_str());
    13:     if (fin.fail()) {
    14:         cout << "Input file failed to open.\n";
    15:         exit(-1);
    16:     }
    17:     toScreen(fin);
    18:     fin.close();
    19:
    20:     return 0;
    21: }
    22:
    23: void toScreen(ifstream& fin) {
    24:     string line;
    25:     int count = 1;
    26:     while(getline(fin, line)) {
    27:         cout << count << ": " << line << endl;
    28:         count++;
    29:     }
    30: }
    

    Notice that the file name entered by the user in this example was showfile.cpp. Thus you can use this listing to compare your work if you have problems.

  6. Submit your program source code to Blackboard as part of assignment 10.

Check Yourself

As time permits, be prepared to answer these questions. You can find more information by following the links after the question.

  1. How can you construct and open a file stream in one statement? (10.3.1)
  2. How do you keep a file opened for writing from destroying existing information? (10.3.2)
  3. What string function do you use to convert a string to a C-string? (10.3.3)
  4. How do you code streams as parameters for a function? (10.3.4)
  5. How can you read data from a file into an object before inserting it into a vector? (10.3.5)
  6. Using string streams, you can read from and write to strings rather than files. Why is this useful? (10.3.6)
  7. To delete the file named "error.log", what statement would you write? (10.3.7)
  8. To change the name of the file named "error.log" to "error.bak", what statement would you write? (10.3.7)

10.3.8: Summary

  • You can construct a stream and open it in one step:
    ofstream fout("messages.txt");
  • The standard open operation will create an empty file
  • You can open a file for appending data by using an extra argument:
    ofstream fout("important.txt", ios::app);
    
  • You can use string variables as the names of files
  • However, we must use the c_str() function to convert to a C-string:
    ifstream fin(filename.c_str());
    
  • Streams can be arguments to a function, but you must use call-by-reference
  • Type istream for function parameters works for both cin and ifstream
  • Type ostream for function parameters works for both cout and ofstream
  • You can read or write to strings, rather than files, using string streams
  • This is useful for converting between numbers and strings
  • Using an istringstream, we can read numbers that are stored in a string by using the >> operator
  • Similarly, by writing to an ostringstream, we can convert numbers to strings using the << operator
  • We developed some helper functions to support these conversions
  • In addition, we discussed file operations for removing and renaming files

10.4: Formatting Output

Learner Outcomes

At the end of the lesson the student will be able to:

  • Write code using output formatting functions
  • Write code using manipulators

10.4.1: Formatting Functions

  • Formatting functions are used to set the layout of the output
  • We have used formatting functions before with the "magic formula":
    cout.setf(ios::fixed);     // fixed notation, not scientific
    cout.setf(ios::showpoint); // show decimal point
    cout.precision(2);         // show 2 decimal places
    
  • However, there are many more formatting functions available
  • Remember that formatting functions do not affect the value of any variables
  • Also, note that these same functions apply to all output streams including cout
  • Function setf() sets a format flag for the stream:
    fout.setf(ios::fixed);      // Fixed notation
    fout.setf(ios::scientific); // Scientific notation
    fout.setf(ios::showpoint);  // show decimal point
    fout.setf(ios::showpos);    // Show + signs
    fout.setf(ios::left);       // Left justify
    fout.setf(ios::right);      // Right justify
    
  • Note how (ios::left) and (ios::right) operate:

    '    7' vs '7    '

  • Function unsetf() clears a format flag:
    fout.unsetf(ios::showpos);  // No more + signs
    
  • Function precision() sets the number of digits to display for floating-point numbers:
    fout.precision(2); // show 2 decimal digits
  • Note that if the fixed or scientific flag is set, then precision() controls the digits to the right of the decimal point
  • If neither the fixed or scientific flag is set, then precision() controls the total digits
  • Function width() sets or returns the field width:
    • Only applies to next item that is output
    • Width expands automatically so that entire item is always output
    fout.width(3);
    fout << 3 << endl; // ouputs <space><space>3
    fout.width(3);
    fout << 123.45 << endl; // ouputs 123.45
    
  • The following program uses formatting functions to provide neatly formatted output
  • Note that the formatting functions work with both cout and the ofstream fout

Example Formatting Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <fstream>   // for file I/O
#include <iostream>
using namespace std;

/**
 * Outputs a line to the ostream.
 *
 * @param aStream the output stream
 * @param line The string to output
 */
void formatOutput(ostream& aStream, double number);

int main() {
    double number;
    cout << "Enter a number and I will format it: ";
    cin >> number;

    ofstream fout;
    fout.open("formatted.txt", ios::app);
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(1);
    }

    formatOutput(cout, number);
    formatOutput(fout, number);

    return 0;
}

void formatOutput(ostream& aStream, double number) {
    aStream << "The formatted number is (";

    aStream.setf(ios::fixed);
    aStream.setf(ios::showpoint);
    aStream.setf(ios::showpos);
    aStream.setf(ios::right);
    aStream.precision(3);
    aStream.width(10); // only applies to next output

    aStream << number << ")" << endl;
}

More Information

  • Format flags: arguments for setf() and unsetf() from cppreference.com

10.4.2: Manipulators

  • Another way to format output is to use a manipulator
  • Manipulator: a function called in a nontraditional way
  • We have use manipulators before: endl
  • Other manipulators include:
    fout << fixed;      // Fixed notation
    fout << scientific; // Scientific notation
    fout << showpoint;  // show decimal point
    fout << showpos;    // Show + signs
    fout << left;       // Left justify
    fout << right;      // Right justify
    
  • Most manipulators are part of the <iostream> library
  • To use some manipulators you need to include:
    #include <iomanip>
    
  • Some manipulators may require arguments
  • setw(): sets the field width for the next output:
    cout << "Start" << setw(4) << 10 << setw(6) << 20;
    
  • setprecision(): sets the number of digits to display for floating-point numbers:
    cout << "$" << setprecision(2) << 10.4  << endl;
    
  • Note that if the fixed or scientific flag is set, then setprecision() controls the digits to the right of the decimal point
  • If neither the fixed or scientific flag is set, then setprecision() controls the total digits
  • For comparison, the equivalent of the "magic formula" using manipulators is:
    cout << fixed << showpoint << setprecision(2);
    
  • The following program uses several manipulators to provide neatly formatted output
  • Note that the manipulators work with both cout and the ofstream fout

Example Using Manipulators for Formatting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <fstream>   // for file I/O
#include <iostream>
#include <iomanip>
using namespace std;

/**
 * Outputs a line to the ostream.
 *
 * @param aStream the output stream
 * @param line The string to output
 */
void formatOutput(ostream& aStream, double number);

int main() {
    double number;
    cout << "Enter a number and I will format it: ";
    cin >> number;

    ofstream fout;
    fout.open("formatted.txt", ios::app);
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(1);
    }

    formatOutput(cout, number);
    formatOutput(fout, number);

    return 0;
}

void formatOutput(ostream& aStream, double number) {
    aStream << "The formatted number is ("
            << fixed
            << showpoint
            << showpos
            << right
            << setprecision(3)
            << setw(10)
            << number << ")" << endl;
}

Input Manipulator

  • C++ has an input manipulator that is useful for ignoring whitespace: ws
  • For example:
    fin >> ws; // clear whitespace from buffer
    

More Information

10.4.3: Example of Formatting Data

  • Let us look at an example of making messy data look neatly formatted
  • AConsider the following data file named: rawdata.txt:
    12.34    -9.87654
    2.3131     -89.506
    
    12.33333333 92.8765
    -1.234567e2
    
  • We want to write a program to take the raw data from the file and format it neatly
  • Also, we will write first to the screen and a then to a file

Example Application to Make Messy Data Neat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
 * Reads all the numbers in the file rawdata.txt
 * and writes the numbers to the screen and to
 * the file neat.txt in a neatly formatted way.
 */
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;

void formatData(istream& messyFile, ostream& neatFile,
        int precision, int fieldWidth);

int main()
{
    ifstream fin;
    fin.open("rawdata.txt");
    if (fin.fail()) {
        cout << "Input file opening failed.\n";
        exit(1);
    }

    ofstream fout;
    fout.open("neat.txt");
    if (fout.fail()) {
        cout << "Output file opening failed.\n";
        exit(1);
    }

    formatData(fin, cout, 5, 12);

    // Close and reopen input file
    fin.close();
    fin.clear(); // allows reuse of stream
    fin.open("rawdata.txt");
    if (fin.fail()) {
        cout << "Input file opening failed.\n";
        exit(1);
    }

    formatData(fin, fout, 5, 12);

    fin.close();
    fout.close();

    cout << "End of program.\n";
    return 0;
}

void formatData(istream& messyFile, ostream& neatFile,
        int precision, int fieldWidth) {
    neatFile.setf(ios::fixed);
    neatFile.setf(ios::showpoint);
    neatFile.setf(ios::showpos);
    neatFile.precision(precision);

    double next;
    while (messyFile >> next) {
        neatFile << setw(fieldWidth) << next << endl;
    }
}

Exercise 10.4

In this exercise we explore how to format output.

Specifications

  1. Create a text file named formatting.txt
  2. Complete the following and record the answers to the questions in the file.
  3. Run the following code and answer the question:
    #include <iostream>
    #include <iomanip>
    using namespace std;
    
    
    int main() {
        cout << "*";
        cout.width(5);
        cout << 123
             << "*" << 123 << "*" << endl;
        cout << "*" << setw(5) << 123
             << "*" << 123 << "*" <<  endl;
    
        return 0;
    }
    

    Q1: To how many numeric fields does width() or setw() formatting functions apply?

  4. Run the following code and answer the question:
    #include <iostream>
    #include <iomanip>
    using namespace std;
    
    
    int main() {
        cout << "*" << setw(5) << 123;
        cout.setf(ios::left);
        cout << "*" << setw(5) << 123;
        cout.setf(ios::right);
        cout << "*" << setw(5) << 123 << endl;
    
        return 0;
    }
    

    Q2: By default, are numbers output ios::left or ios::right?

  5. Run the following code and answer the question:
    #include <iostream>
    #include <iomanip>
    using namespace std;
    
    
    int main() {
        cout << "*" << setw(3) << 12345 << "*\n";
    
        return 0;
    }
    

    Q3: How many digits are displayed? Why?

  6. Run the following code and answer the question:
    #include <iostream>
    using namespace std;
    
    int main() {
        cout << "Enter your age: ";
        int age;
        cin >> age;
        cout << "Enter your full name: ";
        string name;
        getline(cin, name);
        cout << "Your age: " << age << endl
             << "Your full name: " << name << endl;
    }
    

    Q4: What is the problem with this code?

  7. To the above program, add the following code before the call to getline(), rerun the code and answer the questions:
    cin >> ws; // clear whitespace from buffer
    

    Q5: Why did this line fix the problem?

    Q6: How does this fix compare to using cin.ignore()?

  8. Submit your formatting.txt file to Blackboard as part of assignment 10.

Check Yourself

As time permits, be prepared to answer these questions.

  1. How do you set the width of a field when outputting numbers using and output stream like cout? (10.4.1)
  2. After you set the width of a numeric field, how long does the setting last? (10.4.1)
  3. If the number you display needs more digits than the specified width, does the entire number get displayed? (10.4.1)
  4. By default, is numerical output left or right justified? (10.4.1)
  5. What is meant by the term manipulator? (10.4.2)
  6. How do you set the width of a field when outputting numbers using a manipulator? (10.4.2)
  7. What is the "magic formula" for decimal formatting using manipulators? (10.4.2)

10.4.4: Summary

  • C++ has many formatting functions to set the layout of the output
  • We have used formatting functions before with the magic formula for decimal formatting:
    cout.setf(ios::fixed);     // fixed notation, not scientific
    cout.setf(ios::showpoint); // show decimal point
    cout.precision(2);         // show 2 decimal places
    
  • However, there are many more formatting functions and we explored how to use more of them
  • You can use these functions with cout and ofstream types
  • You can use manipulators to change formatting within the stream
  • Manipulators perform the same type of operations as formatting functions but are included in the output stream
  • For example:
    cout << "$" << setprecision(2) << 10.4  << endl;
    

Wrap Up

Due Next:
A9-Keeping Lists (4/29/10)
A10-Storing Information (5/6/10)
  • When class is over, please shut down your computer
  • You may complete unfinished exercises at the end of the class or at any time before the next class.
Home | Blackboard | Day Schedule | Eve Schedule
Syllabus | Help | FAQ's | HowTo's | Links
Last Updated: February 06 2010 @16:38:36