C++ Beginner's Guide: Get an Integer from a String Using string stream
During the CS 106L course, I learned a lot of C++ streams. This document describes how to use basic_stringstream
to get an integer from a string.
The basic unit of communication between a program and its environment is a stream. A stream is a channel between a source and destination which allows the source to push formatted data to the destination.
Overview of string stream
To create a string stream, we need to include the <sstream>
header file. Then, we can create a string stream object using the following code:
#include <sstream>
int main() {
std::istringstream iss("9.15 pounds.");
std::ostringstream oss("The price of the shirt is ");
}
For C++ beginners, we usually use std::cin
to get input from the user and use std::cout
to output something to the console. Here, std::cin
and std::cout
are both streams. The following is an example:
#include <iostream>
int main() {
int value;
std::cin >> value;
std::cout << value << std::endl;
}
In the preceding example, >>
is the stream extraction operator, and <<
is the stream insertion operator. For string streams, we also use >>
and <<
to extract and insert data.
To extract the price and unit from the string stream iss
, use the following code:
- Extract price (double) and unit
- Output
#include <iostream>
#include <sstream>
int main() {
std::istringstream iss("9.15 pounds.");
std::ostringstream oss("The price of the shirt is ");
double price;
std::string unit;
iss >> price >> unit;
std::cout << oss.str() << price << " " << unit << std::endl;
}
The price of the shirt is 9.15 pounds.
What is the behavior of iss >> price >> unit
? We can modify the type of price
to int
and see what happens:
- Extract price (int) and unit
- Output
#include <iostream>
#include <sstream>
int main() {
std::istringstream iss("9.15 pounds.");
std::ostringstream oss("The price of the shirt is ");
int price;
std::string unit;
iss >> price >> unit;
std::cout << oss.str() << price << " " << unit << std::endl;
}
The price of the shirt is 9 .15
The output shows that the value of price
is 9
, and the value of unit
is .15
. This is because the >>
operator will stop extracting data when it encounters a whitespace or an invalid character for the type. In this case, the >>
operator in iss >> price
stops extracting data at .
. Then, the >>
operator in iss >> unit
extracts .15
into unit
and stops extracting data at
.
Implement stringToInteger()
without error-checking
Now, we can use >>
to extract an integer from a string. Let's implement a function stringToInteger()
to convert a string to an integer. The code is as follows:
- Extract an integer from a string
- Output
#include <iostream>
#include <sstream>
int stringToInteger(const std::string& str) {
std::istringstream iss(str);
int value;
iss >> value;
return value;
}
int main() {
std::string str = "123";
int value = stringToInteger(str);
std::cout << "The value is: " << value << std::endl;
}
The value is: 123
Stream state
What if the string contains invalid characters? For example, the string "123abc"
contains non-numeric characters. In this case, the >>
operator stops extracting data at a
. Then, the value of value
will be 123
. However, we want to return an error message to the user. To do this, we need to check whether any error occurs during the extraction process.
A new concept is introduced here: stream state. There are four stream states:
- good: no error occurs. The I/O operations are available.
- eof: reaching the end of the stream.
- fail: the input/output operation failed and all future operations frozen, such as the type mismatch.
- bad: irrecoverable stream error. For example, the file you are reading is deleted suddenly.
To check the stream state, we can use the good()
, eof()
, fail()
, and bad()
functions. The following shows some examples:
- Check stream state
- Output
#include <iostream>
#include <sstream>
#include <vector>
void get_stream_state(std::istringstream &iss) {
if (iss.good()) {
std::cout << "G";
}
if (iss.eof()) {
std::cout << "E";
}
if (iss.fail()) {
std::cout << "F";
}
if (iss.bad()) {
std::cout << "B";
}
std::cout << std::endl;
}
int stringToInteger(const std::string &str) {
std::istringstream iss(str);
std::cout << "Before: ";
get_stream_state(iss);
int value;
iss >> value;
std::cout << "After: ";
get_stream_state(iss);
return value;
}
int main() {
std::vector<std::string> test_strings{"123", "123abc", "abc123", ""};
for (const auto &str : test_strings) {
std::cout << "stringToInteger(\"" << str << "\"):\n"
<< stringToInteger(str) << std::endl;
}
}
stringToInteger("123"):
Before: G
After: E
123
stringToInteger("123abc"):
Before: G
After: G
123
stringToInteger("abc123"):
Before: G
After: F
0 # undefined behavior
stringToInteger(""):
Before: G
After: EF
-514166240 # undefined behavior
Implement stringToInteger()
with error-checking
Using the stream state, we can implement stringToInteger()
with error-checking. From the preceding example, to ensure that the string only contains numeric characters, we need to consider the following cases:
- After the extraction, the stream state is
good
: the string might contain non-numeric characters after the number. - After the extraction, the stream state is
fail
: the string contains non-numeric characters before the number.
The following code shows how to implement stringToInteger()
with error-checking:
#include <iostream>
#include <sstream>
int stringToInteger(const std::string &str) {
std::istringstream iss(str);
int result;
char remain; // Record remaining characters after the integer
iss >> result;
if (iss.fail() || iss.bad()) {
throw std::domain_error("The string does not start with an integer.");
}
iss >> remain;
if (!iss.fail()) {
throw std::domain_error(
"The string contains extra characters after the integer.");
}
return result;
}
int main() {
std::string str = "123";
int value = stringToInteger(str);
std::cout << "The value is: " << value << std::endl;
}
In the preceding code, the program first tries to extract an integer from the string. If the stream state is fail
or bad
, it throws an error. Otherwise, it tries to extract a character from the remaining string. If the stream state is not fail
, it means that the string contains extra characters after the integer. Then, the program throws an error.
Since if ((iss >> result).fail())
is equivalent to if (!(iss >> result))
, we can simplify the code as follows:
#include <iostream>
#include <sstream>
int stringToInteger(const std::string &str) {
std::istringstream iss(str);
int result;
char remain; // Record remaining characters after the integer
if (!(iss >> result) || iss >> remain) {
throw std::domain_error(
"Failed to get integer from string. Please check the string.");
}
return result;
}
int main() {
std::string str = "123";
int value = stringToInteger(str);
std::cout << "The value is: " << value << std::endl;
}