8.3. string
Streams
The sstream
header defines three types to support in-memory IO; these types read from or write to a string
as if the string
were an IO stream.
The istringstream
type reads a string
, ostringstream
writes a string
, and stringstream
reads and writes the string
. Like the fstream
types, the types defined in sstream
inherit from the types we have used from the iostream
header. In addition to the operations they inherit, the types defined in sstream
add members to manage the string
associated with the stream. These operations are listed in Table 8.5. They may be called on stringstream
objects but not on the other IO types.
Table 8.5. stringstream
-Specific Operations
Code | Description |
---|---|
sstream strm; | strm is an unbound stringstream . sstream is one of the types defined in the sstream header. |
sstream strm(s); | strm is an sstream that holds a copy of the string s . This constructor is explicit (§ 7.5.4, p. 296). |
strm.str() | Returns a copy of the string that strm holds. |
strm.str(s) | Copies the string s into strm . Returns void . |
Note that although fstream
and sstream
share the interface to iostream
, they have no other interrelationship. In particular, we cannot use open
and close
on a stringstream
, nor can we use str
on an fstream
.
8.3.1. Using an istringstream
An istringstream
is often used when we have some work to do on an entire line, and other work to do with individual words within a line.
As one example, assume we have a file that lists people and their associated phone numbers. Some people have only one number, but others have several—a home phone, work phone, cell number, and so on. Our input file might look like the following:
morgan 2015552368 8625550123
drew 9735550130
lee 6095550132 2015550175 8005550000
Each record in this file starts with a name, which is followed by one or more phone numbers. We’ll start by defining a simple class to represent our input data:
// members are public by default; see § 7.2 (p. 268)
struct PersonInfo {
string name;
vector<string> phones;
};
Objects of type PersonInfo
will have one member that represents the person’s name and a vector
holding a varying number of associated phone numbers.
Our program will read the data file and build up a vector
of PersonInfo
. Each element in the vector
will correspond to one record in the file. We’ll process the input in a loop that reads a record and then extracts the name and phone numbers for each person:
string line, word; // will hold a line and word from input, respectively
vector<PersonInfo> people; // will hold all the records from the input
// read the input a line at a time until cin hits end-of-file (or another error)
while (getline(cin, line)) {
PersonInfo info; // create an object to hold this record's data
istringstream record(line); // bind record to the line we just read
record >> info.name; // read the name
while (record >> word) // read the phone numbers
info.phones.push_back(word); // and store them
people.push_back(info); // append this record to people
}
Here we use getline
to read an entire record from the standard input. If the call to getline
succeeds, then line
holds a record from the input file. Inside the while
we define a local PersonInfo
object to hold data from the current record.
Next we bind an istringstream
to the line that we just read. We can now use the input operator on that istringstream
to read each element in the current record. We first read the name followed by a while
loop that will read the phone numbers for that person.
The inner while
ends when we’ve read all the data in line
. This loop works analogously to others we’ve written to read cin
. The difference is that this loop reads data from a string
rather than from the standard input. When the string
has been completely read, “end-of-file” is signaled and the next input operation on record
will fail.
We end the outer while
loop by appending the PersonInfo
we just processed to the vector
. The outer while
continues until we hit end-of-file on cin
.
INFO
Exercises Section 8.3.1
Exercise 8.9: Use the function you wrote for the first exercise in § 8.1.2 (p. 314) to print the contents of an istringstream
object.
Exercise 8.10: Write a program to store each line from a file in a vector<string>
. Now use an istringstream
to read each element from the vector
a word at a time.
Exercise 8.11: The program in this section defined its istringstream
object inside the outer while
loop. What changes would you need to make if record
were defined outside that loop? Rewrite the program, moving the definition of record
outside the while
, and see whether you thought of all the changes that are needed.
Exercise 8.12: Why didn’t we use in-class initializers in PersonInfo
?
8.3.2. Using ostringstream
s
An ostringstream
is useful when we need to build up our output a little at a time but do not want to print the output until later. For example, we might want to validate and reformat the phone numbers we read in the previous example. If all the numbers are valid, we want to print a new file containing the reformatted numbers. If a person has any invalid numbers, we won’t put them in the new file. Instead, we’ll write an error message containing the person’s name and a list of their invalid numbers.
Because we don’t want to include any data for a person with an invalid number, we can’t produce the output until we’ve seen and validated all their numbers. We can, however, “write” the output to an in-memory ostringstream
:
for (const auto &entry : people) { // for each entry in people
ostringstream formatted, badNums; // objects created on each loop
for (const auto &nums : entry.phones) { // for each number
if (!valid(nums)) {
badNums << " " << nums; // string in badNums
} else
// ''writes'' to formatted's string
formatted << " " << format(nums);
}
if (badNums.str().empty()) // there were no bad numbers
os << entry.name << " " // print the name
<< formatted.str() << endl; // and reformatted numbers
else // otherwise, print the name and bad numbers
cerr << "input error: " << entry.name
<< " invalid number(s) " << badNums.str() << endl;
}
In this program, we’ve assumed two functions, valid
and format
, that validate and reformat phone numbers, respectively. The interesting part of the program is the use of the string streams formatted
and badNums
. We use the normal output operator (<<
) to write to these objects. But, these “writes” are really string
manipulations. They add characters to the string
s inside formatted
and badNums
, respectively.
INFO
Exercises Section 8.3.2
Exercise 8.13: Rewrite the phone number program from this section to read from a named file rather than from cin
.
Exercise 8.14: Why did we declare entry
and nums
as const auto &
?