Reputation: 1140
I'm writing C++ code for school in which I can only use the std library, so no boost. I need to parse a string like "14:30" and parse it into:
unsigned char hour;
unsigned char min;
We get the string as a c++ string, so no direct pointer. I tried all variations on this code:
sscanf(hour.c_str(), "%hhd[:]%hhd", &hours, &mins);
but I keep getting wrong data. What am I doing wrong.
Upvotes: 4
Views: 6148
Reputation: 110778
sscanf
with %hhd:%hhd
seems to work perfectly fine:
std::string time("14:30");
unsigned char hour, min;
sscanf(time.c_str(), "%hhd:%hhd", &hour, &min);
Note that the hh
length modifier is simply to allow storing the value in an unsigned char.
However, sscanf
is from the C Standard Library and there are better C++ ways to do this. A C++11 way to do this is using stoi
:
std::string time("14:30");
unsigned char hour = std::stoi(time);
unsigned char min = std::stoi(time.substr(3));
In C++03, we can use stringstream
instead but it's a bit of a pain if you really want it in a char
:
std::stringstream stream("14:30");
unsigned int hour, min;
stream >> hour;
stream.ignore();
stream >> min;
Upvotes: 0
Reputation: 154047
Your problem is, of course, that you are using sscanf
. And that
you're using some very special type for the hours and minutes, instead
of int
. Since you're parsing a string of exactly 5 characters, the
simplest solution is just to ensure that all of the characters are legal
in that position, using isdigit
for characters 0, 1, 3 and 4, and
comparing to ':'
for character 2. Once you've done that, it's trivial
to create an std::istringstream
from the string, and input into an
int
, a char
(which you'll ignore afterwards) and a second int
. If
you want to be more flexible in the input, for example allowing things
like "9:45"
as well, you can skip the initial checks, and just input
into int
, char
and int
, then check that the char
contains ':'
(and that the two int
are in range).
As to why your sscanf
is failing: you're asking it to match something
like "12[:]34"
, which is not what you're giving it. I'm not sure
whether you're trying to use "%hhd:%hhd"
, or if for some reason you
really do want a character class, in which case, you have to use [
as
a conversion specifier, and then ignore the input: "%hhd%*[:]%hhd"
.
(This would allow accepting more than one character as the separator,
but otherwise, I don't see the advantage. Also, technically at least,
using %d
and then passing the address of an unsigned
integral types
is not supported, %hhd
must be a signed char
. In practice,
however, I don't think you'll ever run into any problems for
non-negative input values less than 128.)
Upvotes: 2
Reputation: 8027
I would do it like this
unsigned tmp_hour, tmp_mins;
unsigned char hour, mins;
sscanf(hour.c_str(), "%u:%u", &tmp_hours, &tmp_mins);
hour = tmp_hours;
mins = tmp_mins;
Less messing around with obscure scanf
options. I would add some error checking too.
Upvotes: 1
Reputation:
As everyone else has mentioned, you have to use %d
format specified (or %u
). As for the alternative approaches, I am not a big fan of the "because C++ has feature XX it must be used" and oftentimes resort to C-level functions. Though I never use scanf()
-like stuff as it got its own problems. That being said, here is how I would parse your string using strtol()
with error checking:
#include <cstdio>
#include <cstdlib>
int main()
{
unsigned char hour;
unsigned char min;
const char data[] = "12:30";
char *ep;
hour = (unsigned char)strtol(data, &ep, 10);
if (!ep || *ep != ':') {
fprintf(stderr, "cannot parse hour: '%s' - wrong format\n", data);
return EXIT_FAILURE;
}
min = (unsigned char)strtol(ep+1, &ep, 10);
if (!ep || *ep != '\0') {
fprintf(stderr, "cannot parse minutes: '%s' - wrong format\n", data);
return EXIT_FAILURE;
}
printf("Hours: %u, Minutes: %u\n", hour, min);
}
Hope it helps.
Upvotes: 2
Reputation: 13412
As mentioned by izomorphius sscanf
and variants are not C++ they are C. The C++ way would be to use streams. The following works (it's not amazingly flexible but should give you an idea)
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
int main(int argc, char* argv[])
{
string str = "14:30";
stringstream sstrm;
int hour,min;
sstrm << str;
sstrm >> hour;
sstrm.get(); // get colon
sstrm >> min;
cout << hour << endl;
cout << min << endl;
return 0;
}
You could also use getline
to get everything upto the colon.
Upvotes: 1
Reputation: 57749
My understanding is that h
in %hhd
is not a valid format specifier. The correct specifier for decimal integers is %d
.
As R.Martinho Fernandes says in his comment, %d:%d
will match two numbers separated by a colon (':').
Did you want something different?
You can always read the entire text string and parse it any way you want.
Upvotes: 0