What is the proper way of reading a composite key and a numeric value from an input file?

Viewed 60

I'm trying to read a text file consisting of numerous strings which either represent a key/value (the key is a car number in a format of a letter/' '/3digits/' '/2letters; the value is unsigned long long; \t or ' ' between them) or an empty line, e.g.:

empty line
empty line
Z 999 ZZ 80
A 000 AA 124
Z 666 ZZ 42

I am using a cin.getline() function for that, reading a whole line and going through every character, saving a key and a value into an 'element' variable and pushing it into a vector afterwards. But for some reason the program seems to work unexpectedly, giving a weird output:

0
0
Z 999 ZZP 80
A 000 AA| 124
Z 666 ZZ* 42

So far I have been trying to analyse what could go wrong but I just can't see it. I've also tried using other tools like scanf() or cin.get() but failed miserably. Can someone please explain to me why this is happening and maybe show a more correct way of solving this task? Here is the code:

struct kv {
    char key[8];
    unsigned long long val;
};

int main()
{
    std::vector<kv> data_vector;
    kv element;

    char str[64] = {};
    char num[32] = {};
    while (std::cin.getline(str, 64)) {
        if (str[0] == ' ' || str[0] == '\n' || str[0] == '\t' || str[0] == EOF) {
            continue;
        }
        size_t i = 0, n = 0;
        for (i = 0; i < 8; i++)
            element.key[i] = str[i];
        while (!(str[i] >= '0' && str[i] <= '9'))
            i++;
        while (str[i] >= '0' && str[i] <= '9')
            num[n++] = str[i++];
        element.val = atoi(num);
        data_vector.push_back(element);
        for (n = 0; n < 32; n++)
            num[n] = 0;
        for (i = 0; i < 64; i++)
            str[i] = 0;
    }
    for (size_t i = 0; i < data_vector.size(); i++) {
        std::cout << data_vector[i].key << "\t" << data_vector[i].val << std::endl;
    }
    return 0;
}

EDIT: as @JimRhodes pointed out, changing char key[8] to char key[9] and adding element.key[8] = '\0' helped, but empty lines are still being processed the wrong way (as they should be ignored), giving an output of 0.

1 Answers

I think you may not be understanding how std::cin.getline() works. First of all, you do not want to test the return value of std::cin.getline() for true or false. You need to check for eof or fail. Secondly, std::cin.getline() discards the newline character so there is no need to check for '\n'. Your loop could start like this:

for ( ; ; )
{
    str[0] = '\0'; // Clear any previous data
    std::cin.getline(str, 64);
    if ( std::cin.eof() )
    {
        break; // No more data, exit loop
    }
    if ( std::cin.fail() || (str[0] < 'A') )
    {
        continue; // Empty line or line does not start with a letter
    }
    . . .
Related