views:

914

answers:

5

There was a passing comment in a book of mine about people inputting commas into integers and messing up your program, but it didn't elaborate. That got me thinking, so I tried writing a little algorithm to take an std::string and remove all non-integer characters. This code compiles but skips over output. Why isn't anything being assigned to newstring? Does if(isdigit(fstring[i])) evaluate to true of the address it's pointing to hold a digit?

//little algorithm to take the non-integers out of a string
//no idea on efficiency

#include <iostream>
#include <string>

int main()
{
    std::cout << "Enter a number with non-integer characters: ";

    std::string fstring; 
    getline(std::cin, fstring);

    std::string newstring;

    int i = 0, x = 0;

    while (i != fstring.length())
    {
        if (isdigit(fstring[i]))
        {
            newstring[x] = fstring[i];
            i++;
            x++;
        }
        else
        {
           i++;
        }
    }

    std::cout << std::endl;
    std::cout << newstring;
    system("PAUSE");
}

Secondary question, that perhaps belongs elsewhere: how do you convert a string to an int (or a floating point number)?

+6  A: 

newstring is of length 0, so newstring[x] where x=0 is actually illegal. You should append to the string using: newstring.append(1, fstring[i])

For the secondary question, look for atoi(), atof(), strtol(0, strtof() functions.

thesamet
or use the += operator instead.
Tom
Is .append() like .push_back() for vectors? What arguments does it take? I'd like to know why my code works.
Hooked
Oops, this got left out: strings are not like raw arrays? Address 0 is not equal to the first block of memory?
Hooked
Strings are a bit like raw arrays, but you either have to preallocate enough space for the resulting string, or use the append or += method
1800 INFORMATION
So why doesn't newstring.reserve(fstring.size()) work (which it doesn't)?
Hooked
Because reserve, just like reserve on a vector, doesn't change a string's size, it changes it's capacity. When you output, the length of the string is still zero, even though it's allocated enough room to hold more data.
Logan Capaldo
When you reserve, you internally say "make sure I can hold this much", when you *resize*, you actually say "hold this much"
GMan
Oh, 1+ thanks for the clarification GMan. Can you pass an argument as to what you want in each memory address? For instance, if I wanted to set all of them to 0 before I start appending values (which seems like a good idea), how would I do that?
Hooked
A: 
  • string to floating point number:

You need to #include <cstdlib>

float strtof(const char *nptr, char **endptr);

For example:

 float f = strtof("3.4",NULL);
  • string to integer

You need to #include <cstdlib>

int atoi(const char *numPtr);

Notice that these are C - functions, not C++, so you need to use the c_str() method on std::string to get the C-string.

const char* c_str ( ) const;
Tom
+5  A: 

Strings are like arrays, but the default constructor for a string creates an empty string. Why should it allocate more memory than it needs? Even if it does, there's no saying how much, or if it will be big enough for a filtered copy of fstring. I'm impressed it doesn't crash.

A simple modification would be to change:

std::string newstring;

to:

 std::string newstring(fstring.length(), '\0')

And after the loop add:

 newstring.resize(x);

This will ensure that newstring will have at least enough room (probably more) during the filtration and will be trimmed down to the appropriate size when you are finished filtering. You may also be interested in the std::remove_copy_if function in <algorithm>.

E.g.

struct isnotdigit { bool operator()(char c) { return !isdigit(c); } };

std::string newstring(fstring.length(), '\0');
std::string::iterator i = std::remove_copy_if(fstring.begin(), 
  fstring.end(), newstring.begin(), isnotdigit());
newstring.erase(i, newstring.end());

As for converting a string to an integer / float, in addition to atoi, strtol, atof, strtof, etc. functions already mentioned you can also make using of the iostream library:

 #include <sstream>
 std::string integer("23");
 std::istringstream iss(integer);
 int result;
 iss >> result;

 std::string floatingpoint("3.14");
 std::istringstream iss2(floatingpoint);
 double result2;
 iss2 >> result2;

Also, if you are familiar with the printf family of functions you may be interested in scanf, sscanf

 const char *s = "23";
 int result;
 sscanf(s, "%d", &result);
Logan Capaldo
+1 for remove_copy_if()
Nick Presta
I think remove_copy_if has been used wrongly, though: doesn't this code remove all the digits, instead of removing all the non-digits?
Steve Jessop
Right you are onebyone
Logan Capaldo
+1  A: 

To remove digits:

fstring.erase(
      std::remove_if(fstring.begin(), fstring.end(), &isdigit), 
      fstring.end());

To convert string to int/float/...:

int n1 = boost::lexical_cast<int>("123");
float n2 = boost::lexical_cast<float>("123.456");
Shing Yip
Question was to remove non-digits, so maybe a bit of not1(ptr_fun(isdigit)) is in order. Plus I have to explicitly cast isdigit to int(*)(int) to get it to work with algorithms, otherwise it's "unknown type".
Steve Jessop
Ah, I only needed the cast because I was lazily using namespace std. Serves me right.
Steve Jessop
know that isdigit has some dangerous pitfalls. you should make sure that your string only contains characters up to CHAR_MAX (not ones that go beyond/more closer to UCHAR_MAX, like the euro sign or so). Otherwise you could end up passing negative numbers to isdigit if char is signed. impls usually use that int to index an array to fig out the character class. so negative integers are evil then.
Johannes Schaub - litb
Johannes Schaub - litb
Shing Yip
+1  A: 

Expansion on Shing Yip's answer:

To remove non-digits:

#include <iostream>
#include <functional>
#include <string>
#include <algorithm>

using namespace std;

int main() {
    string fstring;
    getline(cin, fstring);
    fstring.erase(
        remove_if(fstring.begin(), fstring.end(),
            not1(ptr_fun(static_cast<int(*)(int)>(isdigit)))
        ),
        fstring.end()
    );

    cout << fstring << "\n";
}

I'm not sure why that static_cast is needed, though. I think something's ambiguous about isdigit without it. [Edit: If you don't do "using namespace std" then you don't need it, so it's my fault for being lazy writing example code.]

It's debatable whether this is any simpler than rolling your own loop:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string fstring, ins;
    getline(cin, ins);
    for (string::iterator it = ins.begin(); it != ins.end(); ++it) {
        if (isdigit(*it)) fstring.push_back(*it);
    }
    cout << fstring << "\n";
}

And C++0x will have copy_if, which was left out basically by accident, and is trivial to implement:

#include <iostream>
#include <string>
#include <algorithm>
#include <iterator>

int main() {
    std::string fstring, ins;
    std::getline(std::cin, ins);
    std::copy_if(ins.begin(), ins.end(), 
        std::back_inserter(fstring), isdigit);
    std::cout << fstring << "\n";
}

To convert to int (or float):

int i = boost::lexical_cast<int>(fstring);

Or if you don't have boost:

#include <sstream>

int i = 0;
std::stringstream(fstring) >> i;

Note that you do have to initialize i, otherwise it won't be set if fstring is empty.

Steve Jessop