chapter 10 file input and output objectives to use ...+.pdf · file input and output objectives •...

36
366 CHAPTER 10 File Input and Output Objectives To use ofstream for output (§10.2.2) and ifstream for input (§10.2.2). To test whether a file exists (§10.2.3). To test the end of a file (§10.2.4). To write data in desired format (§10.3). To read and write data using the getline , get , and put functions (§10.4). To use an fstream object to read and write data (§10.5). To open a file with specified modes (§10.5). To use the eof() , fail() , bad() , and good() functions to test stream states (§10.6). To understand the difference between text I/O and binary I/O (§10.7). To write binary data using the write function (§10.7.1). To read binary data using the read function (§10.7.2). To cast primitive type values and objects to character arrays using the reinterpret_cast operator (§10.7). To read/write objects (§10.7.4). To use the seekp and seekg functions to move the file pointers for random file access (§10.8). To open a file for both input and output to update files (§10.9).

Upload: lamcong

Post on 13-Mar-2018

263 views

Category:

Documents


3 download

TRANSCRIPT

366

CHAPTER 10 File Input and Output Objectives

• To use ofstream for output (§10.2.2) and ifstream for input (§10.2.2).

• To test whether a file exists (§10.2.3). • To test the end of a file (§10.2.4). • To write data in desired format (§10.3). • To read and write data using the getline, get, and put

functions (§10.4). • To use an fstream object to read and write data

(§10.5). • To open a file with specified modes (§10.5). • To use the eof(), fail(), bad(), and good() functions

to test stream states (§10.6). • To understand the difference between text I/O and

binary I/O (§10.7). • To write binary data using the write function

(§10.7.1). • To read binary data using the read function (§10.7.2). • To cast primitive type values and objects to character

arrays using the reinterpret_cast operator (§10.7). • To read/write objects (§10.7.4). • To use the seekp and seekg functions to move the file

pointers for random file access (§10.8). • To open a file for both input and output to update

files (§10.9).

367

10.1 Introduction Data stored in variables, arrays, and objects are temporary; they are lost when the program terminates. To permanently store the data created in a program, you need to save them in a file on a disk or a CD. The file can be transported and can be read later by other programs. C++ defines the ifstream, ofstream, and fstream for processing and manipulating files. These classes are all defined in the <fstream> header file. The ifstream class is for reading data from a file, the ofstream class is for writing data to a file, and the fstream class can be used for updating data in a file. 10.2 Simple Text I/O This section demonstrates how to perform simple input and output. Let us first consider output. 10.2.1 Writing Data to a File

The ofstream class can be used to write primitive data type values, arrays, strings, and objects to a text file. Listing 10.1 demonstrates how to write data. The program creates an instance of ofstream and writes two lines to the file “scores.txt”. Each line consists of first name (a string), middle name initial (a character), last name (a string), and score (an integer).

Listing 10.1 WriteData.cpp (Text File Output)

***PD: Please add line numbers in the following code*** <Side remark line 2: include fstream header> <Side remark line 7: declare object> <Side remark line 10: open file> <Side remark line 13: output to file> <Side remark line 18: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

ofstream output;

// Create a file

output.open("scores.txt");

// Write two lines

output << "John" << " " << "T" << " " << "Smith"

<< " " << 90 << endl;

output << "Eric" << " " << "K" << " " << "Jones"

<< " " << 85;

output.close();

cout << "Done" << endl;

return 0;

}

John T Smith 90

Eric K Jones 85

scores.txt

368

<Side remark: including <fstream> header> Since the ofstream class is defined in the fstream header file, line 2 includes this header file. <Side remark: create object> Line 7 creates an object, output, from the ofstream class using its no-arg constructor. <Side remark: open file> Line 10 opens a file named scores.txt for the output object. If the file does not exist, a new file is created. If the file already exists, its contents are destroyed without warning. <Side remark: cout> You can write data to the output object using the stream insertion operator (<<) in the same way that you send data to the cout object. The cout object is a predefined object for output to the console. Lines 13-14 writes strings and numeric values to output, as shown in Figure 10.1.

output << "John" << " " << "T" << "Smith" << " " << 90 << endl;

output << "Eric" << " " << "K" << "Jones" << " " << 85 << endl;

John T Smith 90

Eric K Jones 85

scores.txt

file

Figure 10.1

The output stream sends data to the file.

<Side remark: close file> The close() function (line 16) must be used to close the stream for the object. If this function is not invoked, the data may not be saved properly in the file.

CAUTION: <Side remark: file exists?>

If a file already exists, the contents of the file will be destroyed without warning.

NOTE:

<Side remark: absolute filename> Every file is placed in a directory in the file system. An absolute file name contains a file name with its complete path and drive letter. For example, c:\example\scores.txt is the absolute file name for the file scroes.txt on

369

the Windows operating system. Here c:\example is referred to as the directory path for the file. Absolute file names are machine-dependent. On Unix, the absolute file name may be /home/liang/example/scores.txt, where /home/liang/example is the directory path for the file scores.txt. CAUTION

<Side Remark: \ in file names> The directory separator for Windows is a backslash (\). The backslash is a special character in C++ and should be written as \\ in a string literal (see Table 2.4). For example, output.open("c:\\example\\scores.txt");

***END CAUTION

NOTE: <Side remark: relative filename>

Absolute file name is platform dependent. It is better to use relative file name without drive letters. The directory of the relative filename can be specified in the IDE if you use an IDE to run C++. For example, the default directory for data files is \windows\Debug_Build for Borland C++Builder.

***END NOTE

10.2.2 Reading Data from a File

The ifstream class can be used to read data from a text file. Listing 10.2 demonstrates how to read data. The program creates an instance of ifstream and reads data from the file scores.txt. scores.txt was created in the preceding example.

Listing 10.2 ReadData.cpp (Text File Input)

***PD: Please add line numbers in the following code*** <Side remark line 2: include fstream header> <Side remark line 7: declare object> <Side remark line 10: open file> <Side remark line 17: input from file> <Side remark line 21: input from file> <Side remark line 25: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

ifstream input;

370

// Open a file

input.open("scores.txt");

// Read data

char firstName[80];

char mi;

char lastName[80];

int score;

input >> firstName >> mi >> lastName >> score;

cout << firstName << " " << mi << " " << lastName << " "

<< score << endl;

input >> firstName >> mi >> lastName >> score;

cout << firstName << " " << mi << " " << lastName << " "

<< score << endl;

input.close();

cout << "Done" << endl;

return 0;

}

<Output>

John T Smith 90 Eric K Jones 85 Done <End Output>

<Side remark: including <fstream> header> Since the ifstream class is defined in the fstream header file, line 2 includes this header file. <Side remark: create object> Line 7 creates an object, input, from the ifstream class using its no-arg constructor. <Side remark: open file> Line 10 opens a file named scores.txt for the input object. <Side remark: cin> You can read data from the input object using the stream extraction operator (>>) in the same way that you read data from the cin object. The cin object is a predefined object for input from the console. Lines 17 and 21 read strings and numeric values from the input file, as shown in Figure 10.2.

371

input >> firstName >> mi >> lastName >> score;

input >> firstName >> mi >> lastName >> score;

John T Smith 90

Eric K Jones 85

scores.txt

file

Figure 10.2

The input stream reads data from the file.

<Side remark: close file> The close() function (line 25) must be used to close the stream for the object. It is not necessary to close the input file, but it is a good practice to do so to release the resources occupied by the file.

NOTE: <Side remark: input stream> <Side remark: output stream>

An input object reads a stream of data. For convenience, an input object is also called an input stream. For the same reason, an output object is called an output stream.

10.2.3 Testing File Existence

<Side remark: file not exist?> If the file does not exist, your program will run and produce incorrect results. Can your program check whether a file exists? Yes. You can invoke the fail() function immediately after invoking the open function. If fail() returns true, it would indicate that the file does not exist. ***PD: Please add line numbers in the following code*** <Side remark line 4: check file operation>

// Open a file

input.open("scoresd.txt");

if (input.fail())

{

cout << "File does not exist" << endl;

cout << "Exit program" << endl;

return 0;

}

10.2.4 Testing End of File

<Side remark: eof function>

372

Listing 10.2 reads two lines from the data file. If you don’t know how many lines are in the file and want to read them all, how do you know the end of file? You can invoke the eof() function on the input object to detect it. Listing 10.3 revises Listing 10.2 to read all lines from the file scores.txt.

Listing 10.3 ReadAllData.cpp (Text File Input)

***PD: Please add line numbers in the following code*** <Side remark line 2: include fstream header> <Side remark line 7: declare object> <Side remark line 10: open file> <Side remark line 12: file exist?> <Side remark line 25: end of file?> <Side remark line 27: input from file> <Side remark line 28: display data> <Side remark line 32: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

ifstream input;

// Open a file

input.open("scores.txt");

if (input.fail())

{

cout << "File does not exist" << endl;

cout << "Exit program" << endl;

return 0;

}

// Read data

char firstName[80];

char mi;

char lastName[80];

int score;

while (!input.eof()) // Continue if not end of file

{

input >> firstName >> mi >> lastName >> score;

cout << firstName << " " << mi << " " << lastName

<< " " << score << endl;

}

input.close();

cout << "Done" << endl;

return 0;

}

<Output>

373

John T Smith 90 Eric K Jones 85 Done <End Output>

The program reads data in a loop (lines 25-30). Each iteration of the loop reads one student record that consists of first name, middle name initial, last name, and score. The loop terminates when the input reaches the end of file. <Side remark: end of file?> How does the program know the end of file? When there is nothing more to read, eof() returns true. How does the program know there is nothing to read? This information is obtained from the operating system. When the program reads the last item 85 in the last line of the file, it attempts to read beyond 5, as shown in following diagram.

J o h n T S m i t h 9 0 \n

E r i c K J o n e s 8 5

Reads beyond end of file

The operating system notifies the program that the end of file is reached. When you invokes eof() now, it returns true.

CAUTION <Side remark: Ctrl+Z or Ctrl+D>

If the data is read from the console, you can use Ctrl+Z on Windows or Ctrl+D on Unix to signify the end of file.

CAUTION

<Side remark: know data format> To read data correctly, you need to know exactly how data is stored. For example, the program in Listing 10.3 would not work if the score is a double value with a decimal point.

10.3 Formatting Output

You have used the stream manipulators to format output to the console in §3.11, “Formatting Output.” You can use the same stream manipulator to format output to a file. Listing 10.4 gives an example that formats the student records to the file named formattedscores.txt.

Listing 10.4 WriteFormatData.cpp (Formatted Text Output)

***PD: Please add line numbers in the following code*** <Side remark line 2: include iomanip header> <Side remark line 3: include fstream header>

374

<Side remark line 8: declare object> <Side remark line 14: output with format> <Side remark line 16: output with format> <Side remark line 19: close file>

#include <iostream>

#include <iomanip>

#include <fstream>

using namespace std;

int main()

{

ofstream output;

// Create a file

output.open("formattedscores.txt");

// Write two lines

// Write two lines

output << setw(6) << "John" << setw(2) << "T" << setw(6) << "Smith"

<< " " << setw(4) << 90 << endl;

output << setw(6) << "Eric" << setw(2) << "K" << setw(6) << "Jones"

<< " " << setw(4) << 85;

output.close();

cout << "Done" << endl;

return 0;

}

The contents in the file are shown below:

J o h n T S m i t h 9 0 \n

E r i c K J o n e s 8 5

10.4 Member Functions: getline, get and put

There is a problem to read data using the stream extraction operator (>>). Data are delimited by whitespace. What happens if the whitepace characters are part of a string? In §7.9.3, “Reading Strings,” you learned how to use the getline function to read a string with whitespace. You can use the same function to read strings from a file. Recall that the syntax for the getline function is

<side remark: getline>

getline(char array[], int size, char delimitChar)

The function stops reading characters when the delimiter

character or end-of-file mark is encountered, or when the size - 1 number of characters are read. The last character in the array is reserved for the null terminator ('\0'). If

375

the delimiter is encountered, it is read, but not stored in the array. The third argument delimitCahr has a default value ('\n'). Suppose a file named state.txt is created that contains the state names delimited by the pound (#) symbol. The following diagram shows the contents in the file:

N e w Y o r k # N e w M e x i c o

# T e x a s # I n d i a n a

Listing 10.5 gives program that reads the cities from the file.

Listing 10.5 ReadState.cpp (Read States)

***PD: Please add line numbers in the following code*** <Side remark line 2: include fstream header> <Side remark line 7: input object> <Side remark line 10: open file> <Side remark line 12: file exist?> <Side remark line 20: string city> <Side remark line 25: end of file?> <Side remark line 24: input from file> <Side remark line 25: display data> <Side remark line 28: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

ifstream input;

// Open a file

input.open("state.txt");

if (input.fail())

{

cout << "File does not exist" << endl;

cout << "Exit program" << endl;

return 0;

}

// Read data

char city[40];

while (!input.eof()) // Continue if not end of file

{

input.getline(city, 40, '#');

cout << city << endl;

}

376

input.close();

cout << "Done" << endl;

return 0;

}

<Output> New York New Mexico Texas Indiana Done <End Output> Invoking getline(state, 40, '#') (line 24) reads characters to the array state until it encounters the # character or the <EOF> character, or when the 39 characters are read. Two other useful functions are get and put. You can invoke the get function on an input object to read a character and invoke the put function on an output object to write a character. The get function has three versions:

char get() // Returns a char

istream * get(char &ch)

char get(char array[], int size, char delimitChar)

The first version returns a character from the input. The second version passes a character reference argument, read a character from the input and store in ch. This function also returns the reference to the input object being used. The third version is almost identical to the getline function, except that it does not insert the null terminator character ('\0') in the character array. The header for the put function is

void put(char ch) It writes the specified character to the output object. Listing 10.6 gives an example of using these two functions. The program prompts the user to enter a file and copies it to a new file.

Listing 10.6 CopyFile.cpp (Copy File)

***PD: Please add line numbers in the following code*** <Side remark line 2: include fstream header> <Side remark line 12: enter input filename> <Side remark line 17: enter output filename> <Side remark line 19: input object> <Side remark line 20: output object> <Side remark line 23: open input file> <Side remark line 24: open output file> <Side remark line 26: file exist?> <Side remark line 33: end of file?> <Side remark line 35: get function> <Side remark line 36: put function> <Side remark line 38: close file>

377

<Side remark line 39: close file> #include <iostream>

#include <fstream>

using namespace std;

int main()

{

const int FILENAME_SIZE = 40;

// Enter a source file

cout << "Enter a source file name: ";

char inputFilename[FILENAME_SIZE];

cin >> inputFilename;

// Enter a target file

cout << "Enter a target file name: ";

char outputFilename[FILENAME_SIZE];

cin >> outputFilename;

ifstream input;

ofstream output;

// Open a file

input.open(inputFilename);

output.open(outputFilename);

if (input.fail())

{

cout << inputFilename << " does not exist" << endl;

cout << "Exit program" << endl;

return 0;

}

while (!input.eof()) // Continue if not end of file

{

output.put(input.get());

}

input.close();

output.close();

cout << "\nCopy Done" << endl;

return 0;

}

<Output>

Enter a source file name: c:\example\CopyFile.cpp Enter a target file name: c:\example\temp.txt Copy Done <End Output> The program prompts the user to enter a source filename in line 12 and enter a target filename in line 17. A file input object is created in

378

line 19 and a file output object is created in line 20. Files are associated with input object and output objects in lines 23-24. Lines 26-31 checks whether the input file exists. Lines 33-36 reads characters repeatedly one at a time using the get function and writes the character to the output file using the put function. There is a problem in this program. If you check the size of the two files, you will find that the new file is one byte larger than the original file. The new file contains an extra character at the end. The reason is that when the last character is read from the input file using input.get(), input.eof() is still false. Afterwards, the program attempts to read another character, input.eof() now becomes true. However, the extraneous character is already sent to the output file. To fix this problem, replace the while loop (lines 33-36) by the following code:

char ch = input.get();

while (!input.eof()) // Continue if not end of file

{

output.put(ch);

ch = input.get();

} The revised code reads a character and checks eof(). If eof() is true, the character is not put to output; otherwise, the character is outputted. This process continues until eof() returns true. 10.5 fstream and File Open Modes

In the preceding sections, you used the ofstream to write data and the ifstream to read data. Alternatively, you can also use the fstream class to create an input stream or output stream. It is convenient to use fstream if your program needs to use the same stream object for both input and output. To open an fstream file, you have to specify a file mode to tell C++ how the file will be used. The file modes are listed in Table 10.1. Table 10.1 File Modes Mode Description

ios::in Opens a file for input.

ios::out Opens a file for output.

ios::app Appends all output to the end of the file.

ios::ate Opens a file for output. If the file already exists, move to the

end of the file. Data can be written anywhere in the file.

ios::truct Discards the file’s contents if the file already exists.

(This is the default action for ios:out).

ios::binary Opens a file for binary input and output.

NOTE

379

Some of the file modes can also be used with an ifstream and ofstream object to open a file. For example, you may use the ios:app mode to open a file with an ofstream object so you can append data to the file. However, for consistency and simplicity, it is better to use the file modes with the fstream objects.

NOTE

<Side remark: combining modes> Several modes can be combined together using the | operator. For example, to open an output file named city.txt for appending data, you can use the following statement: stream.open("city.txt", ios::out | ios::app);

***END NOTE

