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:
|
|
|
Current
position in 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
stdio.h.
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 <iostream>
#include <fstream>
#include "../require.h"
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 <iostream>
#include <fstream>
#include "../require.h"
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.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru