1

I'm working on a code for a school project which I can't use strings.
I'm having problems getting the value for hourlyPay.
The program's output: 5 Christine Kim 4.94066e-324
Although, the file contains the following:
5 Christine Kim 30.00

#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>

using namespace std;

//Class decleration
class Employee 
{
  private:
    int id;            //Employee ID.
    char name[21];        //Employee name.
    double hourlyPay;   //Pay per hour.

  public:
    Employee(int initId=0, char [] =0, double initHourlyPay=0.0);  //Constructor.

    bool set(int newId, char [], double newHourlyPay);
    int getId() { return id; }
    const char * getName() { return name;}
    double getHourlyPay() { return hourlyPay;}


};

Employee::Employee( int initId, char initName[], double initHourlyPay)
{
  bool status = set( initId, initName, initHourlyPay);

  if ( !status )
  {
    id = 0;
    strcpy(name, "");
    hourlyPay = 0.0;
  }
}

bool Employee::set( int newId, char newName[], double newHourlyPay)
{
  bool status = false;

  if ( newId > 0)
  {
    status = true;
    id = newId;
    strcpy(name, newName);
    hourlyPay = newHourlyPay;
  }
  return status;
}

const int MAX_SIZE = 100;


int main()
{

    int id;             //Employee ID.
    char newName[21];

    double hourlyPay;   //Pay per hour.


    Employee list[15];  //Array to store


    ifstream masterFile;        //Opens master file.

    masterFile.open("master10.txt");

    int count = 0;
    if (masterFile)
    {
        for (count; count < 2; count++)
        {
            masterFile >> id;
            masterFile.ignore();
            masterFile.getline(newName, 21);
            masterFile >> hourlyPay;
            list[count].set(id, newName, hourlyPay);
        }
    }

    masterFile.close(); //Close master file.

    cout << list[0].getId() << "   " << list[0].getName() << "  " << list[0].getHourlyPay();
}

The original file contains more lines, but I narrowed it down in order to figure out my error.
What am I doing wrong?

