I’ve been trying to read from a text file and storing them into a linked list in C++

so far here’s what i’ve done

this is my header file

#pragma once
using namespace std;

namespace ns {
    class PersonalInfo {
    
private:
    struct personalInfo {
        int id;
        string name;
        string address;
        string country;
        int number;
        string imagePath;
        struct personalInfo* next;
    };
    personalInfo* Head;
public:
    PersonalInfo(); //constructor
    void readFile();
};
}

and this is my implementation.cpp

#include <iostream>
#include <string>
#include <iomanip>
#include <fstream>
#include "Data.h"
using namespace std;
using namespace ns;

void PersonalInfo::readList() {
personalInfo* list = new personalInfo;
string temp;
try {
    ifstream info("C:\info.txt");
    if (!info.is_open()) {
        throw "File Does Not Exist";
    }
    else {
        while (!info.eof()) {
            getline(info, temp);
            movieList->id = stoi(temp);
            getline(info, list->name);
            getline(info, list->address);
            getline(info, list->country);
            getline(info, temp);
            list->number = stoi(temp);
            getline(videos, list->imagePath);
            list->next = NULL;

            if (Head == NULL) {
                Head = list;
            }
            else
            {
                //next?
            }
        }
    }
}
catch (const char* message) {
    cerr << message << endl;
}
}

i’m stuck on the //next?.

i feel like i’m just making a mess at this point.

my professor said to “Use data structures, files, functions, ADTs, STLs and algorithms in your program. Use STLs inside your ADTs” but i don’t understand what they mean by that. isn’t an STL a template library? or am i missing something?

i have 3 files in total: main.cpp, data.h, implementation.cpp. main contains nothing but System prints and a declaration so i can test my code.

i was recently working on a project in java so i think i’m confusing java with c++

any help is appreciated, i’m at my wits end.

edit: sample of my text file is

1
Sendai 
2-chome 
japan
110 
C:\info.jpg
2
John Douglass
2nd block
america
911
C:\info2.jpg
...

Answer

You are going to drive yourself nuts with input errors if you don’t nail down the file input aspect of filling your list. Why !.eof() inside a loop condition is always wrong. (it is and it is corrupting your attempts at input).

The problem with your use of while (!info.eof()) is after reading last line, .eof() is NOT yet set, so you loop again attempting, info >> newNode->id; which fails setting eofbit. However, since you don’t check the stream state after each read, you invoke Undefined Behavior in your code continually attempting to read from a stream with eofbit set for name, address, country, number and imagePath. The game is over at that point. Your code could appear to operate correctly or SEGFAULT or anything in between. It’s operation is no longer defined.

However, your data corruption starts with the very first six-lines of data for the very first node. Since you are reading groups of 6-lines of data for each node, you must make every read with getline(). Otherwise >> will leave the 'n' at the end of each line Unread.

Your next call to getline() for name reads nothing because it found 'n' as the next character in the input stream and considered the input an empty-line. getline() extracts the 'n' and returns. You then read name into address, and address into country. Your next info >> newNode->number; fails because your line ordering is now off and you attempt to read "japan" into number which fails setting failbit on the stream (which isn’t checked and isn’t eofbit) so the carnage continues… completely corrupting each node you attempt to add to your list.

C++ provides a much simpler and robust way to handle input. When working with any class or struct, you can overload the >> (std::istream) and << (std::ostream) operators to read just what is needed to fill your personalInfo struct. With classes, you declare the overload as a friend function of the class to provide access to the private members. In your case you could declare:

public:
    ...
    /* overload istream for input and ostream for output */
    friend std::istream& operator >> (std::istream& is, PersonalInfo::personalInfo& pi);
    friend std::ostream& operator << (std::ostream& os, PersonalInfo::personalInfo& pi);

and then define the >> operator to read and VALIDATE 6-lines of input at-a-time filling a personalInfo object. You would write that as:

std::istream& operator >> (std::istream& is, PersonalInfo::personalInfo& pi)
{
    std::string id{}, name{}, address{}, country{}, number{}, imagePath{};
    
    /* validate read of six-lines of input */
    if (getline(is, id) && getline(is, name) && getline(is, address) &&
        getline(is, country) && getline(is, number) && getline(is, imagePath)) {
        
        /* convert integer values with std::stoi, initialize tmp strucct */
        PersonalInfo::personalInfo tmp { std::stoi(id), name, address, country,
                                        std::stoi(number), imagePath, nullptr };
        
        pi = tmp;   /* assign to pi reference */
    }
    
    return is;
}

