AhmadLang / Object Oriented Programing using C++
.pdf
Object Oriented Programming using C++
org is interpreted as the stepsize relative to the beginning of the stream. If org is not specified, ios::beg is used.
ios::cur:
org is interpreted as the stepsize relative to the current position (as returned by tellg() of the stream).
ios::end:
org is interpreted as the stepsize relative to the current end position of the the stream.
While it is allowed to seek beyond end of file, reading at that point will of course fail. It is not allowed to seek before begin of file. Seeking before ios::beg will cause the ios::fail flag to be set.
Input from streams: the class `ifstream'
The ifstream class is derived from the istream class: it has the same capabilities as the istream class, but can be used to access files for reading. Such files must exist.
In order to use the ifstream class in C++ sources, the preprocessor directive #include <fstream> must be given. If this directive is used, it is not necessary anymore to include the header file iostream.
The following constructors are available for ifstream objects:
ifstream object:
This is the basic constructor. It creates an ifstream object which may be associated with an actual file later, using the open() member .
ifstream object(char const *name, int mode, int perm):
This constructor can be used to associate an ifstream object with the file named name, using input mode mode, and permission bits perm.
The input mode is by default ios::in.
The file permission bits value is 0644, meaning read-only access.
are the standard Unix permission values. The default owner: read/write access, group members and others:
In the following example an ifstream object is opened for reading. The file must exist:
ifstream in("/tmp/scratch");
ifstream object(int fd):
151
Object Oriented Programming using C++
construct an ifstream wrapper around an existing open file whose file descriptor is known. Using the well-known file descriptor for the standard input stream, STDIN_FILENO, as defined in, e.g., the unistd.h header file.
Instead of directly associating an ifstream object with a file, the object can be constructed first, and opened later.
tt(void ifstream::open(char const *name, int mode, int perm)):
Having constructed an ifstream object, the member function open() can be used to associate the ifstream object with an actual file. This member function can only be used with a file whose name is known. It is not possible to open a ifstream object with the open() member function when a file descriptor is available.
ifstream::close():
Conversely, it is possible to close an ifstream object explicitly using the close() member function. The function sets the ios::fail flag of the closed object. A file is automatically closed when the associated ifstream object ceases to exist.
A subtlety is the following: Assume a stream is constructed, but it is not actually attached to a file. E.g., the statement ifstream istr was executed. When we now check its status through good(), a nonzero (i.e., ok) value will be returned. The `good' status here indicates that the stream object has been properly constructed. It doesn't mean the file is also open. To test whether a stream is actually open, In order to read information from memory, using the stream facilities, istringstream objects can be used. These objects are derived from istream objects. The following constructors and members are available:
istringstream istr:
The constructor will construct an empty istringstream object. The object may be filled with information to be retrieved later.
istringstream istr(string const &text):
The constructor will construct an istringstream object initialized with the contents of the string text.
void istringstream::str(string const &text):
This member function will store the contents of the string text into the istringstream object, overwriting its current contents.
The istringstream object is commonly used for converting ASCII text to its binary equivalent, like the C function atoi. The following example illustrates the use of the istringstream class:
#include <string> #include <sstream>
int main() |
|
{ |
|
istringstream |
|
istr("123 345"); |
// store some text. |
int |
|
x; |
|
152
Object Oriented Programming using C++
istr >> x; |
// extract int |
cout << x << endl; |
// write it out |
istr.str("666"); |
// store another text |
istr >> x; |
// extract it |
cout << x << endl; |
// write it out |
/*
output of this program: 123 666 */
}
Advanced topics
Copying streams
Usually, files are copied either reading a source file character by character or line by line. The basic mold for processing files is as follows:
In an eternal loop:
1.read a character
2.if reading did not succeed (i.e., fail() returns true), break from the loop
3.process the character
It is important to note that the reading must precede the testing, as it is only possible to know after the actual attempt to read from a file whether the reading succeeded or not. Of course, variations are possible: getline(istream &, string &)returns an istream & itself, so here reading and testing may be realized in one expression. Nevertheless, the above mold represents the general case. So, the following program could be used to copy cin to cout:
#include <iostream>
int main()
{
while (true)
{
char c;
cin.get(c); if (cin.fail()) break; cout << c;
}
return 0;
}
By combining the get() with the if-statement a construction comparable to getline() could be used:
if (!cin.get(c)) break;
Note, however, that this would still follow the basic rule: ` read first, test later'.
153
Object Oriented Programming using C++
This simple copying of a file, however, isn't required very often. More often, a situation is encountered where a file is processed up to a certain point, whereafter the remainder of the file can be copied unaltered. The following program illustrates this situation: the ignore() call is used to skip the first line (for the sake of the example it is assumed that the first line is at most 80 characters long), the second statement used a special overloaded version of the <<-operator, in which a streambuf pointer is inserted into another stream. The member rdbuf() returns a streambuf *, which is thereupon inserted into cout, thus copying the remainder of cin to cout:
#include <iostream> |
|
int main() |
|
{ |
|
cin.ignore(80, '\n'); |
// skip the first line |
cout << cin.rdbuf(); |
// copy the rest by inserting a streambuf * |
return 0; |
|
} |
|
Using C's FILE* in C++: the class `stdiobuf'
I/O in C++ and C can be mixed. A stdiobuf object is a streambuf object that is constructed using a C FILE *. Consequently, all C++ I/O operations are available with C FILE pointers after `wrapping' a stdiobuf around a FILE *.
Normally, cin, cout and cerr are constructed as stdiobuf objects.
Conversely (although not standard ANSI/ISO, but available in the Gnu compiler), a streambuf may be regarded as a FILE, so a streambuf * can be used with all C functions expecting FILE * arguments. Since the rdbuf() member of the class ios returns a streambuf *, all iostream objects can be used as FILE pointers.
In order to use the C I/O facilities, and the stdiobuf class, the header file stdiostream.h must be included (using the preprocessor directive #include <stdiostream.h>).
An example showing the wrapping of a stdiobuf around a FILE * and the use of a iostream object in a C output function is given in the next example:
#include <stdiostream.h> |
|
int main() |
|
{ |
|
stdiobuf |
|
f = fopen("stdio.cc", "r"); |
// open this source to read |
cout << &f; |
// copy it to cout |
fprintf(cout.rdbuf(), "hello world\n"); // append `hello world', using // a C function.
return 0;
/*
output produced:
the contents of this file, and an extra line containing the text `hello world'
*/
}
154
Object Oriented Programming using C++
Reading AND Writing to one stream
In order to be able to read and write to a stream an fstream object must be created. As with ifstream and ofstream objects, the constructor receives the name of the file to be opened:
fstream inout("iofile", ios::in | ios::out);
Note the use of the ios constants ios::in and ios::out, indicating that the file must be opened for both reading and writing. Multiple mode indicators may be used, concatenated by the binary or operator '|'. Alternatively, instead of ios::out, ios::app could have been used, in which case writing will always be done at the end of the file.
Under DOS-like operating systems, which use the multiple character \r\n sentinels to separate lines in text files the flag ios::bin is required for processing binary files to ensure that \r\n combinations are processed as two characters.
With fstream objects, the ios::out will result in the creation of the file, if the file doesn't exist, and if ios::out is the only mode specification of the file. If the mode ios::in is given as well, then the file is created only if it doesn't yet exist. So, we have the following combinations:
-------------------------------------------------------------
Specified Filemode
---------------------------------------------
File: |
ios::out |
ios::in | ios::out |
|
------------------------------------------------------------- |
|||
exists |
File is rewritten |
|
File is used as found |
doesn't exist |
File is created |
File is created |
|
-------------------------------------------------------------
Once a file has been opened in read and write mode, the << operator can be used to insert information to the file, while the >> operator may be used to extract information from the file. These operations may be performed in random order. The following fragment will read a blankdelimited word from the file, and will then write a string to the file, just beyond the point where the string just read terminated, followed by the reading of yet another string just beyond the location where the string just written ended:
fstream
f("filename", ios::in | ios::out); string
str;
f >> str; |
// read the first word |
|
// write a well known text |
f << "hello world"; |
|
f >> str; |
// and read again |
Since the operators << and >> can apparently be used with fstream objects, you might wonder whether a series of << and >> operators in one statement might be possible. After all, f >> str should produce a fstream &, shouldn't it?
The answer is: it doesn't. The compiler casts the fstream object into an ifstream object in combination with the extraction operator, and into an ofstream object in combination with the insertion operator. Consequently, a statement like
f >> str << "grandpa" >> str;
results in a compiler error like
155
Object Oriented Programming using C++
no match for `operator <<(class istream, char[8])'
Since the compiler complains about the istream class, the fstream object is apparently considered an ifstream object in combination with the extraction operator.
Of course, random insertions and extractions are hardly used. Generally, insertions and extractions take place at specific locations in the file. In those cases, the position where the insertion or extraction must take place can be controlled and monitored by the seekg() and tellg() member functions Error conditions occurring due to, e.g., reading beyond end of file, reaching end of file, or positioning before begin of file, can be cleared using the clear() member function. Following clear() processing may continue. E.g.,
fstream
f("filename", ios::in | ios::out); string
str;
f.seekg(-10); // this fails, but...
f.clear(); |
// processing f continues |
f >> str; |
// read the first word |
A common situation in which files are both read and written occurs in data base applications, where files consists of records of fixed size, or where the location and size of pieces of information is well known. For example, the following program may be used to add lines of text to a (possibly existing) file, and to retrieve a certain line, based on its order-numer from the file. Note the use of the binary file index to retrieve the location of the first byte of a line.
#include <fstream> #include <string>
void err(char const *msg)
{
cout << msg << endl; return;
}
void err(char const *msg, long value)
{
cout << msg << value << endl; return;
}
void read(fstream &index, fstream &strings)
{
int
idx; |
|
|
if (!(cin >> idx)) |
// read index |
|
return err("line number expected"); |
|
|
index.seekg(idx * sizeof(long)); |
|
// go to index-offset |
long |
|
|
offset; |
|
|
if (!(index.read(&offset, sizeof(long)))) |
// read the line-offset |
|
return err("no offset for line", idx); |
|
|
if (!strings.seekg(offset)) // go to the line's offset return err("can't get string offet ", offset);
156
Object Oriented Programming using C++
string
line; |
|
|
if (!getline(strings, line)) |
// read the line |
|
return err("no line at ", offset); |
|
|
cout << "Got line: " << line << endl; |
// show the line |
|
}
void write(fstream &index, fstream &strings)
{
string line;
if (!getline(cin, line)) |
// read the line |
return err("line missing"); |
|
strings.seekp(0, ios::end); |
// to strings |
index.seekp(0, ios::end); |
// to index |
long |
|
offset = strings.tellp(); |
|
if (!index.write(&offset, sizeof(long))) // write the offset to index err("Writing failed to index: ", offset);
if (!(strings << line << endl)) // write the line itself err("Writing to `strings' failed");
// confirm writing the line
cout << "Write at offset " << offset << " line: " << line << endl;
}
int main()
{
fstream
index("index", ios::in | ios::out), // maybe add ios::bin strings("strings", ios::in | ios::out); // maybe add ios::bin
cout << "enter `r <number>' to read line <number> or " "w <line>' to write a line\n"
"or enter `q' to quit.\n";
while (true) |
|
|
{ |
|
|
cout << "r <nr>, w <line>, q ? "; |
// show prompt |
|
string |
|
|
cmd; |
|
|
cin >> cmd; |
// read cmd |
|
if (cmd == "q") |
// process the cmd. |
|
return 0; |
|
|
if (cmd == "r") read(index, strings);
else if (cmd == "w") write(index, strings);
else
cout << "Unknown command: " << cmd << endl;
}
}
As another example of reading and writing files, consider the following program, which also serves as an illustration of reading an ASCII-Z delimited string:
157
Object Oriented Programming using C++
#include <fstream>
int main()
{
fstream
f("hello", ios::in | ios::out); // r/w the file
f.write("hello", 6); |
// write 2 ascii-z |
f.write("hello", 6); |
|
f.seekg(0, ios::beg); |
// reset to begin of file |
char |
|
buffer[20], |
|
c; |
// read the first `hello' |
|
|
cout << f.get(buffer, 100, 0).tellg() << endl;; |
|
f >> c; |
// read the ascii-z delim |
// and read the second `hello' cout << f.get(buffer + 6, 100, 0).tellg() << endl;
buffer[5] = ' '; |
// change asciiz to ' ' |
cout << buffer << endl; |
// show 2 times `hello' |
}
External processes: `procbuf' and `pfstream'
Streams can also be used to write to or read from separate processes started by a program. A program may read from processes or write to processes, and in most operating systems it is possible to both read and write from processes. Although the last case is in fact a standard a C problem, it might be worth while to examine this case in the context of the C++ programming language.
Reading from or writing from a process is accomplished through a procbuf, which represents an object which has the capability to either read from or write to a process. Around a procbuf a pfstream may be constructed, but this class is currently ( Gnu g++, version 3.0) not completely generally implemented. Even so, acknowledging its restrictions, the pfstream is a handy class, and we present it in several examples as an extension to the procbuf class.
When both reading an wrinting to external processes is at stake, the procbuf cannot be used. Instead, a fork() system call must be invoked using pipe() system calls to set up the communication between the main process (called the parent process) and the process it created (called the child process). This subject is not really covered by the iostream library.
The `procuf' class
The procbuf class is not standard C++ but a Gnu extension. The procbuf class has the following members:
procbuf():
this constructor creates a procbuf in a closed state. It can be opened, though (see below).
:
This constructor creates a subprocess. The /bin/sh command subprocess. The mode argument could be
shell is used to start the ios::in, in which case the
158
Object Oriented Programming using C++
standard output from the process is read by the process creating the procbuf. Alternatively, it could be ios::out, in which case information could be written to the subprocess, which receives the information at its standard input.
procbuf *procbuf::open(char const *command, int mode):
this member function can be used to start command using the specified mode, if the associated procbuf is in its closed state. 0 is returned if starting the subprocess fails.
procbuf *procbuf::close():
this member function terminates the child process. 0 is returned if closing fails.
procbuf::~procbuf():
the destructor terminates the subprocess (if open), using the close() member function.
The `pfstream' class
Like procbuf the pfstream represents a Gnu extension. Actually, there is no class pfstream, but rather the classes ipfstream for reading from a subprocess and opfstream for writing to a subprocess are available.
In order to use the ipfstream or opfstream class, the header file pfstream.h must be included. Both classes use the procbuf class to set up the communication with the external process. Two constructors are available:
ipfstream::ipfstream(char const *command, int mode=ios::in, int prot=0664):
this constructor defines an object from which information may be read. The class ipfstream is derived from ifstream, so the functionality that is available with the ifstream class should also be available for the ipfstream. The parameter command defines the command which is started as a subprocess, and whose standard output is read from the ipfstream object. In order to start a subprocess, the last character in command must be the pipe symbol (|).
opfstream::opfstream(char const *command, int mode=ios::out, int prot=0664):
this class defines an object to which information may be written. The class opfstream is derived from ofstream, so the functionality that is available with the ofstream class should also be available for the opfstream. The parameter command defines the command which is started as a subprocess, and to whose standard input information is written by the opfstream object. In order to start a subprocess, the first character in command must be the pipe symbol (|).
Reading from a separate process
By using a procbuf is is possible to read the standard output from an external process. Both the procbuf and an associated wrapper class, ipfstream are Gnu extensions, and may not be available for other compilers.
159
Object Oriented Programming using C++
In order to use a procbuf, sources must
#include <procbuf.h>
A procbuf object may be given as argument to an istream object, thus creating a process whose standard output can be read by a program. When the istream object goes out of scope, the associated procbu will not be destroyed. This is no problem, as the procbuf will be destroyed automatically when its own scope ends. Of course, with dynamically allocated procbuf objects, it is the responsibility of the programmer to ensure that the allocated memory will be properly deleted.
If an ipfstream object is used, all memory management associated with the ipfstream is properly handled by the ipfstream object, without requiring the programmer to be alert. In order to use an ipfstream object, sources must
#include <pfstream.h>
In the following example a procbuf is used to allow the program to process a long-format directory listing: it lists the directories and other entries separately:
#include <iostream> #include <string> #include <procbuf.h>
int main()
{
procbuf pb;
pb.open("ls -Fla", ios::in);
istream ipb(&pb);
string line, dirs;
cout << "Files:\n"; while (getline(ipb, line))
{
if (line[0] == 'd') dirs += line + '\n';
else
cout << line << endl;
}
cout << "Directories:\n" << dirs;
}
Procbuf objects inherit the PATH environment variable from their calling process: it will usually contain the ` standard PATH' entries. The following program uses an ipfstream and shows the value of the PATH environment variable as seen by the program:
#include <pfstream.h> #include <string>
int main()
{
ipfstream
ipb("|echo $PATH");
string
160