Listing 10.7 gives a program that creates a new file named city.txt (line 10) and writes data to the file. The program then closes the file and reopens it to append new data (line 18), rather than overrides it. Finally, the program reads all data from the file.

Listing 10.7 AppendFile.cpp (Append Data to a File)

***PD: Please add line numbers in the following code*** <Side remark line 2: include fstream header> <Side remark line 7: fstream object> <Side remark line 10: open output file> <Side remark line 13: write data> <Side remark line 15: close stream> <Side remark line 18: open output for append> <Side remark line 21: write data> <Side remark line 23: close stream> <Side remark line 28: open for input> <Side remark line 29: end of file?> <Side remark line 31: read data> <Side remark line 35: close stream>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

fstream inout;

// Create a file

inout.open("city.txt", ios::out);

// Write cities

inout << "Dallas" << " " << "Houston" << " " << "Atlanta" << " ";

380

inout.close();

// Append to the file

inout.open("city.txt", ios::out | ios::app);

// Write cities

inout << "Savannah" << " " << "Austin" << " " << "Chicago";

inout.close();

char city[20];

// Open the file

inout.open("city.txt", ios::in);

while (!inout.eof()) // Continue if not end of file

{

inout >> city;

cout << city << " ";

}

inout.close();

return 0;

}

<Output>

Dallas Houston Atlanta Savannah Austin Chicago <End Output> The program creates a fstream object in line 7, and opens the file city.txt for output using the file mode ios::out in line 10. After writing data in line 13, the program closes the stream in line 15. The program uses the same stream object to reopen the text file with the combined modes ios::out | ios::app in line 18. The program then appends new data to the end of the file in line 21 and closes the stream in line 23. Finally the program uses the same stream object to reopen the text file with the input mode ios::in in line 28. The program then reads all data from the file (lines 29-33). 10.6 Testing Stream States

You have used the eof() function and fail() function to test the states of a stream. C++ provides several more functions in a stream for testing stream states. Each stream object contains a set of bits that act as flags. These bit values (0 or 1) indicate the state of a stream. Table 10.2 lists these bits. Table 10.2 Stream State Bit Values

381

Bit Description

ios::eofbit Set when the end of an input stream is reached.

ios::failbit Set when an operation failed.

ios::hardfail Set when an unrecoverable error occurred.

ios::badbit Set when an invalid operation has been attempted.

ios::goodbit Set when an operation is successful.

The states of the I/O operations are represented in these bits. It is not convenient to directly access these bits. C++ provides member functions in the IO stream object to test these bits. These functions are listed in Table 10.3. Table 10.3 Stream State Functions Function Description

eof() Returns true if the eofbit flag is set.

fail() Returns true if the failbit or hardfail flags is set.

bad() Returns true if the badbit is set.

good() Returns true if the goodbit is set.

clear() Clears all flags.

Listing 10.8 gives an example to detect the stream states.

Listing 10.8 StreamState.cpp (Show Stream State)

***PD: Please add line numbers in the following code*** <Side remark line 2: include fstream header> <Side remark line 5: function prototype> <Side remark line 9: input object> <Side remark line 12: open input file> <Side remark line 15: show state> <Side remark line 16: close file> <Side remark line 19: open output file> <Side remark line 23: read city> <Side remark line 25: show state> <Side remark line 27: close file> <Side remark line 32: show state> <Side remark line 37: show state>

#include <iostream>

#include <fstream>

using namespace std;

void showState(fstream &);

int main()

{

fstream inout;

// Create an output file

382

inout.open("temp.txt", ios::out);

inout << "Dallas";

cout << "Normal operation (no errors)" << endl;

showState(inout);

inout.close();

// Create an output file

inout.open("temp.txt", ios::in);

// Read a string

char city[6];

inout >> city;

cout << "End of file (no errors)" << endl;

showState(inout);

inout.close();

// Attempt to read after file closed

inout >> city;

cout << "Bad operation (errors)" << endl;

showState(inout);

return 0;

}

void showState(fstream & stream)

{

cout << "Stream status: " << endl;

cout << " eof(): " << stream.eof() << endl;

cout << " fail(): " << stream.fail() << endl;

cout << " bad(): " << stream.bad() << endl;

cout << " good(): " << stream.good() << endl;

}

<Output> Normal operation (no errors) Stream status: eof(): 0 fail(): 0 bad(): 0 good(): 1 End of file (no errors) Stream status: eof(): 1 fail(): 0 bad(): 0 good(): 0 Bad operation (errors) Stream status: eof(): 1 fail(): 1 bad(): 0 good(): 0

383

<End Output> The program creates a fstream object using its no-arg constructor in line 9, opens temp.txt for output in line 12, and writes a string Dallas in line 13. The state of the stream is displayed in line 15. There are no errors so far. The program then closes the stream in line 16, reopens temp.txt for input in line 19, and reads a string Dallas in line 23. The state of the stream is displayed in line 25. There are no errors so far, but the end of file is reached. Finally the program closes the stream in line 27 and attempts to read data after the file is closed in line 30, which causes an error. The state of the stream is displayed in line 32. When invoking the showState function in lines 15, 25, and 32, the stream object is passed to the function by reference. 10.7 Binary I/O

<Side Remark: text file> <Side Remark: binary file> So far you have used text files. Data stored in a text file are represented in human-readable form. Data stored in a binary file are represented in binary form. You cannot read binary files. They are designed to be read by programs. For example, the C++ source programs are stored in text files and can be read by a text editor, but the C++ executable files are stored in binary files and are read by the operating system. The advantage of binary files is that they are more efficient to process than text files.

Although it is not technically precise and correct, you can envision a text file as consisting of a sequence of characters and a binary file as consisting of a sequence of bits. For example, the decimal integer 199 is stored as the sequence of three characters, '1', '9', '9', in a text file, and the same integer is stored as a byte-type value C7 in a binary file, because decimal 199 equals hex C7 ( 71612199 1 +×= ).

NOTE: <Side Remark: text vs. binary I/O>

