IFSTREAM: take coordinate from file and convert 3 digit coordinate into integer

Started by
3 comments, last by Bregma 7 years, 7 months ago

Update: I got the program to read the file and I did this 2-3 days after my last post to this thread. I've been offline. One "gotcha" was that I wanted to use "istringstream" but it seems that the correct thing to use was "stringstream". I can now work with my data.

So I've got the output working. My map editor can create a text file that looks like this:


100 x 300 [1]
400 x 50 [2]
450 x 50 [2]

Each object has its x & y placed in the file, along with its 'type' - at this stage, either a grass texture or a wall.

The problem is that I can't pass a string to the class constructor because it only takes int. Also, I can't change x and y to be string because then I'd never be able to perform calculations on it. Currently I've tried using c_str() and I read in each character, one at a time. After that there's a problem, because I can't have an array that stores '1' '0' '0' because that would be 3 integers, and I don't think integers can be concatenated to get '100'.

I tried using atoi but there's something not right about it:


int value = 0;
std::string bobb = "2309";
value = atoi( bobb );
std::cout( "Function: atoi( \"%s\" ) = %d\n", bobb, value );

and I get:


||=== Build: Debug in mappy (compiler: GNU GCC Compiler) ===|
C:\Users\jeff\Documents\mappy\main.cpp||In function 'int SDL_main(int, char**)':|
C:\Users\jeff\Documents\mappy\main.cpp|62|error: cannot convert 'std::string {aka std::basic_string<char>}' to 'const char*' for argument '1' to 'int atoi(const char*)'|
C:\Users\jeff\Documents\mappy\main.cpp|63|error: no match for call to '(std::ostream {aka std::basic_ostream<char>}) (const char [29], std::string&, int&)'|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

What's the deal with that? I thought c++ could convert a string really easily. Do I need to use pointers, and go indirectly?

Logging in from unsecured wireless, I hope no-one steals my password (October 22, 2016)
Advertisement
You’re almost there. std::string::c_str returns the actual contents of the string as a const char*; there’s no need to atoi every character in the array:

std::string input = "123";
int expected = 123;
int value = atoi(input.c_str());

assert( value == expected );

After that there's a problem, because I can't have an array that stores '1' '0' '0' because that would be 3 integers, and I don't think integers can be concatenated to get '100'.


Actually, you can using powers of ten: 123 == 1*100 + 2*10 + 3.
(N.B.: This is how we convert from hex to dec: 0x3E8 == 3*16^2 + 15*16^1 + 8*16^0 == 1000)
However, ASCII '0' has a different value from the literal zero, so we’ll need to “slide the ASCII chart” to bring '0' to a literal zero, like so:

char input[] = "123";
int expected = 123;
int value =
     (input[0]-'0') * 100 +
     (input[1]-'0') *  10 +
     (input[2]-'0') *   1;

assert( expected == value );
This works because the char type is an integer type and can be operating like regular integers. We could write the actual ASCII value of '0' (48) in place of '0' in the test above, but with '0' written out, it is more clear.


While there’s nothing wrong with using atoi, there are more C++-y ways to do parsing using streams:
std::string input = "123";
int expected = 123;
int value = 0;

std::istringstream stream(input);
stream >> value;

assert( expected == value );
And extending on the above, you could read your entire file:
int x;
int y;
int type;

// buffer to hold the non-integer tokens, used to validate that 
// the line is in the correct format
char dummy[4] = {};

string line;
ifstream file;
while ( getline(file, line) ) {
    istringstream stream(line);

    // line := x 'x' y '[' type ']'
    stream >> x >> dummy[0] >> y >> dummy[1] >> type >> dummy[2];

    if ( !stream ) {
        // not enough tokens, or a problem converting some of the integers
        continue;
    }

    if ( strcmp(dummy, "x[]") ) {
        // expected "100 x 200 [300]", but got something
        // like     "100 A 200 ?300~" instead
        continue;
    }

    // x, y, and type have been extracted from the line
    objects.push_back({x, y, type});
}

What's the deal with that? I thought c++ could convert a string really easily. Do I need to use pointers, and go indirectly?

The compiler told you what the deal was: :wink:


cannot convert 'std::string {aka std::basic_string<char>}' to 'const char*' for argument '1' to 'int atoi(const char*)'|

Error messages are sometimes worded pretty stupidly, so you have to get the hang of reading them.

Basically, the message is saying this:


For the 1st argument of 'int atoi(const char*), we cannot convert 'std::string' to the required 'const char*'.

atoi() expects a C-style string (const char*), not a std::string. calling c_str() on your std::string, will return access to it as a C-style string.

Or you could use std::stoi() instead, which takes a std::string directly. Read atoi as 'char-array to int'. Read stoi as 'string to int'.

Note: std::stoi() is part of C++11 and later, so you'll need to have that enabled on your compiler, if it's not enabled by default.

fastcall22: Although I've made progress, there's some things left to fix. When I try std::istringstream stream(line); it gives an error...

C:\Users\Jeff\Documents\project\main.cpp|60|error: variable 'std::istringstream stream' has initializer but incomplete type
c++ reference defines istringstream like so: typedef basic_istringstream<char> istringstream;
Shouldn't 'char' go in before std::istringstream? I'm defining a stream to receive const char, amirite?
-------
servant: When I invoke stoi it says it doesn't exist. I tried std::stoi and mingw won't recognise it. On the c++ reference website, it says that stoi is a member of std. Also, I definitely have c++11 enabled, not only do I see the checkbox ticked, but I'm using a ranged-for-loop which is a c++11 feature. This is pretty embarrassing :wacko:
-------
The good news on this program is that I'm reading text from the file. I might have "50 x 50 [1]" in the file, but when I read it with c_str() it comes out as "50 50 50 50" so obviously I'm getting the first const char, before the whitespace, and it gets the same piece each time. I'm not sure if I want to receive a const char or an entire line; It could be better to go Linus Torvalds style, and check each character on its own... in which case, back to c libraries? "stdlib.h" instead of "cstdlib.h"?
Logging in from unsecured wireless, I hope no-one steals my password (October 22, 2016)

C:\Users\Jeff\Documents\project\main.cpp|60|error: variable 'std::istringstream stream' has initializer but incomplete type

That error message usually means you forgot to include the appropriate library header before using the class.


#include <sstream>

int main()
{
    std::istringstream istr("100 x 300 [1]");

    int x;
    istr >> x;
    // etc....
}

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement