Search code examples
c++structfstreamc-strings

Nested Structures, reading data from a file separated by a blank line


I am currently working on an accounting program for my project. I am struggling with reading from a file into nested structures. Any guidance on where I should go with this? I know I want it to stop reading into account data and move on to the next customer when I reach a blank line (null terminator). Basically, some customers have a couple of accounts, others have more than 2 (but no more than 5, struct array only holds 5). The project itself is much more in-depth (structures contain more variables). I am currently just using a practice file to try and figure out the concept..

Here is my code so far:

 struct ACCOUNT
 {
 char acct_num[7];
 char balance[8];
 };

 struct CUSTOMER
{ 
char cust_name[20];
char number[5];
ACCOUNT acct[5];
};

int main()
{
  CUSTOMER person[3];
  fstream fin;
  fin.open("accounts.dat", ios::in);
  int i, j;
  i = 0, j = 0;
  char buff[20];
  char line[20];
  while (fin.getline(buff, 20))
    {
    strncpys(person[i].cust_name, buff, 10);
    fin.getline(line, 10);
    strncpy(person[i].number, line, 10);


    do {
        fin.getline(line, 20, ' ');
        strncpy(person[i].acct[j].acct_num, line, 10);
        fin.getline(line, 20);
        strncpy(person[i].acct[j].balance, line, 10);
        j++;
        cin.getline(line, 20);

    } while (*line != 0);
    i++;
}
return 0;
}

Data file I am trying to read into:

Jane Smith
FD12
SSDFSS 64.51
SD5545 88.51

John Smith
FD45
SFG789 77.21
NM4521 21.223
MM7888 33.33

John Doe
FSS4
SFGSGG 77.65
HN5555 22.31

Solution

  • Problems I noticed:

    Problem 1

    strncpys(person[i].cust_name, buff, 10);
    

    I assume you meant to use strncpy, not strncpys. Even then, 10 is too small. You needed to use 20.

    Problem 2

    strncpy(person[i].number, line, 10);
    

    Here, 10 is too large. You need to use 5.

    Problem 3

    j needs to be reinitialized to 0 inside the outer loop.

    Problem 4

    The logic for checking for empty lines in the inner while loop is flawed. You have used

        cin.getline(line, 20);
    

    as the last line in the loop but I assume that's error in posting to SO. I think you have

        fin.getline(line, 20);
    

    That is a problem since you consume the next line from the file but the data contained in it just tossed.


    However, the more important suggestions I have are:

    1. Re-think the strategy for parsing the input file. I find it easiest to read the contents of a file line by line and then process each line separately.

    2. Create smaller functions to read different pieces of the input and use them from main.

    3. Write to stdout what you read so you can clearly see where the data was not read like you hoped it would.

    I would suggest the following update to main.

    int main()
    {
       CUSTOMER person[3];
       fstream fin;
       fin.open("socc.in", ios::in);
       int i = 0;
    
       std::string line;
       while ( getline(fin, line) )
       {
          read_customer_name(line, person[i]);
          std::cout << "cust_name: " << person[i].cust_name << std::endl;
    
          if ( getline(fin, line) )
          {
             read_customer_number(line, person[i]);
             std::cout << "number: " << person[i].number << std::endl;
          }
          else
          {
             // Read was not successful.
             // Break the loop.
             break;
          }
    
          // Define it in this scope only.
          int j = 0;
          while ( getline(fin, line) )
          {
             if ( line.empty() )
             {
                break;
             }
    
             read_customer_account(line, person[i].acct[j]);
             std::cout << "acct[j].acct_num: " << person[i].acct[j].acct_num << std::endl;
             std::cout << "acct[j].balance: " << person[i].acct[j].balance << std::endl;
    
             j++;
          }
       }
       return 0;
    }
    

    where the helper functions are:

    void read_customer_name(std::string const& line, CUSTOMER& person)
    {
       strncpy(person.cust_name, line.c_str(), 20);
    }
    
    void read_customer_number(std::string const& line, CUSTOMER& person)
    {
       strncpy(person.number, line.c_str(), 5);
    }
    
    void read_customer_account(std::string const& line, ACCOUNT& acct)
    {
       std::istringstream str(line);
       str.getline(acct.acct_num, 10, ' ');
       str.getline(acct.balance, 10);
    }