Computers do not differentiate binary files and text files. All files are stored in binary format, and thus all files are essentially binary files. Text I/O is built upon binary I/O to provide a level of abstraction for character encoding and decoding.

<Side Remark: ios::binary> Binary I/O does not require conversions. If you write a numeric value to a file using binary I/O, the exact value in the memory is copied into the file. To perform binary I/O in C++, you have to open a file using the binary mode ios::binary. By default, a file is opened in text mode. You used the << operator and put function to write data to a text file and the >> operator, get, and getline functions to read data from a text

384

file. To read/write data from/to a binary file, you have to use the read and write functions on a stream. 10.7.1 The write Function

The syntax for the write function is <Side Remark: write function>

streamObject.write(char * address, int size) Listing 10.9 shows an example of using the write function.

Listing 10.9 BinaryCharOutput.cpp (Output Binary Characters)

***PD: Please add line numbers in the following code*** <Side remark line 7: fstream object> <Side remark line 8: open binary file> <Side remark line 9: character array> <Side remark line 10: write data> <Side remark line 11: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

fstream binaryio;

binaryio.open("city.dat", ios::out | ios::binary);

char s[] = "Atlanta";

binaryio.write(s, 5);

binaryio.close();

cout << "Done" << endl;

return 0;

}

Line 8 opens the binary file city.dat for output. Invoking binaryio.write(s, 5) (line 10) writes five characters from the array to the file. Often you need to write data other than characters. How can you accomplish it? C++ provides the reinterpret_cast for this purpose. You can use this operator to cast the address of a primitive type value or an object to a character array pointer for binary I/O. The syntax of this type of casting is:

reinterpret_cast<dataType>(address) where address is the starting address of the data (primitive, array, or object) and dataType is the data type you are converting to. In this case for binary I/O, it is char *. For example, see the following code in Listing 10.10.

Listing 10.10 BinaryIntOutput.cpp (Output Binary Int)

***PD: Please add line numbers in the following code*** <Side remark line 7: fstream object> <Side remark line 8: open binary file>

385

<Side remark line 9: int value> <Side remark line 10: binary output> <Side remark line 11: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

fstream binaryio;

binaryio.open("temp.dat", ios::out | ios::binary);

int value = 199;

binaryio.write(reinterpret_cast<char *>(&value), sizeof(value));

binaryio.close();

cout << "Done" << endl;

return 0;

}

Line 10 writes the content in variable value to the file. reinterpret_cast<char *>(&value) (line 10) cast the address of the int value to the type char *. sizeof(value) returns the storage size for the value variable, which is 4 since it is an int type variable.

NOTE: <Side Remark: .txt and .dat>

For consistency, this book uses the extension .txt to name text files and .dat to name binary files.

10.7.2 The read Function

The syntax for the read function is <Side Remark: write function>

streamObject.read(char * address, int size) Assume the file city.dat was created in Listing 10.9. Listing 10.11 reads the characters using the read function.

Listing 10.11 BinaryCharInput.cpp (Input Binary Characters)

***PD: Please add line numbers in the following code*** <Side remark line 7: fstream object> <Side remark line 8: open binary file> <Side remark line 9: character array> <Side remark line 10: write data> <Side remark line 11: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

fstream binaryio;

binaryio.open("city.dat", ios::in | ios::binary);

char s[10];

386

binaryio.read(s, 5);

s[5] = '\0';

cout << s;

binaryio.close();

return 0;

}

<Output> Atlan <End Output> Line 8 opens the binary file city.dat for input. Invoking binaryio.read(s, 5) (line 10) reads five characters from the file to the array. Similarly, to read data other than characters, you need to use the reinterpret_cast operator. Assume that the file temp.dat was created in Listing 10.10. Listing 10.12 reads the integer using the read function.

Listing 10.12 BinaryIntInput.cpp (Input Binary Integer)

***PD: Please add line numbers in the following code*** <Side remark line 7: fstream object> <Side remark line 8: open binary file> <Side remark line 9: int value> <Side remark line 10: binary output> <Side remark line 11: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

fstream binaryio;

binaryio.open("temp.dat", ios::in | ios::binary);

int value;

binaryio.read(reinterpret_cast<char *>(&value), sizeof(value));

cout << value;

binaryio.close();

return 0;

}

<Output> 199 <End Output> Line 4 reads an int from the file to the variable value. 10.7.3 Example: Binary Array I/O

This section gives an example in Listing 10.13 to write an array of double values to a binary file, and read it back from the file.

Listing 10.13 BinaryArrayIO.cpp (Array Input/Output)

***PD: Please add line numbers in the following code*** <Side remark line 7: constant array size> <Side remark line 9: fstream object>

387

<Side remark line 12: open binary file> <Side remark line 13: create array> <Side remark line 14: write to file> <Side remark line 15: close file> <Side remark line 18: open input file> <Side remark line 19: create array> <Side remark line 20: read from file> <Side remark line 21: close file>

#include <iostream>

#include <fstream>

using namespace std;

int main()

{

const int SIZE = 5; // Array size

fstream binaryio; // Create stream object

// Write array to the file

binaryio.open("array.dat", ios::out | ios::binary);

double array[SIZE] = {3.4, 1.3, 2.5, 5.66, 6.9};

binaryio.write(reinterpret_cast<char *>(&array), sizeof(array));

binaryio.close();

// Read array from the file

binaryio.open("array.dat", ios::in | ios::binary);

double result[SIZE];

binaryio.read(reinterpret_cast<char *>(&result), sizeof(result));

binaryio.close();

// Display array

for (int i = 0; i < SIZE; i++)

cout << result[i] << " ";

return 0;

}

<Output>

3.4 1.3 2.5 5.66 6.9 <End Output> The program creates a stream object in line 9, opens the file array.dat for binary output in line 12, writes an array of double values to the file in line 14, and closes the file in line 15. The program then opens the file array.dat for binary input in line 18, reads an array of double values from the file in line 20, and closes the file in line 21. Finally the program displays the contents in the array result (lines 24-25). 10.7.4 Example: Binary Object I/O

