0

I'm working on a project where I need to create a Linked List from a given data file. It contains operation codes, first names, last names, addresses, cities, states, and zip codes. The problem is that there is an unknown number of spaces on the lines that contain an address and the city. An example of the data file is as follows:

A
Donald
Duck
1123 Appleberry Circle
Saint Cloud
MN
88084
A
Barry
Briches
112 New York Ave
Saint Cottleville
FL
78098

I've tried using a combination of:

infile >> FirstName >> LastName;
infile.getline(Address, 20);
infile.getline(City, 20);
infile >> State >> ZipCode;

but when I go to print it out, the output comes out really weird. Any help on the issue would be greatly appreciated. All code I have done is attached.

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string.h>
#include "list.h"

using namespace std;

int main()
{
    ifstream infile("data2.txt", ios::in);
    ofstream outfile("output2.txt", ios::out);

    char opCode, fname[12], lname[12], address[20], city[12], state[4], 
    zip[6];

    MailListClass mailingList;

    infile >> ws >> opCode;

    while (opCode != 'Q')
    {
        switch (opCode)
        {
            case 'A' :  infile >> fname >> lname;
                        infile.getline(address, 20, '\n');
                        infile >> city >> state >> zip;
                        mailingList.addRecord(fname, lname, address, city, 
                        state, zip, outfile);
                        break;
            case 'P' :  mailingList.printRecord(outfile);
                        break;
        }
        infile >> ws >> opCode;
    }
    return 0;
}

The .h and .cpp struct/class files:

#ifndef LIST_H
#define LIST_H

#include <fstream>

using namespace std;

struct node
{
    char lname[12], fname[12], city[12], address[20], state[4], zip[6];
    node *next;
};

class MailListClass
{
    private:
        node *headPtr, *currPtr, *prevPtr;
    public:
        MailListClass();
        void addRecord(char fname[12], char lname[12], char address[20],
                            char city [12], char state[4], char zip[6],
                            ofstream &outfile);
        void printRecord(ofstream &outfile);
};

#endif 

list.cpp

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>

#include "list.h"

using namespace std;

MailListClass::MailListClass()
{
    headPtr = NULL;
    currPtr = NULL;
    prevPtr = NULL;
}

void MailListClass::addRecord(char fname[12], char lname[12], char address[20], char city[12],
                            char state[4], char zip[6], ofstream &outfile)
{
    node *tail = new node;
    tail->next = NULL;
    strcpy(tail->fname, fname);
    strcpy(tail->lname, lname);
    strcpy(tail->address, address);
    strcpy(tail->city, city);
    strcpy(tail->state, state);
    strcpy(tail->zip, zip);

    if (headPtr != NULL)
    {
        currPtr = headPtr;
        while (currPtr->next != NULL)
        {
            currPtr = currPtr->next;
        }
        currPtr->next = tail;
    }
    else
    {
        headPtr = tail;
    }
} 
void MailListClass::printRecord(ofstream &outfile)
{
    currPtr = headPtr;
    while (currPtr != NULL)
    {
        outfile << left << setw(12) << currPtr->lname;
        outfile << setw(12) << currPtr->fname;
        outfile << setw(20) << currPtr->address;
        outfile << setw(12) << currPtr->city;
        outfile << setw(6) << currPtr->state;
        outfile << setw(10) << currPtr->zip << endl;
        currPtr = currPtr->next;
    }
}

The outfile should looks like this:

Last Name    First Name    Address              City   State  Zip
Last Name    First Name    Address              City   State  Zip

but with the getline() in my code, my output looks closer to this:

Last Name    First Name
                  Address#        AddressNameCityCity
Last Name    First Name
                  Address#        AddressName
jpat94
  • 1
  • Welcome to Stack Overflow. Please, take the [tour] for a short introduction to this site. – Scheff's Cat Feb 18 '19 at 07:04
  • Why not use `std::stringstream`? I think this would be a perfect case for this example. You can then just parse through the file based on a new line rather than the approach you are taking. – Sailanarmo Feb 18 '19 at 07:11

1 Answers1

0

I'm afraid that questions like this have asked very often. However, here my answer:

I believe, this is the mis-concept:

infile >> FirstName >> LastName;
infile.getline(Address, 20);

It reads FirstName and LastName but the question is where are the line-ends read?

A simplified sample to illustrate this:

#include <iostream>
#include <string>

int main()
{
  std::string name1, name2;
  std::cin >> name1 >> name2;
  std::cout << "name1: '" << name1 << "', name2: '" << name2 << "'\n";
  for (std::string line; std::getline(std::cin, line);) {
    std::cout << "next: '" << line << "'\n";
  }
  return 0;
}

Tested on coliru with

Donald
Duck
1123 Appleberry Circle

given as input (by echo -e):

