Итак ... вы действительно хотите сделать это с простым старым массивом Employee
и простыми старыми массивами символов и cstring
?
Нет ничего плохого в том, чтобы делать это таким образом, и это, несомненно, заставит вас оценить удобство использования string
и vector
. Тем не менее, существует множество ценных знаний, которые можно получить, пройдя указатель (или пару указателей) по каждой строке данных, чтобы проанализировать id, name & hourlyPay
из каждой строки данных.
Ложная мазь всего процесса разбора не знает, сколько spaces
может содержаться в name
(у него может не быть ни одного, одного, двух, трех, ...). Хотя, все не так страшно, как может показаться. Вы знаете, что у вас есть int
, который начинает строку данных, и double
в конце - все, что осталось между пробелами после int
и до того, как double
станет вашим name
.
Ключевым моментом здесь действительно является чтение каждой строки данных в буфер (массив символов) как cstring
. Затем вы можете использовать стандартный инструмент strtol
, чтобы прочитать id
и продвинуть указатель на 1 после последней цифры в id
. Затем вы можете просто выполнять итерацию вперед, посимвольно проверяя if (isspace(*p))
и продолжая продвигаться, пока не будет найден непробельный символ (или вы не нажмете завершающий нуль символ в конце). Как только вы найдете свой непробельный символ - у вас есть указатель на начало name
.
Теперь вам нужно работать на другом конце и делать резервные копии , пока не найдете space
до hourlyPay
. Не так сложно Вам понадобится strlen (buf)
, но, по крайней мере, используя masterFile.getline(..)
, вы избавитесь от необходимости обрезать '\n'
от конца буфера, перезаписав символом nul-terminating . Просто установите указатель конца на buf + len - 1
, и вы сидите на последней цифре hourlyPay
. Затем аналогичным образом, это просто вопрос резервного копирования while (ep > sp && !isspace (*p))
(вы знаете, если ваш указатель конца когда-либо достигнет вашего указателя начала, находящегося в начале name
, анализ не удался)
Теперь запомните, здесь вы находитесь на 1 символ до начала hourlyPay
, поэтому, когда вы собираетесь преобразовать hourlyPay
с использованием strtod
, вы должны не забыть использовать p + 1
в качестве начало cstring-сегмента для конвертации hourlyPay
. Как и при любом преобразовании strtoX
, у вас есть два основных теста (1), что после преобразования начальный указатель не равен параметру endptr
, указывающему, что цифры были фактически преобразованы в число и (2) errno
было не установлен во время преобразования - указывает на сбой в реальном преобразовании. (и при преобразовании в тип меньшего размера, чем преобразование - например, в int
с использованием strtol
- вы должны проверить, что преобразованное значение находится в диапазоне int
, прежде чем присваивать его значению.
Теперь у вас есть id
и hourlyPay
- все, что осталось, это резервное копирование с начала hourlyPay
до конца name
. Вы делаете это таким же образом, проверяя isspace()
, пока оно не перестанет быть истинным, и что ваш указатель на конец name
все еще больше, чем ваш указатель на начало name
. Теперь вы можете использовать strncpy
, чтобы скопировать name
в переменную для newName
, скопировав p - sp + 1
символов (помните, что вы сидите на последнем символе с p
, поэтому вам нужно будет добавить 1
, чтобы получить все символы в имени.
Поместив его в целом и предоставив комментарии, встроенные ниже, вы можете сделать что-то вроде следующего (заметив, что ваши исходные class
и функции-члены остались без изменений - только разбор id, name & hourlyPay
в main()
был сильно затронут ) Как всегда - ключ к проверке каждого шага - тогда вы можете быть уверены в данных, которые вы обрабатываете.
#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';
}
(теперь вы понимаете, почему string
и другие инструменты C ++ делают вещи намного лучше?)
Пример входного файла
В качестве входного файла использовались только те данные, которые вы опубликовали, например,
$ cat dat/master10.txt
5 Christine Kim 30.00
Пример использования / Вывод
$ ./bin/employeepay
5 Christine Kim 30
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.