388

This section gives an example in Listing 10.14 to write objects to a binary file, and read the objects back from the file. Listing 10.1 writes student records into a text file. A student record consists of first name, middle name initial, last name, and score. These fields are written to the file separately. A better way to process it is to declare a class to model records. Each record is an object of the Student class.

NOTE: <Side remark: struct obsolete>

You may define records using the struct keyword. However, it is better to use classes. In C++, struct is obsolete and replaced by the class.

Let the class be named Student with the data fields firstName, mi, lastName, and score, their supporting accessors and mutators, and two constructors. The class UML diagram is shown in Figure 10.3.

<PD: UML Class Diagram>

Student

-firstName: string -mi: char -lastName: string -score: double +Student() +Student(firstName: string, mi: char,

lastName: string, score: int) +getFirstName(): string +getMi(): char +getLastName(): string +getScore(): int +setFirstName(s: string): void +setMi(ch: char): void +setLastName(s: string): void +setScore(score: int): void

The first name of this student. The middle name initial of this student. The last name of this student. The score of this student. Constructs a default Student object. Constructs a student with specified first name, mi, last

name, and score Returns the first name of this student. Returns the mi of this student. Returns the last name of this student. Returns the score of this student. Sets a new first name of this student. Sets a new mi of this student. Sets a new last name of this student. Sets a new score for this student.

Figure 10.3 The Student class describes student information.

Listing 10.14 defines the interface of the Student class in the header file and Listing 10.15 implements the class.

Listing 10.14 Student.h (Student Class Header)

***PD: Please add line numbers in the following code*** <Side remark line 6: public members> <Side remark line 7: no-arg constructor> <Side remark line 8: constructor> <Side remark line 9: mutator function> <Side remark line 13: accessor function> <Side remark line 18: private data fields>

389

#include <string>

using namespace std;

class Student

{

public:

Student();

Student(string firstName, char mi, string lastName, int score);

void setFirstName(string s);

void setMi(char mi);

void setLastName(string s);

void setScore(int score);

string getFirstName();

char getMi();

string getLastName();

int getScore();

private:

string firstName;

char mi;

string lastName;

int score;

};

Listing 10.15 Student.cpp (Student Class Implementation)

***PD: Please add line numbers in the following code*** <Side remark line 1: include header file> <Side remark line 4: no-arg constructor> <Side remark line 9: constructor> <Side remark line 18: setFirstName> <Side remark line 44: getFirstName>

#include "Student.h"

// No-arg constructor

Student::Student()

{

}

// Construct a Student object with specified data

Student::Student

(string firstName, char mi, string lastName, int score)

{

setFirstName(firstName);

setMi(mi);

setLastName(lastName);

setScore(score);

}

void Student::setFirstName(string s)

{

firstName = s;

}

void Student::setMi(char mi)

390

{

this->mi = mi;

}

void Student::setLastName(string s)

{

lastName = s;

}

void Student::setScore(int score)

{

this->score = score;

}

string Student::getFirstName()

{

return firstName;

}

char Student::getMi()

{

return mi;

}

string Student::getLastName()

{

return lastName;

}

int Student::getScore()

{

return score;

} Listing 10.16 gives a program that creates two Student objects, writes them to a file named object.dat, and reads them back from the file.

Listing 10.16 BinaryObjectIO.cpp (Object Input/Outout)

***PD: Please add line numbers in the following code*** <Side remark line 3: include Student class> <Side remark line 6: display Student data> <Side remark line 9: fstream object> <Side remark line 17: open output file> <Side remark line 19: create student1> <Side remark line 20: create student2> <Side remark line 22: write student1> <Side remark line 24: write student2> <Side remark line 27: close file> <Side remark line 30: open input file> <Side remark line 32: create student> <Side remark line 34: read from file> <Side remark line 37: display student>

#include <iostream>

391

#include <fstream>

#include "Student.h"

using namespace std;

void displayStudent(Student student)

{

cout << student.getFirstName() << " ";

cout << student.getMi() << " ";

cout << student.getLastName() << " ";

cout << student.getScore() << endl;

}

int main()

{

fstream binaryio; // Create stream object

binaryio.open("object.dat", ios::out | ios::binary);

Student student1("John", 'T', "Smith", 90);

Student student2("Eric", 'K', "Jones", 85);

binaryio.write(reinterpret_cast<char *>

(&student1), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student2), sizeof(Student));

binaryio.close();

// Read student back from the file

binaryio.open("object.dat", ios::in | ios::binary);

Student studentNew;

binaryio.read(reinterpret_cast<char *>

(& studentNew), sizeof(Student));

displayStudent(studentNew);

binaryio.read(reinterpret_cast<char *>

(& studentNew), sizeof(Student));

displayStudent(studentNew);

binaryio.close();

return 0;

}

<Output> John T Smith 90 Eric K Jones 85 <End Output> The program creates a stream object in line 16, opens the file object.dat for binary output in line 17, creates two Student objects in lines 19-20, writes them to the file in lines 22-25, and closes the file in line 27.

392

The statement to write an object to the file is

binaryio.write(reinterpret_cast<char *>

(&student1), sizeof(Student)); The address of object student1 is cast into the type char *. The size of an object is determined by the data fields in the object. The program opens the file object.dat for binary input in line 31, creates a Student object using its no-arg construction in line 33, reads a Student object from the file in lines 35-36, and displays the object’s data in line 38. The program continues to read another object (lines 40-41) and displays its data in line 43. Finally the program closes the file in line 45. 10.8 Random Access File

<Side Remark: file pointer> A file consists of a sequence of bytes. There is a special marker called file pointer that is positioned at one of these bytes. A read or write operation takes place at the location of the file pointer. When a file is opened, the file pointer is set at the beginning of the file. When you read or write data to the file, the file pointer moves forward to the next data item. For example, if you read a character using the get() function, C++ reads one byte from the file pointer and now the file pointer is 1 byte ahead of the previous location, as shown in Figure 10.4.