(note: the friend keyword is only used on the declaration inside the class, not in the definition that follows if defined separately. Also note, you could assign each value to pi.member directly, but it was easier to just initialize a tmp struct and assign to pi)

You should add a try {...} catch {...} around the initialization of tmp above to catch and handle any error in the conversion of std::stoi(id) or std::stoi(number). If a failure in conversion occurs, you would want to set failbit for the stream state of is above so the read loop terminates at the first failure and does not attempt to add the invalid data. You can use is.setstate (std::ios_base::failbit); to set failbit on the stream in the catch portion of your try-block. I’ll leave that to you.

(see the example at the link above, essentially you just wrap the initialization of tmp and assignment to pi in the try portion and output an error setting failbit in the catch clause)

With the overload defined, you simply loop reading a personalInfo object at a time and adding it to your linked list, e.g.

void PersonalInfo::readFile()
{
    if (filename.empty()) { /* validate filename not empty */
        std::cerr << "error: readFile() - filename empty.n";
        return;
    }
    
    PersonalInfo::personalInfo tmp{};   /* temporary struct to fill */
    std::ifstream f(filename);          /* open file */
    
    if (!f.is_open()) { /* validate file open for reading */
        std::cerr << "error: readFile() - file open failed " << filename << 'n';
        return;
    }
    
    while (f >> tmp)    /* read six lines at a time filling tmp  */
        add (tmp);      /* allocate and initialize node with tmp */
}

The add() function just isolates the allocation and initialization of the node and adding it in-order to your list. You can do it all in readFile(), but I find it cleaner to not combine the I/O readFile() with the list-operation add(), e.g.

bool PersonalInfo::add (const PersonalInfo::personalInfo& pi)
{
    /* allocate and initialize node */
    PersonalInfo::personalInfo *node = new PersonalInfo::personalInfo;
    *node = pi;
    
    if (!Head)                  /* add to list */
        Head = Tail = node;
    else {
        Tail->next = node;
        Tail = node;
    }
    
    return true;
}

(note: Tail always points to the last node in the list allowing adding nodes to the end of the list in O(1) time. For the first node, both Head and Tail point to the first node. Thereafter, you simply update Tail->next to the address of the new node and then reset Tail to that address)

To output (print) your list you do much the same overloading the << operator so it outputs one node in your list, e.g.

std::ostream& operator << (std::ostream& os, PersonalInfo::personalInfo& pi)
{
    os  << "nid        : " << pi.id << 'n'
        << "name      : " << pi.name << 'n'
        << "address   : " << pi.address << 'n'
        << "country   : " << pi.country << 'n'
        << "number    : " << pi.number << 'n'
        << "imagePath : " << pi.imagePath << 'n';
    
    return os;
}

You can change the formatting of the output to anything you like. A simple prnList() function that simply iterates over each node in the list outputting each node can be written as:

void PersonalInfo::prnList()
{
    PersonalInfo::personalInfo *iter = Head;
    
    while (iter) {
        std::cout << *iter;
        iter = iter->next;
    }
}

Avoid using MagicNumbers and hardcoding filenames in your code. You shouldn’t have to recompile just to read from a different filename. Provide the filename to your object as part of the constructor. You can use a single constructor and provide an empty filename if none is given when the class is constructed. (You would just need to write another member function, a “setter” function to set the filename). You can write the constructor as follows:

     PersonalInfo (std::string fname="") { Head = Tail = nullptr; filename = fname; }

Which optionally takes a std::string argument for the filename, initializing filename empty if none is provided.

main() takes arguments. That is what int argc, char **argv are for. A simple way of providing the filename to read is just to provide it as the first argument to your program and use argv[1] to initialize the filename in your PersonalInfo class when you declare it. That makes setting the filename when you create the class as simple as:

int main (int argc, char **argv) {
    
    if (argc < 2) {
        std::cerr << "error: insufficient inputn"
                     "usage: ./program filenamen";
        return 1;
    }
    
    PersonalInfo P { argv[1] };
    P.readFile();
    P.prnList();
}