$ g++ -std=c++17 main.cpp && echo -e "Donald\nDuck\n1123 Appleberry Circle\n" | ./a.out
name1: 'Donald', name2: 'Duck'
next: ''
next: '1123 Appleberry Circle'
next: ''

Live Demo on coliru

The line read and printed after Duck is empty. This is because the operator<<(std::istream&, std::string&) doesn't read the white space (e.g. space, tab, line-end) delimiting the text. Hence, the line-ends after Donald and Duck are left in input buffer. After first string input (Donald >> name1), it doesn't hurt. The next input (Duck >> name2) consumes/skips white spaces until a non-whitespace follows. After second input, the line-end stays in input buffer until the next call of std::getline() consumes it.

There are various ways to fix this:

  1. Read everything with std::getline() (and, may be, use std::istringstream with input operators to process the read line buffer further).

  2. Place a std::getline() after infile >> FirstName >> LastName; to skip rest of line.

  3. Use std::istream::ignore() which

    Extracts and discards characters from the input stream until and including delim.

The above sample fixed with std::istream::ignore():

#include <iostream>
#include <string>

int main()
{
  std::string name1, name2;
  std::cin >> name1 >> name2;
  std::cin.ignore();
  std::cout << "name1: '" << name1 << "', name2: '" << name2 << "'\n";
  for (std::string line; std::getline(std::cin, line);) {
    std::cout << "next: '" << line << "'\n";
  }
  return 0;
}

Tested on coliru again:

$ g++ -std=c++17 main.cpp && echo -e "Donald\nDuck\n1123 Appleberry Circle\n" | ./a.out
name1: 'Donald', name2: 'Duck'
next: '1123 Appleberry Circle'
next: ''

Live Demo on coliru


Concerning the "asked very often", here some other questions:

There are probably much more. I found the above by
google site:stackoverflow.com c++ input missing

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • Thank-you forgetting back to me so quickly. So I tried using: infile.ignore(); before my infile.getline(address, 20); and I still have an issue with my output in the output file dropping down a line. Is there something I'm missing? – jpat94 Feb 18 '19 at 07:10
  • @jpat94 I'm not sure how this comes. You may check with a debugger, stepping line by line through code for input and watch the variables what exactly is stored in. Are you using VisualStudio? – Scheff's Cat Feb 18 '19 at 07:21
  • I wasn't seeing anything odd with the debugger, but I'll give it another shot. Yes I am using VisualStudio for this project. – jpat94 Feb 18 '19 at 07:23
  • @jpat94 Nice to hear. (I'm using VS2013.) VS has a very excellent visual debugger. If I remember right you should be able to see white spaces in the variable output as well. Though, `getline()` should filter newlines away. However, are you aware that `1123 Appleberry Circle` doesn't fit into `char address[20]`? Why not `std::string` instead of `char[20]`. Also: compare `1123 Appleberry Circle` to how you read `infile >> city >> state >> zip;` and what the `infile.getline(address, 20, '\n');` in line before does. – Scheff's Cat Feb 18 '19 at 07:29
  • @jpat94 Sorry, I believe I was confused about `1123 Appleberry Circle`. (In Germany, the house numbers are after street.) However, it has 22 characters, so 3 characters (and the line-end) are not consumed. Additionally, `Saint Cloud` are two strings. So, `infile >> city` should read only `Saint` leaving the `Cloud` for `infile >> state`. Checking this in debugger, you should be able to confirm my doubts. – Scheff's Cat Feb 18 '19 at 07:37
  • 1123 Appleberry Circle was a poor example as it does exceed my char address[20]. All the data provided contains less than 20, I was just a bit careless with my example there. Also I did try to use getlines for all my variables, but then when I go to run my program, it just freezes up. I'm just not sure what I'm doing wrong with the address line and city line. – jpat94 Feb 18 '19 at 07:40
  • Yes the Saint Cloud example is the same issue I'm having with the addresses. I tried using getline for both the address and city since I have no way of knowing how many spaces will be on each line, but when I output the information it comes out on a separate line. – jpat94 Feb 18 '19 at 07:42
  • @jpat94 OK. So, please read this: [How to debug small programs](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/). (I mean this seriously.) Consider, how I reduced your input problem to a minimal sample code. Do this in your side as well. Once, you mastered your input issue, you can take this sample as "cheat sheet" for your actual program. (No joke, this is exactly how I master new things - after more than 20 years in business.) – Scheff's Cat Feb 18 '19 at 07:44
  • @jpat94 May be, there is another issue in your output code as well. Watch the variables at the point there output happens. You may also check whether the `char` arrays are delimited correctly. There must always be a `'\0'` after text. According to what I wrote above, try to fix these things separately. – Scheff's Cat Feb 18 '19 at 07:48