-
Notifications
You must be signed in to change notification settings - Fork 2
strtol() loop example
This code will loop through a string, parsing each integer until it runs into a value that can't be parsed into an integer:
std::vector<int> parse_all_ints(const char* str)
{
std::vector<int> ints;
char* end = nullptr;
for (int value = 0; (value = strtol(str, &end, 10)) && (str != end); str = end)
{
ints.push_back(value);
}
return ints;
}
int main()
{
const char* str = " 123 4 \t\n 26 nope 55";
std::cout << "Parsing string '" << str << "'.\n";
std::vector<int> ints = parse_all_ints(str);
std::cout << "Found " << ints.size() << " ints:\n";
for (int i : ints)
{
std::cout << i << " ";
}
return 0;
}Output:
Parsing string ' 123 4
26 nope 55'.
Found 3 ints:
123 4 26
long strtol(const char* str, char** end, int radix)
strtol() takes as input:
- The string to parse,
str - The pointer to write the
endvalue to - The base to use to parse the integers (this is usually 10 but may sometimes be 16 or even 8 on occasion)
And it returns the parsed integer. It returns 0 if it successfully parses the integer 0 or if it failed to parse. To distinguish between these two cases, it is important to compare str and end -- if str == end, there was no integer to parse.
If we want to use strtol() to parse integers until there are no integers left, we would like to write a loop that stops when str and end point to the same place. Here's a straightforward version of that loop:
const char* str = " 123 4 \t\n 26 nope 55";
const char* start = str;
char* end = nullptr;
std::vector<int> parsed_ints;
int value = strtol(start, &end, 10);
while (str != end)
{
parsed_ints.push_back(value);
start = end;
value = strtol(start, &end, 10);
}But we can do a little better -- first of all, we have value = strtol(...) written twice, which isn't ideal. One way of cleaning loops like this up is to take advantage of a helpful C and C++ fact -- assignment statements return the value assigned.
Here's an example of that in action:
int x = 200;
std::cout << "x: " << x << "\n"; // prints "x: 200"
int y = (x = 300);
std::cout << "x: " << x << ", y: " << y << std::endl; // prints "x: 300, y: 300"The statement x = 300 evaluated to the value x was set to, which in this case was 300. y was then set to that value, making y 300 as well.
As such, it's perfectly valid to write a test like this:
int x = 200;
if (x = 0) // not a typo -- "x = 0" evaluates as "0"
{
std::cout << "This will never execute, because x = 0 evaluates to 0";
}
std::cout << "x now has a value of 0 in all cases";While it's not unusual to put function calls in conditionals...
if (strtol(start, &end, 10))
{
std::cout << "This will execute if the integer parsed is not equal to 0";
}... sometimes we want to use the value returned from the function call, too:
int value;
if (value = strtol(start, &end, 10))
{
std::cout << "Parsed non-zero integer whose value is " << value;
}To make it clear that we meant to do this -- i.e., that we didn't mean to write == but accidentally wrote = instead, it's typical to wrap these clauses in parentheses:
int value;
if ((value = strtol(start, &end, 10)))
{
std::cout << "Parsed non-zero integer whose value is " << value;
}Finally, let's note that strtol() will return 0 if it parsed 0 or if it failed, and will have start == end if it failed. So, if strtol() failed, it will return 0 and start == end:
int value;
if ((value = strtol(start, &end, 10)) && start != end)
{
std::cout << "Successfully parsed integer, which may be 0, and whose value is " << value;
}Putting all this together, we can remove our repeated line from our loop:
int value;
while ((value = strtol(start, &end, 10)) && str != end)
{
parsed_ints.push_back(value);
start = end;
}But you may notice something else -- this is a loop that initializes a variable and that does something at the end of each loop. In other words, it can be naturally rewritten as a for loop:
for (int value = 0; (value = strtol(str, &end, 10)) && (str != end); str = end)
{
ints.push_back(value);
}This has the additional benefit of keeping the int value declaration within the scope of the for loop. However, this is a real doozy of a for loop -- it might be best to keep it in a while loop for clarity.
Our whole loop now looks like this:
const char* str = " 123 4 \t\n 26 nope 55";
const char* start = str;
char* end = nullptr;
std::vector<int> parsed_ints;
for (int value = 0; (value = strtol(str, &end, 10)) && (str != end); str = end)
{
ints.push_back(value);
}We can avoid introducing the start named variable by wrapping all of this in a function:
std::vector<int> parse_all_ints(const char* str)
{
std::vector<int> ints;
char* end = nullptr;
for (int value = 0; (value = strtol(str, &end, 10)) && (str != end); str = end)
{
ints.push_back(value);
}
return ints;
}Bringing it all together, we end up with the code shown at the top of the page:
std::vector<int> parse_all_ints(const char* str)
{
std::vector<int> ints;
char* end = nullptr;
for (int value = 0; (value = strtol(str, &end, 10)) && (str != end); str = end)
{
ints.push_back(value);
}
return ints;
}
int main()
{
const char* str = " 123 4 \t\n 26 nope 55";
std::cout << "Parsing string '" << str << "'.\n";
std::vector<int> ints = parse_all_ints(str);
std::cout << "Found " << ints.size() << " ints:\n";
for (int i : ints)
{
std::cout << i << " ";
}
return 0;
}Output:
Parsing string ' 123 4
26 nope 55'.
Found 3 ints:
123 4 26