Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 14

Turning an object into a “flattened” type, like a string, is often called marshalling. Marshalling is useful for saving objects to disk or sending them across a network, and is further described in Chapter 24.

The main advantage of a string stream over a standard C++ string is that, in addition to data, the object knows about its current position. There may also be performance benefits depending on the particular implementation of string streams.

File Streams

Files lend themselves very well to the stream abstraction because reading and writing files always involves a position in addition to the data. In C++, the ofstream and ifstream classes provide output and input functionality for files. They are defined in the header file <fstream>.

When dealing with the file system, it is especially important to detect and handle error cases. The file you are working with could be on a network file store that just went offline. You may be trying to write to a file that the current user does not have permissions to edit. These conditions can be detected using the standard error handling mechanisms described earlier.

The only major difference between output file streams and other output streams is that the file stream constructor takes the name of the file and the mode in which you would like to open it. The default mode is write, which starts writing to a file at the beginning, overwriting any existing data. You can also open an output file stream in append mode using the constant ios_base::app.

The following simple program opens the file test and outputs the arguments to the program.

#include <iostream> #include <fstream>

using namespace std;

int main(int argc, char** argv)

{

ofstream outFile(“test”); if (!outFile.good()) {

cerr << “Error while opening output file!” << endl; return -1;

}

outFile << “There were “ << argc << “ arguments to this program.” << endl; outFile << “They are: “ << endl;

for (int i = 0; i < argc; i++) { outFile << argv[i] << endl;

}

}

Jumping around with seek() and tell()

The seek() and tell() methods are present on all input and output streams, but they rarely make sense outside of the context of file streams.

392

Demystifying C++ I/O

The seek() methods let you move to an arbitrary position within an input or output stream. Such movement breaks the streams metaphor, so it is best to use these methods sparingly. There are several forms of seek(). The methods of seek() within an input stream are actually called seekg() (the g is for get), and the versions of seek() in an output stream are called seekp() (the p is for put). Each type of stream has two methods of seeking. You can seek to an absolute position in the stream, such as the beginning or the 17th position, or you can seek to an offset, such as the 3rd position from the current marker. Positions are measured in characters.

To seek to an absolute position in an output stream, you can use the one-parameter version of seekp(), as in the following case, which uses the constant ios_base::beg to move to the beginning of the stream. There are also constants provided for the end of the stream (ios_base::end) and the current position of the stream (ios_base::cur).

outStream.seekp(ios_base::beg);

Seeking within an input stream is exactly the same, except that the seekg() method is used:

inStream.seekg(ios_base::beg);

The two-argument versions of seek() move to a relative position in the stream. The first argument prescribes how many positions to move and the second argument provides the starting point. To move relative to the beginning of the file, the constant ios_base::beg is used. To move relative to the end of the file, ios_base::end is used. To move relative to the current position, ios_base::cur is used. For example, the following line moves to the second character from the beginning of the stream:

outStream.seekp(2, ios_base::beg);

The next example moves to the third-to-last position of an input stream.

inStream.seekg(-3, ios_base::end);

You can also query a stream’s current location using the tell() method. tell() returns a ios_base::pos_type that indicates the current position. You can use this result to remember the current marker position before doing a seek() or to query whether you are in a particular location. As with seek(), there are separate versions of tell() for input streams and output streams. Input streams use tellg(), and output streams use tellp().

The following line checks the position of an input stream to determine if it is at the beginning.

ios_base::pos_type curPos = inStream.tellg(); if (curPos == ios_base::beg) {

cout << “We’re at the beginning.” << endl;

}

Below is a sample program that brings it all together. This program writes into a file called test.out and performs the following tests:

1.

2.

3.

Outputs the string 12345 to the file

Verifies that the marker is at position 5 in the stream

Moves to position 2 in the output stream

393

Chapter 14

4.Outputs a 0 in position 2 and flushes the output stream

5.Opens an input stream on the test.out file

6.Reads the first token as an integer

7.Confirms that the value is 12045

#include <iostream> #include <fstream>

using namespace std;

int main(int argc, char** argv)

{

ofstream fout(“test.out”); if (!fout) {

cerr << “Error opening test.out for writing\n”; exit(1);

}

//1. Output the string “12345”. fout << “12345”;

//2. Verify that the marker is at the end.

ios_base::pos_type curPos

= fout.tellp();

if (curPos == 5) {

 

cout << “Test passed: Currently at position 5” << endl;

}

else {

 

 

 

cout << “Test failed: Not at position 5” << endl;

}

 

 

 

//3. Move to position 2 in the stream. fout.seekp(2, ios_base::beg);

//4. Output a 0 in position 2 and flush the stream. fout << 0;

fout.flush();

//5. Open an input stream on test.out.

ifstream fin(“test.out”); if (!fin) {

cerr << “Error opening test.out for reading\n”; exit(1);

}

//6. Read the first token as an integer. int testVal;

fin >> testVal;

//7. Confirm that the value is 12045.

if (testVal == 12045) {

cout << “Test passed: Value is 12045” << endl; } else {

cout << “Test failed: Value is not 12045”;

}

}

394

Demystifying C++ I/O

Linking Streams Together

A link can be established between any input and output streams to give them flush-on-access behavior. In other words, when data is requested from an input stream, its linked output stream will automatically flush. This behavior is available to all streams, but is particularly useful for file streams that may be dependent upon each other.

Stream linking is accomplished with the tie() method. To tie an output stream to an input stream, call tie() on the input stream, and pass the address of the output stream. To break the link, pass NULL.

The following program ties the input stream of one file to the output stream of an entirely different file. You could also tie it to an output stream on the same file, but bidirectional I/O (covered below) is perhaps a more elegant way to read and write the same file simultaneously.

#include <iostream> #include <fstream> #include <string>

using namespace std;

int main(int argc, char** argv)

{

ifstream inFile(“input.txt”); ofstream outFile(“output.txt”);

//Set up a link between inFile and outFile. inFile.tie(&outFile);

//Output some text to outFile. Normally, this would

//not flush because std::endl was not sent. outFile << “Hello there!”;

//outFile has NOT been flushed.

//Read some text from inFile. This will trigger flush()

//on outFile.

string nextToken; inFile >> nextToken;

// outFile HAS been flushed.

}

The flush() method is defined on the ostream base class, so you can also link an output stream to another output stream:

outFile.tie(&anotherOutputFile);

Such a relationship would mean that every time you wrote to one file, the buffered data that had been sent to the other file would be written. You could use this mechanism to keep two related files synchronized.

395