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.