In each edition of the User Group magazine I will be looking in depth at streams, as they form a fundamental part of the usability of C++. In this article I present an overview of screen and memory streams. In the second article I will present an overview of file streams. Subsequent articles will examine streams in far greater detail.
C++, like its ancestor C, has no inherent I/O facilities, and like C relies on standard libraries for its I/O. In addition to the C functions printf and scanf, C++ has two object-based streams namely cout and cin.
C & C++ streams are nearly but not quite the same. C streams are buffered I/O streams that are accessed via function calls, whereas C++ streams are objects that interface to buffered I/O streams. A C stream is not extensible, you cannot add your own '%' formatting codes. The hierarchy of classes that form the C++ streams are extensible by the programmer.
A user can, when defining an object, define how a stream will output or input the information from or into the object. This is a big aid to standardizing the I/O of objects within a suite of programs.
The C++ stream that outputs information to the screen is called cout (Console OUTput). This object uses a multiply-overloaded left-bit-shift operator ( << ) to feed information to the screen. The left-bit-shift operator in the context of streams is known as the inserter operator because it inserts information into the stream.
cout << "This is a simple string" ;
The standard C string codes such as \n can still be used, however, it is preferred to use 'endl' (endline) to force end of line (equivalent of \n & flush).
cout << "This is line 1" << endl << "This is line 2 with a value of " << myvalue << endl ;
Notice that the variable myvalue does not need any of the usual '%' formatting codes used by C. This is achieved by the multiple overloading of the inserter operator for each of the fundamental data types, i.e. one each for ints, longs, floats, chars and char* types. The programmer can extend the overloading of the inserter operator to control the formatted output of their own objects.
The C++ stream that extracts information from the input stream is called cin (Console INput). This object uses the multiply-overloaded right-bit-shift operator ( >> ) to feed information from the input stream into variables.
The right-bit-shift operator in the context of streams is known as the extractor operator, for obvious reasons.
cin >> myint >> mystring >> mylongfloat;
Like cout, cin does not require '%' string formatting codes as in the case of the scanf function, 'cin' also only requires the name of the variable and not a pointer to the variable as in the case of scanf. Again this is achieved by multiply overloading the extraction operator.
The I/O streams can utilize a series of manipulators to change the characteristics of the output. We have already seen 'endl'. Others include 'hex', 'dec' and 'oct' for base conversion, manipulators for whitespace removal, 'ends' to insert a string terminator character into the stream and flush it. (mainly used in strstream, a similar in function to sprintf), 'setprecision' and 'setw' set the number of decimal places and the width of the output of the next data item respectively. There are also a set of flags, called 'ios' flags (Input-Output-Stream flags). These are a series of bit flags used for controlling and detecting errors on the streams. These will be covered in more detail by later articles on streams.
Mixing Old and New
Although it is possible to use both C++ streams and C streams in the same program, it would not be wise to do so. Output to the screen is buffered, so if printf and cout are both outputting to the screen, only when their buffers are flushed does the actual text hit the screen. Although experiments with Borland C++ version 3.1 seem able to handle mixed C and C++ streams without any problems. Borland does provide a syncronisa-tion member function in the ios class called 'sync_with_stdio'.
Examples of stream Manipulators
As mentioned previously, the output and input streams can have modifiers applied to the way in which the stream behaves.
float my_float = 22.9756; cout << setiosflags(ios::fixed | ios: :showpoint) << setprecision(2) << my_float << endl;
Output would be 22.98 - note the automatic rounding caused by setprecision, without this manipulator the result would have been 22.97559 (due to inaccuracies of floating point numbers)
int k = 0; cin >> hex >> k; cout << endl << "The value entered is " << k;
If the user entered 10 the printed number would be 16. (i.e. 10 hex == 16 decimal).
A complete list of these manipulators can be found in the 'Borland user guide' in the section 'Using C++ streams'.
In C++, sprintf is replaced by the ostrstream class. It is easier to use than sprintf and follows the same formatting rules as cout, except that the target is not cout but the object of type strstream or ostrstream.
Automatic Memory Allocation An object of type 'ostrstream' uses a dynamically allocated buffer. This is handy if the size of the resulting output is unknown.
ostrstream buffer; const float PI = 3.14159265; const float r = 10.5; buffer << "The area of the circle of radius " << r << " = " << PI*r*r << ends; cout << buffer.str() << endl;
The resulting output will be The area of circle of radius 10.5 = 346.360596' Note: The 'ends' stream manipulator (end string) is the equivalent of '\0' the string terminator.
Manual Memory Allocation
The 'ostrstream' class can be tied to a specified area of memory. As a consequence a fixed size buffer must be allocated.
const int Length = 100; char memstring[Length]; ostrstream storage(memstring, Length); const float PI = 3.14159265; const float r = 10.5; storage << "The area of the circle of radius " << r << " = " << PI*r*r << ends; cout << storage.str() << endl;
Obviously the string memstring could by output directly:
cout << memstring << endl ;
Problems with 'ostream'
The disadvantage of the dynamically allocated buffer is that to print it you must use the str() member function. This has the side effect that the stream is 'frozen' and is not deleted from memory when the object goes out of scope unless explicitly 'unfrozen' with the srreambuf::freeze(0) member function. The reasons behind this are beyond the scope of this article and will be covered in a later issue.
To read from a variable is relatively straight-forward. An istream object is set up and used in a similar manner to cin. The only awkward bit is reading in strings. For this you have to remember to remove whitespace and to use the get() or getline() member functions of the stream to copy the string to the target variable.
char memory_info = "100 200 22.995 hello fred"; int 11, i2; float f1; char sl, s2 ; istrstream stringstream (memory_info, sizeof(memory_info)); stringstream >> i1 >> i2 >> f1; stringstream >> ws; stringstream.get(sl,10,' '); stringstream >> ws; stringstream.get(s2,10,' '); cout << "i1 = " << il << endl << "i2 = " << i2 << endl << "f1 = " << f1 << endl << "s1 = " << s1 << endl << "s2 = " << s2 << endl;
A Superior Console I/O
The streams mentioned this far have all been streams defined in the proposed ANSI standards. In order to be useful on DOS systems, the Turbo/Borland C++ (version 3.0+) has an additional stream which is the equivalent of the conio (direct console) functions. This stream enables colour changing of text, cursor positioning, highlighting etc.
This stream is called constream and has been derived from the ostream stream. It can be limited to write only to certain areas of the screen. The beauty of such streams is that colour, cursor position etc remains constant for one instantiation of the constream. If output to another instantiation of constream occurrs no modification to the characteristics of the first constream occurs. Examine and compile the programs postest.cpp and constr.cpp on the companion disk to see what I mean.
constream console; console << setxy(10,10) << "Hello world " << highvideo << "Im highlighted " << lowvideo << "and Im dim " << normvideo << "and Im norman";
In the next edition of Overload, I will examine file access using streams (both simple sequential and random file access). This has only been a brief skimming over the top of an immense and little exploited area of C++. In subsequent issues I hope to demonstrate to you the power and flexibility of the C++ stream classes.
Probably the best reference work on streams that I have found is Ted Faison's book 'Borland C++3 Object Orientated Programming' published by SAMS (ISBN 0-672-30004-4)
Who knows we may even make it as far as persistent objects. But I'll take it one step at a time and cover the basics first.
Q: How can I tell if there is anything in the cin buffer?
My first thought on this was that cin::peek() != EOF could be used. This will be able detect if anything is in the buffer, but when the buffer is empty it will go to the standard input stream and wait for input until a control-Z (^Z) character is entered. This is OK if you are redirecting a control-Z terminated file into the standard input. But otherwise it is not a lot of use. The solution to this problem is to use no_of_chars = cin.rdbuf()->in_avail() The stream cin is of type istream_withassign; this is derived from istream, which itself uses a streambuf class in order to hold its temp information. Calling istream::rdbuf() returns a pointer to the streambuf used by that stream. The size of the streambuf can be found by calling the member function streambuf::in_avail(). The length includes a null terminator and a length of 2 or more means that cin is not empty.
Overload Journal #1 - Apr 1993 + Programming Topics
|Browse in :||
All > Topics > Programming (772)
Any of these categories - All of these categories