byte file byte … byte byte byte byte byte … byte byte byte byte byte

file pointer

byte file byte … byte byte byte byte byte … byte byte byte byte byte

file pointer

(a) Before get()

(b) After get()

Figure 10.4

After reading a character, the file pointer is moved one byte ahead.

All the programs you have developed so far read/write data sequentially, i.e., the file pointer always moves forward. If a file is open for input, it starts to read data from beginning to the end. If a file is open for output, it writes data one item after the other from the beginning or from the end (with the append mode ios::app). <Side Remark: seekp function> <Side Remark: seekg function> The problem with sequential access is that in order to read a byte in a specific location, all the bytes that precede it must be read. This is not efficient. C++ enables the file pointer to jump backward or forward freely using the seekp and seekg member functions on a stream object. This capability is known as random file access.

393

The seekp (“seek put”) function is for the output stream and the seekg (“seek get”) function is for the input stream. Each function has two versions with one argument or two arguments. With one argument, the argument is the absolute location. For example,

input.seekg(0);

output.seekp(0); moves the file pointer to the beginning of the file. With two arguments, the first argument is a long integer that indicates an offset and the second argument, known as the seek base, specifies where to calculate the offset from. Table 10.4 lists the three possible seek base arguments: Table 10.4 Seek Base Seek Base Description

ios::beg Calculates the offset from the beginning of the file.

ios::end Calculates the offset from the end of the file.

ios::cur Calculates the offset from the current file pointer.

Table 10.5 gives some examples of using the seekp and seekg functions. Table 10.5 seekp and seekg Examples Statement Description

seekg(100L, ios::beg); Moves the file pointer to the 100th byte from

the beginning of the file.

seekg(-100L, ios::end); Moves the file pointer to the 100th byte backward

from the end of the file.

seekp(42L, ios::cur); Moves the file pointer to the 42nd byte forward

from the current file pointer.

seekp(-42L, ios::cur); Moves the file pointer to the 42nd byte backward

from the current file pointer.

seekp(100L); Moves the file pointer to the 100th byte in the file.

<Side Remark: tellp function> <Side Remark: tellg function> You can also use the tellp and tellg functions to return the position of the file pointer in the file. Listing 10.17 demonstrates how to access file randomly. The program first stores 10 student objects into the file, and then retrieves the 3rd student from the file.

Listing 10.17 RandomAccess.cpp (Random Access File)

***PD: Please add line numbers in the following code*** <Side remark line 17: open output file> <Side remark line 19: create students> <Side remark line 30: output students> <Side remark line 51: close file>

394

<Side remark line 54: open input file> <Side remark line 56: create student> <Side remark line 58: move to 3rd student> <Side remark line 60: read student> <Side remark line 63: display student>

#include <iostream>

#include <fstream>

#include "Student.h"

using namespace std;

void displayStudent(Student student)

{

cout << student.getFirstName() << " ";

cout << student.getMi() << " ";

cout << student.getLastName() << " ";

cout << student.getScore() << endl;

}

int main()

{

fstream binaryio; // Create stream object

binaryio.open("object1.dat", ios::out | ios::binary);

Student student1("Student1", 'T', "Smith", 90);

Student student2("Student2", 'T', "Smith", 90);

Student student3("Student3", 'T', "Smith", 90);

Student student4("Student4", 'T', "Smith", 90);

Student student5("Student5", 'T', "Smith", 90);

Student student6("Student6", 'T', "Smith", 90);

Student student7("Student7", 'T', "Smith", 90);

Student student8("Student8", 'T', "Smith", 90);

Student student9("Student9", 'T', "Smith", 90);

Student student10("Student10", 'T', "Smith", 90);

binaryio.write(reinterpret_cast<char *>

(&student1), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student2), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student3), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student4), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student5), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student6), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student7), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student8), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student9), sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student10), sizeof(Student));

395

binaryio.close();

// Read student back from the file

binaryio.open("object1.dat", ios::in | ios::binary);

Student studentNew;

binaryio.seekg(2 * sizeof(Student));

cout << "Current position is " << binaryio.tellg() << endl;

binaryio.read(reinterpret_cast<char *>

(& studentNew), sizeof(Student));

displayStudent(studentNew);

cout << "Current position is " << binaryio.tellg() << endl;

binaryio.close();

return 0;

}

<Output>

Current position is 136 Student3 T Smith 90 Current position is 204 <End Output> The program creates a stream object in line 16, opens the file object1.dat for binary output in line 17, creates ten Student objects in lines 19-28, writes them to the file in lines 30-49, and closes the file in line 51. The program opens the file object1.dat for binary input in line 54, creates a Student object using its no-arg construction in line 56, and moves the file pointer to the address of the third student in the file in line 58. The current position is now at 136. (Note that the size of each Student object is 68). After the 3rd object is read, the file pointer is moved to the 4th object. So, the current position becomes 204. 10.9 Updating Files

Often you need to update the contents of the file. You can open a file for both input and output. For example,

binaryio.open("object1.dat", ios::in | ios::out | ios::binary);

This statement opens the binary file object1.dat for both input and output. Listing 10.18 demonstrates how to update a file. Suppose file object1.dat has already been created with ten Student objects. The program first reads the 3rd student from the file, changes the last

396

name, writes the revised object back to the file, and reads the new object back from the file.

Listing 10.18 UpdateFile.cpp (Update File)

***PD: Please add line numbers in the following code*** <Side remark line 2: include header file> <Side remark line 19: open input/output> <Side remark line 21: student1> <Side remark line 23: read student1> <Side remark line 25: display student1> <Side remark line 29: update student1> <Side remark line 32: student2> <Side remark line 34: read student2> <Side remark line 36: display student2>

#include <iostream>

#include <fstream>

#include "Student.h"

using namespace std;

void displayStudent(Student student)

{

cout << student.getFirstName() << " ";

cout << student.getMi() << " ";

cout << student.getLastName() << " ";

cout << student.getScore() << endl;

}

int main()

{

fstream binaryio; // Create stream object

// Open file for input and output

binaryio.open("object1.dat", ios::in | ios::out | ios::binary);

Student student1;

binaryio.seekg(2 * sizeof(Student));

binaryio.read(reinterpret_cast<char *>

(&student1), sizeof(Student));

displayStudent(student1);

student1.setLastName("Peterson");

binaryio.seekp(2 * sizeof(Student));

binaryio.write(reinterpret_cast<char *>

(&student1), sizeof(Student));

Student student2;

binaryio.seekg(2 * sizeof(Student));

binaryio.read(reinterpret_cast<char *>

(&student2), sizeof(Student));

displayStudent(student2);

binaryio.close();

return 0;

397

}

<Output>

Student3 T Smith 90 Student3 T Peterson 90 <End Output> The program creates a stream object in line 16, opens the file object1.dat for binary input and output in line 19. The program first reads the third object from the file (lines 23-24), displays it (line 25), and changes its last name (line 27) and writes the revised object back to the file (lines 29-30). The program then reads the third object back from the file (lines 34-35) and displays it (line 36). You will see the last name of this object has been changed in the sample output. Key Terms

***PD: Please place terms in two columns same as in intro5e.

• absolute file name • binary I/O 577 • file open mode • file pointer 600 • fstream • ifstream • input stream • ofstream • output stream • random access file • relative file name • sequential access file • stream state • text I/O

Chapter Summary

• C++ provides the classes ofstream, ifstream, and fstream for facilitating file input and output. You can use the ofstream class to write data to a file, use ifstream read data from a file, and use the fstream class to read and write data.

• You can use the open function to open a file, the close

function to close a file, the fail function to test if

398

a file exists, the eof function to test whether the end of file is reached.

• The stream manipulators (e.g., setw, precision, left,

and right) can be used to format output.

• You can use the getline function to read a line from a file, and the get function to read a character from a file, and the put function to write a character to a file.

• The file open modes (iso::in, iso::out, iso::app,

iso::truct, and iso::binary) can be used to specify how a file is opened.

• File I/O can be classified into text I/O and binary

I/O. Text I/O interprets data in sequences of characters. Binary I/O interprets data as raw binary values. How text is stored in a file is dependent on the encoding scheme for the file. C++ automatically performs encoding and decoding for text I/O. To perform binary I/O, open the file using the iso::binary mode.

• For binary output, use the write function. For binary

input, use the read function. You can use the reinterpret_cast operator to cast any type of data into array of bytes for binary input and output.

• You can process a file sequentially or in a random manner. The seekp and seekg functions can be used to move the file access pointer anywhere in the file before invoking the put and get functions.

Review Questions

Section 10.2 Simple Text I/O 10.1 How do you declare and open a file for output? How do you

declare and open a file for input? 10.2 Why should you always close a file after it is processed? 10.3 How do you detect whether a file exists? 10.4 How do you detect whether the end of file is reached?

399

Section 10.3 Formatting Output 10.5 Can you use the stream manipulators to format text output? Section 10.4 Member Functions: getline, get and put 10.6 What are the differences between getline and get functions? 10.7 What function do you use to write a character? Section 10.5 fstream and File Open Modes 10.8 How do you open a file so that you can append data into the

file? 10.9 What is the file open mode ios::truct? Section 10.6 Testing Stream States 10.10 How do you determine the state of I/O operations? Section 10.7 Binary I/O 10.11 What is a text file, and what is a binary file? Can you view

a text file or a binary file using a text editor? 10.12 How do you open a file for binary I/O? 10.13 The write function can write only array of bytes. How do you

write a primitive type value or an object into a binary file?

10.14 If you write string "ABC" to an ASCII text file, what values

are stored in a file? 10.15 If you write string "100" to an ASCII text file, what values

are stored in a file? If you write a numeric byte-type value 100 using binary I/O, what values are stored in a file?

Section 10.8 Random Access File

400

10.16 What is the file pointer? 10.17 What are the differences between seekp and seekg? Programming Exercises Sections 10.2-10.6 10.1* (Creating a text file) Write a program to create a file

named Exercise10_1.txt if it does not exist. If it exists, append new data to it. Write one hundred integers created randomly into the file using text I/O. Integers are separated by a space.

10.2*

(Counting characters, words, and lines in a file) Write a program that will count the number of characters (excluding control characters '\r' and '\n'), words, and lines, in a file. Words are separated by spaces, tabs, carriage return characters, or line feed characters.

10.3* (Processing scores in a text file) Suppose that a text file

Exercise10_3.txt contains an unspecified number of scores. Write a program that reads the scores from the file and displays their total and average. Scores are separated by blanks.

10.4* (Writing/Reading data) Write a program to create a file

named Exercise10_4.txt if it does not exist. Write one hundred integers created randomly into the file using text I/O. Integers are separated by spaces in the file. Read the data back from the file and display the sorted data.

Section 10.7 Binary I/O Classes 10.5* (Creating a binary data file) Write a program to create a

file named Exercise10_5.dat if it does not exist. If it exists, append new data to it. Write one hundred integers created randomly into the file using binary I/O.

10.6* (Storing Loan objects) Write a program that creates five

Loan objects and stores them in a file named Exercise10_6.dat. The Loan class was introduced in §9.15, “Case Study: The Loan Class.”

10.7*

401

(Restoring objects from a file) Suppose a file named Exercise10_6.dat has been created from the preceding exercise. Write a program that reads the Loan objects from the file and computes the total of the loan amount. Suppose you don’t know how many Loan objects are in the file. Use the eof() to detect end of the file.

Section 10.8 Random Access File 10.8* (Updating count) Suppose you want to track how many times a

program has been executed. You may store an int to count the file. Increase the count by 1 each time this program is executed. Let the program be Exercise10_8 and store the count in Exercise10_8.dat.