












Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Community
Ask the community for help and clear up your study doubts
Discover the best universities in your country according to Docsity users
Free resources
Download our free guides on studying techniques, anxiety management strategies, and thesis advice from Docsity tutors
An in-depth explanation of C++ input and output streams, including the different types of I/O, how to read and write characters, strings, and numbers, and the use of files for input and output. It covers the basics of the iostream library, the different constructors and functions for ifstream, ofstream, and fstream, and the concept of file modes.
Typology: Study notes
1 / 20
This page cannot be seen from the preview
Don't miss anything!
LECTURE 14 Input and Output Streams Classes dealing with the input and output of data are called stream classes. We have dealt with these classes in a slightly haphazard way. I'd like to talk ab out them in a more systematic way to give you a b etter idea of how input and output works. This won't b e a complete discussion since that would take to o long. There is a chapter on streams in Stroustrup and the rst third of More C++ for Dummies deals with streams. Most of this lecture comes from More C++ for Dummies. The class hierarchy is shown in the gure.
The mother of all base classes is ios. (In more recent compilers, ios has b een replaced by ios_base.) The class ios contains most of the actual I/O co de. It is ios that keeps track of the error state of the stream. The error ags are an enumerated typ e within ios. In addition the ios class converts data for display. It understands the format of the di erent typ es of numb ers, how to output character strings, and how to convert an ASCI I string to and from an integer or a oating{p oint numb er. Standard output, cout, is an ob ject of the class ostream, as is cerr, the standard error output. Standard input, cin, is an ob ject of the class istream. cout, cin, and cerr are automatically constructed as global ob jects at program start{up. Ob jects of iostream deal with b oth input and output. Ob jects of ifstream deal with input les and ob jects of the class ofstream deal with output les. Ob jects fstream deal with les that can one can write to and read from. ofstream, ifstream, and fstream are sub classes that are de ned in the include le fstream.h. Notice that fstream.h deals
with le stream classes. The overloaded right shift op erator operator>>() is called the extractor. It is a memb er function of the class istream. The overloaded left shift op erator operator<<() is called the inserter. It is a memb er function of the class ostream. Thus we have
//for input istream& operator>>(istream& source, char *pDest); istream& operator>>(istream& source, int &dest); istream& operator>>(istream& source, char &dest); //...etc... //for output ostream& operator<<(ostream& dest, char *pSource); ostream& operator<<(ostream& dest, int source); ostream& operator<<(ostream& dest, char source);
So when we typ e
#include <iostream.h>
void fn() { cout<<"Hello, world\n"; }
First, C++ determines that the left{hand argument is of typ e ostream and the right{ hand argument is of typ e char. Armed with this knowledge, it nds the prototyp e operator<<(ostream&, char) in iostream.h. C++ generates a call to this function, the char* inserter, passing the function the string "Hello, world\n" and the ob ject cout as the two arguments. That is, it calls operator<<(cout, "Hello, world\n"). The char* inserter function, which is part of the standard C++ library, p erforms the requested output. Input and Output Files We have already seen that to op en les to read from and write to:
#include <fstream.h>
int main() { ifstream infile("input.dat"); //input.dat is the name of the file //in your directory ofstream outfile("output.dat"); //output.dat is the name of the file //that will be created in your directory float x; while(infile >> x) //detects end-of-file and exits loop { outfile << "x = " << x << endl; }
exists unless you specify app or ate)
ios::out out Open file for output
ios::trunc out Truncate file to zero length if it already exists (default if file exists and app or ate is not specified)
These mo des are bit elds that are enumerated memb ers of a bit vector in the class ios. That is why they are referred to as ios::out and ios::app and not simply as out and app. Let me give an example of what I mean by \bit elds". ios::app might equal 00000001 , ios::ate might equal 00000010 , ios::out might equal 00000100 , etc. So each mo de corresp onds to one bit which can b e 0 or 1. This means that more than one mo de's value can b e set at the same time using the arithmetic OR. For example, to op en an output le with app end, you could use the following:
ofstream out("outfile", ios::out | ios::app)
The | op erator takes the union of the two arguments. Once you sp ecify any part of the mo de, you must sp ecify the entire mo de. Thus, when I sp eci ed ios::app, I also had to sp ecify ios::out, b ecause it was no longer sp eci ed by default. The ios::nocreate ag says \If le do esn't exist, don't create it." This is esp ecially useful for input. For example, it is the only way to test for the existence of a le. Let me explain the di erence b etween op ening a le in text mo de versus binary mo de. When a le is in text mo de, newline characters are converted into a carriage{return/line{ feed combination on output. The reverse pro cess o ccurs on le input: carriage{return/line{ feed pairs are converted into a single newline character. This pro cess do esn't o ccur when the le is op ened in binary mo de. The default for op ening a le is text mo de. The third argument to the fstream constructors sp eci es the typ e of le sharing allowed b etween applications. The p ossible values are: File{Sharing Flags
Sharing Flag Meaning
filebuf::openprot Compatibility sharing mode filebuf::sh_compat
filebuf::sh_none Exclusive mode; no sharing allowed
filebuf::sh_read Other read opens allowed
filebuf::sh_write Other write opens allowed
These are also bit elds. So the union of filebuf::sh_read and filebuf::sh_write allows complete sharing of les b etween applications. As we have seen in our earlier discussion of input and output, it is also p ossible to op en a le for b oth input and output. This is handled by the fstream class, which inherits from b oth the ifstream and ofstream classes. The constructor for the fstream class lo oks the same as those for the ifstream and ofstream classes except the mo de argument is not defaulted:
fstream::fstream(const char *pFileName, int mode, int prot = filebuf::openprot);
To op en such a le, the mo de should b e set to ios::in|ios::out. For example,
#include <fstream.h>
int main() { fstream inout("input.dat",ios::i n|io s::o ut) ; float x; inout >> x; inout<< endl << "x = " << x << endl; inout.close(); return 0; }
Error Flags If something go es wrong with the input/output (I/O) op erations, the error state is set. Once the error state is set, all subsequent requests for I/O are ignored. The error state stays set until it is reset by the application. This allows the application to p erform several I/O op erations in row b efore checking{p erhaps at the end of the function{whether the I/O op erations succeeded. The error ag consists of a set of bits, each of which can b e set indep endently. These bits are de ned as follows. (Note the values are provided to satisfy your curiosity. Don't rely on them. Always use the name of the ag instead.)
Flag Binary Value Meaning
ios::eofbit 001 End-of-file encountered
ios::failbit 010 Last I/O operation failed
ios::badbit 100 Invalid operation attempted
When a read op eration encounters the end{of{ le, it sets the ios::eofbit in the error ag for the ifstream ob ject. The ios::failbit is set when an I/O op eration fails.
//stuff if(!in) //invokes operator!(), which calls in.fail() { //operation failed return; } }
Alternatively, we could write the following:
void fn(istream& in) { //stuff if(in) //invokes operator void*(), which calls in.good() { //operation succeeded //stuff } }
(Explanation of operator void*(). If we had a p ointer istream *pIn to an istream ob ject, then *pIn would refer to the ob ject p ointed to by pIn. In our case in is an istream ob ject, so we don't need the indirection op erator *.) Some programmers prefer this form of calling ios::good() and ios::fail(). A simple example of error checking is
#include <fstream.h>
int main() { ifstream infile("input.dat"); //input.dat is the name of the file //in your directory ofstream outfile("output.dat"); //output.dat is the name of the file //that will be created in your directory float x; while(!infile.eof() && infile.good()) //makes sure that end-of-file //hasn't been reached and that //infile is in good shape { infile >> x; outfile << "x = " << x << endl; } infile.close(); outfile.close(); return 0; }
(This program outputs the last x twice b ecause the end{of{ le is not reached until it go es b eyond the last item in input.dat.) Other Memb er Functions of istream and ostream The istream and ostream classes supp ort a numb er of memb er I/O functions in addition to the overloaded insertion and extraction op erators. Some of these follow:
The get() Function The get() function comes in two avors. The simplest version inputs a single character. For example, in the following program snipp et, get() is used to read input from the input le input.txt:
#include <fstream.h>
int main() { ifstream in("input.txt"); char c; while(!in.eof()) { c = in.get(); cout << c; } cout << endl; return 0; }
The program starts by op ening the le input.txt. The program then lo ops un- til the le is empty. On each lo op, the program fetches another character and outputs it to the standard output. Notice that get() is not quite the same as operator>>(istream&,char& ), which also fetches a single character from the in- put stream. The di erence lies in the fact that operator>>(), by default, skips any white{space characters (e.g., space, tab, newline, etc.) found in the le, whereas get() do es not. In fact this program also spits out a strange end{of{ le character at the end. The second version of get() carries the following prototyp e:
istream& istream::get(char* pszTarget, int nCount, char delim='\n');
This version inputs a series of characters terminated either by the app earance of a terminator character in the input stream or by a character count. Notice that you can have any character you want terminate the input. The count character solves the p otential bug of inputting more characters than the bu er can hold. For
This program reads an entire line at a time. Notice that when the line that was just read is output to cout, the program must replace the delimiter that was stripp ed out by getline().
The read() Function The prototyp e declaration for the read function is
istream& read(char* pszTarget, int nCount);
This function reads a xed numb er of characters from the input stream without regard to any typ e of delimiter. In addition, read() do esn't tack a NULL character to the end of the pszTarget bu er, nor do es it attempt to interpret '\n' characters.
The put() Function The put() function carries the following simple prototyp e:
ostream& ostream::put(char ch);
This function do es nothing more than output the sp eci ed character to the out- put stream. This is functionally identical to the operator<<(ostream&, char&) inserter.
The putback(Ch c) function The putback(Ch c) function allows a program to put an unwanted character back to b e read some other time, as shown in the class of complex numb ers in lecture 6. Ch is a template typ e, i.e., it's any typ e you sp ecify.
The write() Function The write() function outputs a xed numb er of characters from the source char- acter string to the output stream. This function carries the following prototyp e
ostream& ostream::write(const char* pszSource, int nCount);
This is also a blo ck{oriented transfer.
The Bu er We said earlier that the base class ios do es most of the input/output work. But it needs another class called streambuf which acts as a server to the ios class. streambuf is an intermediatry b etween ios and the physical media, e.g., the screen, the disk, etc. The class streambuf p erforms the actual I/O to the outside world. The class streambuf has several sub classes, each of which sp ecializes in its own particular typ e of media. For example, filebuf handles le I/O for the ios class. Lo ok at the following co de:
ofstream out("ofile.txt"); int nAnInt = 10; out << nAnInt;
The constructor for ofstream rst creates an ios ob ject. It then constructs a filebuf ob ject for output to the le ofile.txt. During output, the ios ob ject converts the numb er 10 into the character 1 followed by the character 0. The ios ob ject passes the string \10" to the filebuf ob ject for output to the le. This is a nice division of lab or. When you create a di erent typ e of output ob ject{for example an ostream ob ject that outputs to the display{you get the same ios base class ob ject (all formatting is the same, after all) but a di erent sub class of streambuf (outputting to a display is not at all the same as outputting to a le). It's worth taking a moment to understand what disk bu ering is. This is one of the functions p erformed by streambuf. If streambuf went to the disk every time ios wanted to write a few characters to disk, p erformance would b e really slow. In fact, when you read or write to the disk, you must read an entire blo ck of data at a time. (It's like Lay's Potato Chips{you can't eat just one \byte".) The size of a blo ck dep ends on the disk, but it's usually 512 bytes or more. Therefore, on output, the streambuf class collects output requests in the bu er until it has several blo cks worth. It then writes the entire bu er to the disk at once. Writing the output bu er to disk is called \ ushing the bu er." For example, endl ends the line ("\n") and then ushes the bu er. On input, the situation is reversed. When the ios class asks for the rst character from the input stream, the input bu er is empty. Rather than read a single character (even if that were p ossible), the streambuf reads several blo cks of data into the input bu er. Then streambuf returns only the rst character to ios and keeps the rest. When the next input request comes in, streambuf returns the next character from the input bu er without b othering to read from the disk. The streambuf class do esn't read from the disk again until the input bu er has b een emptied by input requests. A few conditions cause the output bu er to b e ushed to disk early. For example, closing the le causes any remaining data that might b e hanging around in the bu er to b e ushed to disk. The application program can also force the output bu er to b e ushed by calling ostream::flush(). For example,
out << student; out.flush()
This assures your data is safely on the disk in case the program or the system crashes later on. Finally an output stream can b e tied to an input stream so that a request for I/O from the input stream immediately ushes the output stream. For example,
char szname[80]' cout << "Enter your name"; cin >> szName;
the number of characters that are input. If fewer characters are required on output, the remaining space is filled with the fill character.
Each of these functions also has a void argument list version, which simply queries the current setting. The fill() function sets the ll character. The default for the ll character is the blank space. The precision() function sets the precision. During the display of oating{p oint numb ers, this setting determines the numb er of digits displayed to the right of the decimal p oint. On output, the width() function sp eci es the minimum eld width to b e used in displaying the next eld inserted. If the value b eing output requires more characters than are sp eci ed by width(), the width is ignored. If the output eld is smaller than the sp eci ed width, the di erence is made up by rep eated application of the ll character. If the width is zero, the minimum numb er of characters necessary to contain the eld are used for output. Zero is the default for the width. The width() function is strange in one resp ect. When you set a data memb er within a structure to a particular value, you usually exp ect it to stay set. But this is not so for the width. Each time you set the width, that setting applies only to the next op eration. After that, the width is reset to zero. The width() function also has an e ect on the input. Setting the width restricts the numb er of characters extracted by the char* extractor. This is imp ortant b ecause the operator>>(istream&, char*), unlike the getline() function, has no place to indicate the size of the bu er receiving the character string. Settting the width to the size of the bu er ensures that the bu er b oundaries are not exceeded. Here is an example of how to use a few of these format control functions:
#include <fstream.h>
int main() { ifstream infile("input.dat"); if(infile.fail()) { cout << "Couldn't open input.dat" << endl; return -1; }
char buffer[5]; //short array of characters infile.width(sizeof buffer); infile >> buffer; cout << buffer << endl;
infile.width(sizeof buffer); //have to repeat width() infile >> buffer; cout.width(15); cout.fill('*'); cout << buffer << endl; return 0; }
The program starts by op ening the input le in the normal fashion. Before extracting from the input le ob ject, however, the program sets the input width to match the size of the bu er. The program then extracts a few characters into buffer and displays them to cout so you can see what you have. Supp ose the input le consists of 30 characters, all in a row.
//input.dat file 1234567890123456789012345 6789 0
If we just had an in >> buffer statement, the program would try to put all 30 characters into the bu er which has length 5. This would crash the program and give a \bus error". The output from running the program is
1234
Notice that width the input stream width set to 5 characters, the extractor reads only four characters, cleverly leaving a space for the NULL in the nal p osition of the bu er. The remaining formatting features of ios are hidden in a protected data memb er called x_flags. This data memb er consists of a series of 1{bit elds, some of which work together. Because these are single{bit elds, however, they can b e (and are) set in di erent combinations to pro duce the desired e ect. They are listed on pages 89{ 90 of More C++ for Dummies. The only ones that you would probably use with any frequency are those dealing with oating{p oint format. By setting either the ios::fixed or ios::scientific ag, you sp ecify whether oating{p oint numb ers are displayed in xed or scienti c format. If neither bit is set, the stream is in automatic mo de, meaning use whatever is most applicable. In automatic mo de, the precision referred to previously sp eci es the numb er of digits to b e displayed in the numb er. In either xed or scienti c mo de, precision sp eci es the numb er of digits after the decimal p oint. The default is automatic. Several functions access the ag bits. The function long ios::flags() reads the current ag word. The function long ios::flags(long lNewFlag) sets the ag word to the value contained in lNewFlag, and returns the previous value. An example of how to use this is:
long lPreviousFlags; lPreviousFlags = cout.flags(); //record current flag word
Manipulator Member Functions Purpose
endl ostream::flush() Insert \n and flush the buffer
flush ostream::flush() Flush the stream
setfill() ios::fill() Set the fill character
setiosflags() ios::setf() Set the flags
resetiosflags() ios::unsetf() Undo the flags
setprecision() ios::precision() Set the floating-point precision
setw() ios::width() Set the width of the next field
Just like the width() function, the setw() manipulator must b e included in the stream for each ob ject whose width is not the default, zero. The only advantage that function calls have over manipulators is that the functions return the previous settings while the manipulators don't return anything. So if you want to store the previous setting, do something, then restore the previous setting, you can easily do it with function calls. With manipulators, you can only undo the ags with resetiosflags(). Here is an example using manipulators:
#include <iostream.h> #include <iomanip.h>
void fn() { cout << setw(8) << 10 << setw(8) << 20 << endl; //keep setting width cout << setiosflags(ios::scientif ic) << "7.0 = " << 7.0 << endl; //scientific notation cout << resetiosflags(ios::scient ific ); //turn off scientific notation }
Custom Inserters The fact that C++ overloads the left shift op erator to p erform output means that you can overload the same op erator to p erform output on classes you de ne. We have seen examples of this. Recall that the class of complex numb ers in lecture 6 had a friend function that overloaded the inserter:
ostream & operator << (ostream &, const complex &);
Let's give another example with the class USDollar:
#include <iostream.h> #include <iomanip.h> class USDollar { public: USDollar(double v = 0.0) { dollars = v; cents = int((v - dollars) * 100.0 + 0.5); } operator double() { return dollars + cents / 100.0; } void display(ostream& out) { out << '$' << dollars << '.' //set fill to 0's for cents << setfill('0') << setw(2) << cents //now put it back to spaces << setfill(' '); } protected: unsigned int dollars; unsigned int cents; };
//operator<< - overload the inserter for our class ostream& operator<< (ostream& o, const USDollar& d) { d.display(o); return o; } int main() { USDollar usd(1.50); cout << "Initially usd = " << usd << "\n"; usd = 2.0 * usd; cout << "then usd = " << usd << "\n"; return 0; }
The display() function starts by displaying $, the dollar amount, and the obligatory dec-
{ unit = v; cent = int((v - unit) * 100.0 + 0.5); } virtual void display(ostream& out) = 0;
protected: unsigned int unit; unsigned int cent; }; class USDollar : public Currency { public: USDollar(double v = 0.0) : Currency(v) { } //display $123. virtual void display(ostream& out) { out << '$' << unit << '.' << setfill('0') << setw(2) << cent << setfill(' '); } }; class DMark : public Currency { public: DMark(double v = 0.0) : Currency(v) { } //display 123.00DM virtual void display(ostream& out) { out << unit << '.' //set fill to 0's for cents << setfill('0') << setw(2) << cent //now put it back to spaces << setfill(' ') << " DM"; } }; ostream& operator<< (ostream& o, Currency& c)
{ c.display(o); return o; } void fn(Currency& c) { // the following output is polymorphic because the // operator<<(ostream&, Currency&) is defined through a virtual // member function cout << "Deposit was " << c << "\n"; } int main() { //create a dollar and output it using the //proper format for a dollar USDollar usd(1.50); fn(usd);
//now create a DMark and output it using its own format DMark d(3.00); fn(d); return 0; }
The class Currency has two sub classes, USDollar and DMark. In Currency, the display() function is declared pure virtual. In each of the two sub classes, this function is overloaded with a display() function to output the ob ject in the prop er format for that typ e. The call to display() in operator<<() is now a virtual call. Thus, when operator<<() is passed USDollar, it outputs the ob ject as a dollar. When passed DMark, it outputs the ob ject as a deutsche mark. Thus, although operator<<() is not virtual, b ecause it invokes a virtual function, it acts like a virtual function and the result is:
Deposit was $1. Deposit was 3.00 DM
This is another reason why it is b etter to p erform the work of output in a memb er function, and let the non-memb er function refer to that function.