I’ll have to look back and see if there are other major points that need addressing. Putting it altogether, a short example implementation of your list would be:

#include <iostream>
#include <fstream>
#include <string>

class PersonalInfo {
    
private:
    struct personalInfo {
        int id;
        std::string name;
        std::string address;
        std::string country;
        int number;
        std::string imagePath;
        struct personalInfo* next;
    };
    personalInfo *Head, *Tail;      /* add Tail pointer for O(1) in-order insertion */
    std::string filename;
    
public:
    PersonalInfo (std::string fname="") { Head = Tail = nullptr; filename = fname; }
    /* overload istream for input and ostream for output */
    friend std::istream& operator >> (std::istream& is, PersonalInfo::personalInfo& pi);
    friend std::ostream& operator << (std::ostream& os, PersonalInfo::personalInfo& pi);
    /* add to allocate and intialize each node */
    bool add (const PersonalInfo::personalInfo& pi);
    void readFile();
    void prnList();
};

std::istream& operator >> (std::istream& is, PersonalInfo::personalInfo& pi)
{
    std::string id{}, name{}, address{}, country{}, number{}, imagePath{};
    
    /* validate read of six-lines of input */
    if (getline(is, id) && getline(is, name) && getline(is, address) &&
        getline(is, country) && getline(is, number) && getline(is, imagePath)) {
        
        /* convert integer values with std::stoi, initialize tmp strucct */
        PersonalInfo::personalInfo tmp { std::stoi(id), name, address, country,
                                        std::stoi(number), imagePath, nullptr };
        
        pi = tmp;   /* assign to pi reference */
    }
    
    return is;
}

std::ostream& operator << (std::ostream& os, PersonalInfo::personalInfo& pi)
{
    os  << "nid        : " << pi.id << 'n'
        << "name      : " << pi.name << 'n'
        << "address   : " << pi.address << 'n'
        << "country   : " << pi.country << 'n'
        << "number    : " << pi.number << 'n'
        << "imagePath : " << pi.imagePath << 'n';
    
    return os;
}

bool PersonalInfo::add (const PersonalInfo::personalInfo& pi)
{
    /* allocate and initialize node */
    PersonalInfo::personalInfo *node = new PersonalInfo::personalInfo;
    *node = pi;
    
    if (!Head)                  /* add to list */
        Head = Tail = node;
    else {
        Tail->next = node;
        Tail = node;
    }
    
    return true;
}

void PersonalInfo::readFile()
{
    if (filename.empty()) { /* validate filename not empty */
        std::cerr << "error: readFile() - filename empty.n";
        return;
    }
    
    PersonalInfo::personalInfo tmp{};   /* temporary struct to fill */
    std::ifstream f(filename);          /* open file */
    
    if (!f.is_open()) { /* validate file open for reading */
        std::cerr << "error: readFile() - file open failed " << filename << 'n';
        return;
    }
    
    while (f >> tmp)    /* read six lines at a time filling tmp  */
        add (tmp);      /* allocate and initialize node with tmp */
}

void PersonalInfo::prnList()
{
    PersonalInfo::personalInfo *iter = Head;
    
    while (iter) {
        std::cout << *iter;
        iter = iter->next;
    }
}

int main (int argc, char **argv) {
    
    if (argc < 2) {
        std::cerr << "error: insufficient inputn"
                     "usage: ./program filenamen";
        return 1;
    }
    
    PersonalInfo P { argv[1] };
    P.readFile();
    P.prnList();
}

Example Use/Output

With your sample data in the file named dat/personalinfo.txt, you would run the program as follows receiving the following output:

$ ./bin/ll_personalinfo dat/personalinfo.txt

id        : 1
name      : Sendai
address   : 2-chome
country   : japan
number    : 110
imagePath : C:\info.jpg

id        : 2
name      : John Douglass
address   : 2nd block
country   : america
number    : 911
imagePath : C:\info2.jpg

The key in all of this is to nail down your input routine. Validate all six-lines are read and that every member of personalInfo is filled before considering the input valid. You should also enclose ech std::stoi() conversion in a try {..} catch {..} block to catch any errors during conversion to int. That I leave that to you.

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