2

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)?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
jkeys
  • 3,803
  • 11
  • 39
  • 63

5 Answers5

6

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
  • 6,382
  • 2
  • 31
  • 42
  • 1
    or use the += operator instead. – Tom May 03 '09 at 00:45
  • Is .append() like .push_back() for vectors? What arguments does it take? I'd like to know why my code works. – jkeys May 03 '09 at 00:51
  • Oops, this got left out: strings are not like raw arrays? Address 0 is not equal to the first block of memory? – jkeys May 03 '09 at 00:52
  • 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 May 03 '09 at 00:57
  • So why doesn't newstring.reserve(fstring.size()) work (which it doesn't)? – jkeys May 03 '09 at 01:03
  • 2
    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 May 03 '09 at 01:26
  • 3
    When you reserve, you internally say "make sure I can hold this much", when you *resize*, you actually say "hold this much" – GManNickG May 03 '09 at 05:36
  • 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? – jkeys May 03 '09 at 06:10
5

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
  • 39,555
  • 5
  • 63
  • 78
  • 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 May 03 '09 at 12:00
2

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
  • 273,490
  • 39
  • 460
  • 699
1

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
  • 1,596
  • 1
  • 12
  • 11
  • 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 May 03 '09 at 11:12
  • Ah, I only needed the cast because I was lazily using namespace std. Serves me right. – Steve Jessop May 03 '09 at 12:12
  • 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 May 03 '09 at 13:08
  • so i think a better approach would be to use the function std::isdigit, which is a template and won't suffer from that problem: !boost::bind(&std::isdigit, _1, std::locale("")) – Johannes Schaub - litb May 03 '09 at 13:20
  • Your're right, it should be removing non-digit not digit. I was thinking about the C function int isdigit(int) in , maybe i should have written &::isdigit. In any case, using the template std::isdigit is better and safer. – Shing Yip May 03 '09 at 16:39
0
  • 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;
rlbond
  • 65,341
  • 56
  • 178
  • 228
Tom
  • 43,810
  • 29
  • 138
  • 169