Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Seeking in iostreams

Each type of iostream has a concept of where its “next” character will come from (if it’s an istream) or go (if it’s an ostream). In some situations you may want to move this stream position. You can do it using two models: One uses an absolute location in the stream called the streampos; the second works like the Standard C library functions fseek( ) for a file and moves a given number of bytes from the beginning, end, or current position in the file.

The streampos approach requires that you first call a “tell” function: tellp( ) for an ostream or tellg( ) for an istream. (The “p” refers to the “put pointer” and the “g” refers to the “get pointer.”) This function returns a streampos you can later use in the single-argument version of seekp( ) for an ostream or seekg( ) for an istream, when you want to return to that position in the stream.

The second approach is a relative seek and uses overloaded versions of seekp( ) and seekg( ). The first argument is the number of bytes to move: it may be positive or negative. The second argument is the seek direction:

ios::beg

From beginning of stream

ios::cur

Current position in stream

ios::end

From end of stream

Here’s an example that shows the movement through a file, but remember, you’re not limited to seeking within files, as you are with C and cstdio. With C++, you can seek in any type of iostream (although the behavior of cin & cout when seeking is undefined):

//: C18:Seeking.cpp
// Seeking in iostreams
#include "../require.h"
#include <iostream>
#include <fstream>
using namespace std;

int main(int argc, char* argv[]) {
  requireArgs(argc, 1);
  ifstream in(argv[1]);
  assure(in, argv[1]); // File must already exist
  in.seekg(0, ios::end); // End of file
  streampos sp = in.tellg(); // Size of file
  cout << "file size = " << sp << endl;
  in.seekg(-sp/10, ios::end);
  streampos sp2 = in.tellg();
  in.seekg(0, ios::beg); // Start of file
  cout << in.rdbuf(); // Print whole file
  in.seekg(sp2); // Move to streampos
  // Prints the last 1/10th of the file:
  cout << endl << endl << in.rdbuf() << endl;
} ///:~

This program picks a file name off the command line and opens it as an ifstream. assert( ) detects an open failure. Because this is a type of istream, seekg( ) is used to position the “get pointer.” The first call seeks zero bytes off the end of the file, that is, to the end. Because a streampos is a typedef for a long, calling tellg( ) at that point also returns the size of the file, which is printed out. Then a seek is performed moving the get pointer 1/10 the size of the file – notice it’s a negative seek from the end of the file, so it backs up from the end. If you try to seek positively from the end of the file, the get pointer will just stay at the end. The streampos at that point is captured into sp2, then a seekg( ) is performed back to the beginning of the file so the whole thing can be printed out using the streambuf pointer produced with rdbuf( ). Finally, the overloaded version of seekg( ) is used with the streampos sp2 to move to the previous position, and the last portion of the file is printed out.

Creating read/write files

Now that you know about the streambuf and how to seek, you can understand how to create a stream object that will both read and write a file. The following code first creates an ifstream with flags that say it’s both an input and an output file. The compiler won’t let you write to an ifstream, however, so you need to create an ostream with the underlying stream buffer:

      ifstream in("filename", ios::in|ios::out);
ostream out(in.rdbuf());

You may wonder what happens when you write to one of these objects. Here’s an example:

//: C18:Iofile.cpp
// Reading & writing one file
#include "../require.h"
#include <iostream>
#include <fstream>
using namespace std;

int main() {
  ifstream in("Iofile.cpp");
  assure(in, "Iofile.cpp");
  ofstream out("Iofile.out");
  assure(out, "Iofile.out");
  out << in.rdbuf(); // Copy file
  in.close();
  out.close();
  // Open for reading and writing:
  ifstream in2("Iofile.out", ios::in | ios::out);
  assure(in2, "Iofile.out");
  ostream out2(in2.rdbuf());
  cout << in2.rdbuf();  // Print whole file
  out2 << "Where does this end up?";
  out2.seekp(0, ios::beg);
  out2 << "And what about this?";
  in2.seekg(0, ios::beg);
  cout << in2.rdbuf();
} ///:~

The first five lines copy the source code for this program into a file called iofile.out, and then close the files. This gives us a safe text file to play around with. Then the aforementioned technique is used to create two objects that read and write to the same file. In cout << in2.rdbuf( ) , you can see the “get” pointer is initialized to the beginning of the file. The “put” pointer, however, is set to the end of the file because “Where does this end up?” appears appended to the file. However, if the put pointer is moved to the beginning with a seekp( ), all the inserted text overwrites the existing text. Both writes are seen when the get pointer is moved back to the beginning with a seekg( ), and the file is printed out. Of course, the file is automatically saved and closed when out2 goes out of scope and its destructor is called.

Contents | Prev | Next


Contact: webmaster@codeguru.com
CodeGuru - the website for developers.
[an error occurred while processing this directive]