Shaun Peretz
  • 73
  • 2
  • 6
  • 1
    Forcing people to use C style strings instead of C++ strings is a clear sign of very common bad C++ teaching. Consider getting a [good book](https://stackoverflow.com/q/388242/9254539). – eesiraed May 05 '18 at 04:27
  • `hourlyPay` has been read when maststerFile.getline(...), it's in the `newName` – Monkey Shen May 05 '18 at 04:29
  • Unlike `>>` which will stop at whitespace, `getline` will read until the end of the file, the delimiter (default is `'\n'`, aka the end of the line), or the C string that the extracted input will be stored in is full. None of these will stop the function from reading the hourly pay when it reads in the name. – eesiraed May 05 '18 at 04:34
  • Using *double* for currency is not a good idea (people get really upset when you start losing money because values cannot be exactly represented in floating-point notation). Best use a signed (or unsigned) integer of sufficient size to hold the value (multiplied by `100`) so that every value can be represented exactly. See, e.g. [How to read and store currency values in C](http://stackoverflow.com/questions/33979478/how-to-read-and-store-currency-values-in-c) – David C. Rankin May 05 '18 at 04:44

4 Answers4

1

I have figured it out.

#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>

using namespace std;

//Class decleration
class Employee 
{
  private:
    int id;            //Employee ID.
    char name[21];        //Employee name.
    double hourlyPay;   //Pay per hour.

  public:
    Employee(int initId=0, char [] =0, double initHourlyPay=0.0);  //Constructor.

    bool set(int newId, char [], double newHourlyPay);
    int getId() { return id; }
    const char * getName() { return name;}
    double getHourlyPay() { return hourlyPay;}


};

Employee::Employee( int initId, char initName[], double initHourlyPay)
{
  bool status = set( initId, initName, initHourlyPay);

  if ( !status )
  {
    id = 0;
    strcpy(name, "");
    hourlyPay = 0.0;
  }
}

bool Employee::set( int newId, char newName[], double newHourlyPay)
{
  bool status = false;

  if ( newId > 0)
  {
    status = true;
    id = newId;
    strcpy(name, newName);
    hourlyPay = newHourlyPay;
  }
  return status;
}

const int MAX_SIZE = 100;


int main()
{

    int id;             //Employee ID.
    char newName[21];

    double hourlyPay;   //Pay per hour.


    Employee list[15];  //Array to store


    ifstream masterFile;        //Opens master file.

    masterFile.open("master10.txt");

    int count = 0;
    if (masterFile)
    {
        for (count; count < 2; count++)
        {
            masterFile >> id;
            masterFile.ignore();
            masterFile.get(newName, 21);
            masterFile >> hourlyPay;
            list[count].set(id, newName, hourlyPay);


        }

    }

    masterFile.close(); //Close master file.

    cout << list[0].getId() << "   " << list[0].getName() << "  " << list[0].getHourlyPay();
}

I only changed getline to get and now it can read in the middle of a line with a limit of 20 chars. I appreciate everyone's attention and help.

Shaun Peretz
  • 73
  • 2
  • 6
  • If you are guaranteed that the name is `20` char of less, **and** it is contained in a *fixed-length* field, your code makes sense -- but names rarely are limited to `20` characters, and as written, if name is `Ho Wu` you are either storing a whole lot of spaces, or you just included `hourlyPay` as part of the name. You can't "*guess*" parsing data. If you are guaranteed the fixed-length field in the data file, you are OK. Otherwise, you will need a bit more detail in your parse. (and what if there is more than `1-char` between `id` and `name`? – David C. Rankin May 05 '18 at 06:51
  • I appreciate your comments. I should have added more comments from the original assignment. One of the original requirements: "You can also assume that the name occupies 20 columns in the file". – Shaun Peretz May 05 '18 at 06:54
  • Oh well -- there is a lot of good learning in the manual parse I provided. Keep it in your hip-pocket for the time when you no longer have such a guarantee `:)` – David C. Rankin May 05 '18 at 06:55
  • I most certainly will. Your comment make a lot of sense, I just think the code you provided is a bit too complicated for me (as I mentioned earlier I'm a beginner). With that being said, I'd prefer to write a code I truly understand, rather than just copy paste. – Shaun Peretz May 05 '18 at 07:04
  • Yes, as I was going though it, I was either really impressed with your professor making you learn manual parsing -- or I had the suspicion that it may be more than you were asking for. Either way. All it does is read the whole line into an array, and then move a couple of pointers forwards and backwards to bracket the fields you need (and most of it is just validation of each step -- which is as important as the steps themselves). Good luck with your coding. – David C. Rankin May 05 '18 at 07:10
  • As an amazing exercise in pointers, you can simply take a piece of graph-paper and write the characters in the line out in the squares as they are in the file, and then take a pencil and just follow the pointer assignments and movements, labeling where each is pointing as you work through the loop -- to see exactly what is going on. – David C. Rankin May 05 '18 at 07:13
0

It's not a good idea to contain a name with blank space in the line, if you insist to do this, I think we can read the line and split the name and the hourlyPlay. The better way is that not contain blank space in the name, but use character like _, and replace the character with blank space after reading, the you can simple use >> to read each field

I modified a little to support multiline reading & printing, check the source:

#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <vector>

using namespace std;

//Class decleration
class Employee
{
private:
    int id;            //Employee ID.
    char name[21];        //Employee name.
    double hourlyPay;   //Pay per hour.

public:
    Employee(int initId = 0, const char* = 0, double initHourlyPay = 0.0);  //Constructor.

    bool set(int newId, const char*, double newHourlyPay);
    int getId() { return id; }
    const char * getName() { return name; }
    double getHourlyPay() { return hourlyPay; }


};

Employee::Employee(int initId, const char* initName, double initHourlyPay)
{
    bool status = set(initId, initName, initHourlyPay);

    if (!status)
    {
        id = 0;
        strcpy(name, "");
        hourlyPay = 0.0;
    }
}

bool Employee::set(int newId, const char* newName, double newHourlyPay)
{
    bool status = false;

    if (newId > 0)
    {
        status = true;
        id = newId;
        strcpy(name, newName);
        hourlyPay = newHourlyPay;
    }
    return status;
}

int main()
{
    int id;                 //Employee ID.
    double hourlyPay;       //Pay per hour.
    vector<Employee> list;  //Array to store
    ifstream masterFile;    //Opens master file.
    char line[256];

    masterFile.open("master10.txt");
    if (masterFile)
    {
        while (!masterFile.eof())
        {
            masterFile >> id;
            masterFile.getline(line, sizeof(line));
            char* last_word = strrchr(line, ' ');
            line[last_word - line] = 0;
            hourlyPay = atof(last_word + 1);
            list.push_back(Employee(id, line, hourlyPay));
        }
    }

    //Close master file.
    masterFile.close();
    for (size_t i = 0; i < list.size(); ++i)
        cout << list[i].getId() << "   " << list[i].getName() << "  " << list[i].getHourlyPay() << endl;
}
Monkey Shen
  • 199
  • 1
  • 6
  • 1
    Enable warnings - `for (count; count < 2; count++)` (`count` statement without effect `-Wunused-value`), either `for (; count < 2; count++)` or `for (count = 0; count < 2; count++)` (just a nit -- but a really good idea...) – David C. Rankin May 05 '18 at 04:53
  • I can't use strings, only char arrays. The file is given and contains 15 lines. Each line contains the following: (int) ID number. (char array)name of up to 20 characters. (double)pay rate per hour. I narrowed it to one line in order to solve it quicker. – Shaun Peretz May 05 '18 at 06:03
  • @ShaunPeretz I have edited my answer, removed the string, just use char array – Monkey Shen May 05 '18 at 06:31
0

Data from the file is not correctly entered into the variables in your code. Here's a solution which is self explainatory.

for (count; count < 1; count++)
{
    char secondname[11];
    masterFile >> id;
    masterFile.ignore();
    masterFile >> newName;
    masterFile >> secondname;
    masterFile >> hourlyPay;

    strcat(newName, " ");
    strcat(newName, secondname);

    list[count].set(id, newName, hourlyPay);
}
seccpur
  • 4,996
  • 2
  • 13
  • 21
0

So.. you really want to do this with a plain old array of Employee and plain old character arrays and cstring?

There is absolutely nothing wrong with doing it that way -- and it will sure make you appreciate the convenience of using string and vector. But, that said, there is a wealth of valuable learning that can be gained from walking a pointer (or couple of pointers) through each line of data to parse the id, name & hourlyPay from each line of data.

The fly-in-the-ointment of the entire parsing process is not knowing how many spaces may be contained in name (it could have none, one, two, three, ...). Though, things are not as dire as they may appear. You know you have an int that begins the line of data and a double at the end -- everything left in between the whitespace after the int and before the double is your name.

The key here is really to read each line of data into a buffer (character array) as a cstring. Then you can use the standard tool strtol to read the id and advance a pointer to 1-past the last digit in id. You can then simply iterate forward on a character-by-character basis checking if (isspace(*p)) and continuing to advance until a non-whitespace character is found (or you hit the nul-terminating character at the end). Once you find your non-whitespace character -- you have a pointer set to the beginning of name.

Now you have to work on the other end and back up until you find the space before hourlyPay. Not that difficult. You will need strlen (buf), but at least using masterFile.getline(..) you are saved the need of trimming the '\n' from the end of the buffer by overwriting with a nul-terminating character. Simply set your end-pointer to buf + len - 1 and you are sitting on the last digit of hourlyPay. Then in similar manner, it is just a matter up backing up while (ep > sp && !isspace (*p)) (you know if your end-pointer ever reaches your start-pointer sitting at the beginning of name the parse has failed)

Now remember, here you are 1-character before the beginning of hourlyPay, so when you go to convert hourlyPay using strtod, you must remember to use p + 1 as the start of the cstring-segment for converting hourlyPay. As with any strtoX conversion, you have two primary tests (1) that following the conversion the start-pointer is not equal to the endptr parameter indicating that digits were actually converted to a number and (2) errno was not set during conversion -- indicating a failure in the actual conversion. (and when converting to a smaller type than the conversion -- e.g. to int using strtol -- you must check that the value converted is within the range of int before assigning to your value.

Now you have your id and hourlyPay -- all that is left is backing up from the beginning of hourlyPay to the end of name. You do this in like manner checking isspace() until it is no longer true, and that your pointer to the end of name is still greater than your pointer to the start of name. Now you can use strncpy to copy the name to your variable for newName by copying p - sp + 1 characters (remember you are sitting on the last character with p so you will need to add 1 to get all characters in name.

Putting it altogether and providing comments inline below, you could do something like the following (noting that your original class and member functions were left unchanged -- only the parse of the id, name & hourlyPay in main() was dramatically affected) As always -- the key is to validate each step -- then you can have confidence in the data you are processing.

#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <cctype>
#include <limits>

#define MAXLIST 16      /* if you need constants, define one (or more) */
#define MAXNM   64
#define MAXBUF  1024
#define FNAME   "dat/master10.txt"

using namespace std;

//Class decleration
class Employee 
{
  private:
    int id;                 //Employee ID.
    char name[MAXNM];       //Employee name.
    double hourlyPay;       //Pay per hour.

  public:
    Employee (int initId=0, char [] =0, double initHourlyPay=0.0);
    bool set (int newId, char [], double newHourlyPay);
    int getId() { return id; }
    const char *getName() { return name;}
    double getHourlyPay() { return hourlyPay;}
};

Employee::Employee (int initId, char initName[], double initHourlyPay)
{
  bool status = set(initId, initName, initHourlyPay);

  if (!status)
  {
    id = 0;
    strcpy(name, "");
    hourlyPay = 0.0;
  }
}

bool Employee::set (int newId, char newName[], double newHourlyPay)
{
    bool status = false;

    if (newId > 0) {
        status = true;
        id = newId;
        strcpy(name, newName);
        hourlyPay = newHourlyPay;
    }

    return status;
}

int main (void) {

    int id,                         //Employee ID.
        count = 0;
    long tmp;                       /* tmp for strtol conversion */
    char newName[MAXNM] = "",
        buf[MAXBUF] = "";           /* line buffer */
    double hourlyPay;               //Pay per hour.

    Employee list[MAXLIST];         //Array to store

    ifstream masterFile (FNAME);    //Opens master file.
    if (!masterFile.is_open()) {    /* validate file open for reading */
        cerr << "error: file open failed '" << FNAME << "'\n";
        return 1;
    }

    /* read each line in masterFile into buf */
    while (count < MAXLIST && masterFile.getline (buf, sizeof buf)) 
    {
        size_t len = strlen (buf);  /* get length */

        char *sp = buf,             /* start pointer */
            *p = buf + len - 1,     /* working pointer */
            *ep = NULL;             /* end pointer for strtod conversion */

        /* parse and convert id, leaving sp 1-past last digit */
        errno = 0;                  /* zero errno before strtol conversion */
        tmp = strtol (buf, &sp, 0); /* store conversion in tmp */
        if (buf == sp) {            /* validate characters were converted */
            cerr << "error: no digits converted in id.\n";
            return 1;
        }
        if (errno != 0) {   /* validation errno not set */
            cerr << "error: failed converstion for id.\n";
            return 1;
        }
        /* validate tmp within range of integer */
        if (tmp < numeric_limits<int>::min() || 
            numeric_limits<int>::max() < tmp) {
            cerr << "error: id not within integer range.\n";
            return 1;
        }
        id = (int)tmp;  /* assign tmp to id */

        /* advance sp to 1st char in name */
        while (*sp && isspace (*sp))
            sp++;

        /* parse hourlyPay */

        /* work backward with p until space before hourlyPay found
         * always validate p > sp so you don't back up beyond the start of 
         * name (or the beginning of buf).
         */
        while (p > sp && !isspace (*p))
            p--;
        if (p > sp && !isdigit(*(p + 1))) { /* validate next char is digit */
            cerr << "error: failed to parse hourlyPay.\n";
            return 1;
        }

        errno = 0;                  /* zero errno before strtol conversion */
        hourlyPay = strtod (p+1, &ep);  /* convert hourlyPay to double */
        if (p + 1 == ep) {          /* validate characters were converted */
            cerr << "error: no digits converted in hourlyPay.\n";
            return 1;
        }
        if (errno != 0) {   /* validation errno not set */
            cerr << "error: failed converstion for hourlyPay.\n";
            return 1;
        }

        /* continue working backwards to end of name */
        while (p > sp && isspace (*p))
            p--;

        if (p <= sp) {  /* validate chars between sp & p */
            cerr << "error: failed to find end of name.\n";
            return 1;
        }

        len = p - sp + 1;       /* get number of chars in name */
        if (len > MAXNM - 1) {  /* validate it will fit in newName */
            cerr << "error: name exceeds" << len << "characters.\n";
            return 1;
        }
        strncpy (newName, sp, len); /* copy name to newName */

        /* set values in list[count], on success, increment count */
        if (list[count].set (id, newName, hourlyPay))
            count++;
    }
    masterFile.close();     //Close master file.

    /* outoput all employee id and hourlyPay information */
    for (int i = 0; i < count; i++)
        cout << list[i].getId() << "   " << list[i].getName() << 
                "  " << list[i].getHourlyPay() << '\n';
}

(now you see why string and the other C++ tools make things much nicer?)

Example Input File

The only data you posted was used as the input file, e.g.

$ cat dat/master10.txt
5 Christine Kim 30.00

Example Use/Output

$ ./bin/employeepay
5   Christine Kim  30

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85