Хорошая новость в том, что вы были очень, очень близки. Плохая новость - это «действительно очень близкая» часть - требуется всего одна тонкая ошибка, чтобы торпедировать ваш код.
Сначала общее обсуждение. Хотя в использовании указатель на указатель на элемент нет ничего плохого, вы упускаете преимущества использования C ++ и автоматической обработки памяти, предлагаемой <vector>
. Тем не менее, это хорошо знать, как справиться с обоими.
Ваша ошибка:
while(curr->next != NULL)
Здесь, при поддержке вашего связного списка для назначения указателей на table
, вы не можете выполнить итерацию и включить водород, остановив один элемент до Helium
. Это оставляет ваш последний указатель как неинициализированный (неопределенный) указатель . Когда вы передаете элементы в qsort
, вы SegFault. Чтобы обеспечить правильное включение H
, вам необходимо:
while(curr != NULL)
(curr
содержит действительные данные до NULL
, а не curr->next = NULL
)
Кроме того, как уже говорилось, ваше чтение не может быть выполнено из-за того, что вы не проверяете каждый вызов getline
. Лучшим подходом было бы прочитать каждую строку из файла в nextline
, а затем использовать stringstream
, чтобы разделить значения во временные переменные перед выделением каждого node
. Там вы проверяете свои чтения и проверяете ваш анализ до выделения памяти . Вы можете сделать что-то вроде:
Element **get_table (string filename, int& size)
{
ifstream infile;
string nextline;
size = 0;
infile.open(filename.c_str());
Node *head = NULL;
if (!infile.is_open()) {
cerr << "file open failed '" << filename << "'\n";
return NULL;
}
while (getline (infile, nextline)) {
stringstream s (nextline);
string name, abbr;
int atomNum;
double mass;
if ((s >> atomNum >> abbr >> mass >> name)) {
Node *node = new Node;
node->elem = new Element;
node->elem->atomNum = atomNum;
node->elem->abbr = abbr;
node->elem->mass = mass;
node->elem->name = name;
node->next = head;
head = node;
size++;
}
else {
/* handle error or warn on failed parse as desired */
}
}
Далее просто примечание о значениях жесткого кодирования - не надо. Если вам нужны константы (включая имена файлов), то #define
они находятся вверху вашего исходного файла, так что есть одно удобное место для изменения значений при изменении ваших потребностей, например,
periodic_table.cpp
#include "Element.h"
#define FNAME "dat/periodictable.dat"
...
int main(void) {
int size = 0;
Element **table = get_table (FNAME, size);
В целом, ваш код предоставляет хорошо отформатированную периодическую таблицу, например,
Пример использования / Вывод
$ ./bin/periodic_table
Name Abr Ano Mass
----------------------- --- --- ------
Actinium Ac 89 227.00
Aluminum Al 13 26.98
Americium Am 95 243.00
Antimony Sb 51 121.76
Argon Ar 18 39.95
Arsenic As 33 74.92
Astatine At 85 210.00
...
Ununtrium Uut 113 284.00
Uranium U 92 238.03
Vanadium V 23 50.94
Xenon Xe 54 131.29
Ytterbium Yb 70 173.05
Yttrium Y 39 88.91
Zinc Zn 30 65.38
Zirconium Zr 40 91.22
The average mass = 146.44
(вы можете настроить ширину выходного поля (setw(24)
), чтобы привести в порядок расстояние между столбцами под заголовком)
Посмотрите вещи и дайте мне знать, если у вас есть вопросы.
.. а если вы хотите узнать секретную, мощную технику отладки, которая раскрыла ошибку?
while (curr->next != NULL)
{
cerr << "assigning table[" << i << "]: " << curr->elem->abbr << '\n';
table[i] = curr->elem;
curr = curr->next;
i++;
}
Я просто вывел на экран аббревиатуры, присвоенные table
, и остановился на:
assigning table[116]: He
Segmentation fault (core dumped)
Код, используемый в приведенном выше примере
Element.h
Не определять using xxx
в заголовках. Это будет включать в себя все пространство имен в каждом файле, включающем заголовок.
#ifndef ELEMENT_H
#define ELEMENT_H
#include <string>
struct Element
{
std::string name;
int atomNum;
double mass;
std::string abbr; // abbreviation
};
Element **get_table (std::string file, int& size);
#endif /* ELEMENT_H */
get_table.cpp
#include "Element.h"
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
struct Node
{
Element *elem;
Node *next;
};
Element **get_table (string filename, int& size)
{
ifstream infile;
string nextline;
size = 0;
infile.open(filename.c_str());
Node *head = NULL;
if (!infile.is_open()) {
cerr << "file open failed '" << filename << "'\n";
return NULL;
}
while (getline (infile, nextline)) {
stringstream s (nextline);
string name, abbr;
int atomNum;
double mass;
if ((s >> atomNum >> abbr >> mass >> name)) {
Node *node = new Node;
node->elem = new Element;
node->elem->atomNum = atomNum;
node->elem->abbr = abbr;
node->elem->mass = mass;
node->elem->name = name;
node->next = head;
head = node;
size++;
}
}
//convert linked list into array of pointers
Element **table = new Element*[size];
int i = 0;
Node *curr = head;
while (curr != NULL) /* error here - curr, not curr->next */
{
// cerr << "table[" << i << "]: " << curr->elem->abbr << '\n';
table[i] = curr->elem;
curr = curr->next;
i++;
}
//deallocate linked list
while(head != NULL)
{
Node* temp = head->next;
delete head;
head = temp;
}
infile.close();
return table;
}
periodic_table.cpp
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include "Element.h"
#define FNAME "dat/periodictable.dat"
using namespace std;
int compare_by_name(const void* e1, const void* e2)
{
struct Element *left = *(Element**) e1;
struct Element *right = *(Element**) e2;
if (left->name < right->name)
return -1;
else if (left->name > right->name)
return 1;
else
return 0;
}
int printTable(Element** tab, const int size)
{
cout << "Name" << setw(23) << "Abr" << setw(4) << "Ano" <<
setw(7) << "Mass" << endl;
cout << endl << "----------------------- --- --- ------" << endl;
cout << fixed;
for (int i = 0; i < size; i++) {
cout << setw(24) << left << tab[i]->name;
cout << setw(3) << left << tab[i]->abbr;
cout << setprecision(0) << setw(4) << right << tab[i]->atomNum;
cout << setprecision(2) << setw(7) << right << tab[i]->mass << endl;
}
return 0;
}
double getAvgMass(Element** table, const int size)
{
double sum = 0.0;
if (size == 0)
return sum;
else
for (int i = 0; i < size; i++) {
sum += table[i]->mass;
}
return sum / size;
}
/*
*
*/
int main() {
int size = 0;
Element **table = get_table (FNAME, size);
if (table != NULL) //sort table alphabetically by element name and print
{
qsort (table, size, sizeof (Element*), compare_by_name);
printTable(table, size);
//calculate and display average mass of elements in table
cout << setw(24) << left << "The average mass =" <<
fixed << setprecision(2) << setw(14) << right <<
getAvgMass(table, size) << endl;
}
delete [] table;
table = nullptr;
return 0;
}