Programmers regard C++ as a strongly typed language, implying that all data have a type. The type determines how much memory the computer allocates to store the data and how it interprets the data's pattern of 1s and 0s. One problem programmers face is implementing robust or "bulletproof" data input that is resilient to incorrect user input. If the program can detect an input error, it can prevent it from causing a failure or data corruption. The program can easily detect out-of-bounds errors because in- and out-of-bounds values are usually the same type. For example, the program prompts the user for a double value to use as the sqrt function's argument and the user enters a negative value. The input operation succeeds, allowing the program to advance to the bounds check. But type conflicts can cause input operations to fail before programs can detect them.
Together, the characters in a string can represent any data, including currency and time designators. Consequently, programs solve the type conflict problem by reading input data as a string, validating it, and converting it to the appropriate type. The casting operator can convert one type to a similar one, like an integer and a double, but cannot convert a string to another type. Although C generally provided functions to convert between C-strings and numbers, they were not universally available because no governing standard required them. The ANSI C++ 2011 standard added the to_string functions to convert numbers to string objects. Before the C++11 standard, programmers used streams to convert between numbers and strings.
The dual nature of string streams allows programs to use them both ways at different times. Programs can write data to an output stream using its intrinsic conversion operations to convert numbers to strings. Alternatively, programs can read data into a string stream like a string and later read the data out like a stream. The following figures summarize the string streams and demonstrate how programs use them.
C-string1 | string Class2 | |
---|---|---|
Header Files | <strstream> | <sstream> |
Classes | istrstream ostrstream |
istringstream ostringstream |
Possible Implementation |
![]() |
![]() |
istrstream n1("123"); // (a) istrstream n2("3.14"); int i; // (b) double d; n1 >> i; // (c) n2 >> d; cout << "i = " << i << endl; // (d) cout << "d = " << d << endl;
char a[100]; // (e) ostrstream i(a, 100); // (f) ostrstream f(a, 100); i << 76 << ends; // (g) cout << "a = " << a << endl; f << 76.58 << ends; // (h) cout << "a = " << a << endl;
string s1("123"); // (a) string s2("3.14"); istringstream n1(s1); istringstream n2(s2); int i; double d; n1 >> i; // (b) n2 >> d; cout << "i = " << i << endl; // (c) cout << "d = " << d << endl;
ostringstream i; // (d) ostringstream f; i << 76; // (e) string s1 = i.str(); // (f) cout << "s1 = " << s1 << endl; // (g) f << 76.58; // (e) string s2 = f.str(); // (f) cout << "s2 = " << s2 << endl; // (g)
The final version of the s-rolodex.cpp program still has some problems. If the input file is empty, or a line in the file is empty or doesn't match the expected pattern, the program can crash or incorrectly parse the data. An easy solution is to read and process the file by lines, skipping those that don't match. However, once read, processing correct lines is more cumbersome than using the getline function to read individual fields. An elegant and more robust solution rereads the fields from matching lines.
while (!in.eof()) { string line; // (a) getline(in, line); if (line.length() == 0 || line[0] == '#') // (b) continue; istringstream input(line); // (c) string name; // (d) getline(input, name, ':'); string address; getline(input, address, ':'); string phone; getline(input, phone, '\n'); cout << left << setw(20) << name << setw(35) << address << setw(20) << phone << endl; } |
Bill Gates:1 Microsoft Way, Redmond, WA:(403) 123-4567 Cranston Snort:1600 Pennsylvania Ave:(306) 678-9876 Albert Einstein:Princeton, NJ:(456) 123-8765 # This is a comment John Smith:123 Elm St.:801-555-1234 |
At one time in industrialized countries, it was a common practice for people to write checks to pay bills and for routine purchases. Checkbooks often included a "register" for people to record the written checks and account deposits. Disciplined people frequently "balanced their checkbook," reconciling their account and maintaining an accurate record of available funds. The checkbook example builds on this (perhaps idealized) situation by simulating a checkbook register and calculating a balance.
Reading and Processing Loop | Data File |
---|---|
double balance = 0; // (a) while (!in.eof()) { string entry; getline(in, entry); if (entry.length() == 0 || entry[0] == '#') continue; istringstream input(entry); string type; getline(input, type, ':'); string date; getline(input, date, ':'); string to; getline(input, to, ':'); double amount; input >> amount; // (b) if (type == "Deposit" || type == "deposit") // (c) balance += amount; else balance -= amount; cout << left << setw(10) << type << setw(10) << date << setw(20) << to << right << setw(10) << fixed << setprecision(2) << amount << endl; } cout << right << setw(50) << fixed << setprecision(2) << balance << endl; // (d) |
deposit:July 7:-:300 416:July 8:Gas Company:15.85 417:7/9:Auto Store:19.95 418:7/10:Grocery Store:47.50 419:Dec 5:Hardware Store:47.89 Deposit:8/19/2006:-:150.00 |
Reading a complete line from a file and validating it before processing can solve some but not all the stated problems, leaving one unresolved. If the line is not empty and not a comment but still doesn't match the expected field and delimiter pattern, it passes the simple validation test but fails the processing operations. Programmers can craft a solution using the find, rfind, andsubstr functions, but it's awkward, error-prone, and very rigid. Even small changes to the file's pattern can render such a solution unusable. This problem requires an even more robust and general solution.
View | Download | Comments |
---|---|---|
s-rolodex2.cpp | s-rolodex2.cpp | A more robust version of the Rolodex program that safely excludes blank lines (including at the end of the file) and comment lines beginning with the '#' character. |
s-rolodex2.txt | s-rolodex2.txt | An input file with data, blank lines, and comments. |
checkbook.cpp | checkbook.cpp | A program reading a "checkbook register" file, printing it in a tabular format and calculating a balance. |
checkbook.txt | checkbook.txt | A checkbook register file. |