0

UPDATED....New issue with addstudent function.

I made some changes based on feedback. I also made an update to the addstudent()function based on the assignment requirements and I am running into a strange issue.

The requirements for the addstudent function are that the parameters be a string id, string name, and string major. I added a line that allocates a new student node in this function, but it seems that only the ID is being kept. When I print the student information, the ID shows up, but the name and major are both blank. Where am I going wrong?

#include <iostream>
#include <string.h>
using namespace std;

class Student
{
public:
    string ID;
    string name;
    string major;
public:
    Student *next;

    Student()
    {
        ID = "0";
        name = "0";
        major = "0";
        next = NULL;
    }

    Student(string id, string name, string major)
    {
        ID = id;
        name = name;
        major = major;
        next = NULL;
    }

};


class Classroom
{
public:
    Student* head;

    Classroom()
    {
        head = NULL;
    }

    Classroom(Student* n)
    {
        head = n;
    }

    void addStudent(string id, string name, string major)
    {
    Student *n=new Student(id, name, major);
    Student* current; 
    if (head == NULL)
    { 
        n->next = head; 
        head = n; 
    }
    else if(head->name > n->name)
    { 
        n->next = head; 
        head = n; 
    } 
    else
    { 
        current = head; 
        while (current->next!=NULL && 
               current->next->name < n->name) 
        { 
            current = current->next; 
        } 
        n->next = current->next; 
        current->next = n; 
    } 
} 

void removeStudent(string id)
    {
        if (!head) 
        {
            cout << "No students exist in this list.\n";
            return;
        }

        Student **precurrent = &head,
                *current = head;

        while (current)
        {
            if (current->ID == id) 
            {
                *precurrent = current->next;
                cout<< "Student removed from list.\n";
                return;
            }
            precurrent = &current->next;
            current = current->next;
        }

      cout << "No student exists with this ID.\n";
    }

void print()
    {
        if (!head) {
            cout << "No students in list.\n";
            return;
        }

        for (Student *temp = head; temp; temp = temp->next)
                    cout <<"\nStudent Information:\n  Student ID: " << temp->ID 
                    << "\n  Student Name: " << temp->name 
                    << "\n  Student Major: " << temp->major << '\n';
    }

    void print(string id)
    {
        if (!head)
        {
            cout << "No students in list.\n";
      return;
        }
        for (Student *temp = head; temp; temp = temp->next)
    {
              if (temp->ID == id)
              {

                  cout <<"Student Information: \n"<< "Student ID: " << temp->ID <<'\n' << "Student Name: " << temp->name <<'\n' << "Student Major: " << temp->major << '\n';
          return;
              }
        if(temp==NULL)
        {
          cout<<"Student ID does not exist.\n";
          return;
        }
        }
    }

    bool isEmpty()
    {
        if (head == NULL)
            return true;
        else;
        return false;
    }

    int getSize()
    {
    int count = 0;
    if(head==NULL)
    {
    return count;
    }
        Student* temp = head;
        while (temp != NULL)
        {
            temp = temp->next;
            count++;
        }
        return count;
    }

Student *at(int index)
    {
        Student *temp = head;

        for (int i=1; i < index && temp; i++, temp = temp->next) {}

        if (!temp)
            cout << "Index not found.\n";

        return temp;
    }
};


int main()
{

    Classroom cs;
    int choice;

    do
    {
        cout <<"\nPlease select an option below: (Enter 0 to exit)\n"
      << "1. Add student.\n"
        << "2. Remove student.\n"
        << "3. Print all student.\n"
        << "4. Print specific student.\n"
        << "5. Print student at index.\n"
        << "6. Print number of students.\n"
        << "7. Check if student list is empty.\n\n";

        cin >> choice;
        Student* s1 = new Student();

        switch (choice)
        {
        case 0:
            break;
        case 1:
    {
      string id, name, major;
            cout << "Enter Student ID:";
      cin.ignore();
            getline(cin, id);
            cout << "Enter Student Name:";
            getline(cin, name);
            cout << "Enter Student Major:";
            getline(cin, major);
            cs.addStudent(id, name, major);
            break;
    }
        case 2:
    {
      string id;
            cout << "Enter Student ID of the Student to be removed: ";
            cin >> id;
            cs.removeStudent(id);
            break;
    }
        case 3:
            cs.print();
            break;
        case 4:
    {
      string id;
            cout << "Enter ID of student to print: ";
            cin >> id;
            cs.print(id);
            break;
    }
        case 5:
    {
      int index;
      if(cs.isEmpty())
      {
        cout<< "No Students Exists.\n";
        break;
      }
            cout << "Enter index to print: ";
            cin >> index;
      Student *s=cs.at(index);
      if(s)
      cs.print(s->ID);
            break;
    }
        case 6:
            cout << "There are "<< cs.getSize() << " students.\n";
            break;
        case 7:
            if(cs.isEmpty())
      cout<< "No Students Exists.\n";
      else
      cout << "The list is not empty.\n";
            break;
        default:
            cout << "Enter valid option (0-7)\n";
        }
    } while (choice != 0);

    return 0;
}
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • `while(temp==NULL)` -> `if (temp == NULL)` in `at()` and move `return(temp);` to the end of the function (leaving `break;` alone). You need to check `Student *s = cs.at.(index); if (s) cout << s;` (you don't want to output `NULL` and the error that it is not found is already output in the `at()` member function). How is `at()` not working? – David C. Rankin Jun 06 '20 at 02:58
  • In at method - 1) remove first break as you are already returning out of the method. 2) remove second while loop and add return NULL. That means you are returning NULL when index not found. – Alok Singh Jun 06 '20 at 03:15
  • Under the [Terms of Service - Subscriber Content](https://stackoverflow.com/legal/terms-of-service/public) you agree *that any and all content, ... you provide ... is ... licensed to Stack Overflow ... pursuant to Creative Commons licensing terms (CC-BY-SA), and you grant Stack Overflow the ... right ... to ... display ... such [content], even if such [content] has been ... removed by you.* Please do not deface your question. – David C. Rankin Jun 07 '20 at 01:11

2 Answers2

1

You have a large number of small errors that result in trouble throughout your code. With at() you are simply making it much harder than it needs to be. When you think about your at() function, there are simply two-conditions which must be true as you iterate over your list:

  1. Your iteration counter must be less than the index, e.g. i < index (in C/C++ indexes are zero based on everything -- keep your code consistent) and
  2. When you advance your list pointer, temp = temp->next, temp must not be NULL (or just temp for short).

You can put those together as:

    Student *at(int index)
    {
        Student *temp = head;
        int i = 0;              /* C/C++ use zero based indexes */

        for (; i < index && temp; i++, temp = temp->next) {}

        if (!temp)
            std::cerr << "error: index not found.\n";

        return temp;
    }

(where you have 2 loop-variables, temp and i and you use the comma operator to increment both in the for loop)

Similarly, you make your void removeStudent(std::string id) function much more complicated than it needs to be. When working with a list, you never keep track of a previous pointer, instead you use a pointer-to-pointer to hold the address of the current node (say Student **ppcurrent;) and you use a temporary pointer to point to the current node (say Student *pcurrent;).

When pcurrent->ID == id to remove, you simply set the pointer at the current address (as held by ppcurrent) to the next pointer in the list overwriting the Student to be removed with the ->next in the list. The beauty of this method is there are no special cases for head or any other node in the list, you simply replace the node at the current address with the next, e.g.

    void removeStudent(std::string id)
    {
        if (!head) {
            std::cout << "No students exist in this list.\n";
            return;
        }

        Student **ppcurrent = &head,
                *pcurrent = head;

        while (pcurrent)
        {
            if (pcurrent->ID == id) {
                *ppcurrent = pcurrent->next;
                return;
            }
            ppcurrent = &pcurrent->next;
            pcurrent = pcurrent->next;
        }

        std::cout << "No student exists with this ID.\n";
    }

(See Linus on Understanding Pointers and see Why is “using namespace std;” considered bad practice?)

Your handling of std::cin and your use of std::cin.ignore() will miss cases where extraneous characters exist in stdin. You need to VALIDATE every use of std::cin to check for a stream error and then std::cin.clear() any stream error caused by a type mismatch or otherwise before you can use .ignore() and then you must empty all characters from stdin, not just one. Putting that together, you have a few issues to correct in main(), e.g.

    do
    {
        std::cout <<"\nPlease select an option below: (Enter 0 to exit)\n"
                    "  1. Add student.\n"
                    "  2. Remove student.\n"
                    "  3. Print all student.\n"
                    "  4. Print specific student.\n"
                    "  5. Print student at index.\n"
                    "  6. Print number of students.\n"
                    "  7. Check if student list is empty.\n\n"
                    "choice: ";

        if (!(std::cin >> choice)) {
            std::cerr << "\nerror: invalid integer.\n";
            std::cin.clear();
            std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
            continue;
        }
        std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');

and then similarly with case: 5 of your switch() statement:

                std::cout << "\nEnter index to print (zero based): ";
                if (!(std::cin >> index)) {
                    std::cerr << "\nerror: invalid integer.\n";
                    std::cin.clear();
                    std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
                    continue;
                }

There were probably a lot more than needed correcting, but the bulk of the remaining problems were just awkwardness in the way things were coded (which is to be expected given that you are just learning C++, so nothing against the way to approached it -- that is normal and to be expected). For example, above, you only need one std::cout to output your entire menu, and you don't add a std::endl; after outputting a string-literal, just include the '\n' at the end of the literal, example:

std::cout << "hello world\n";

instead of

std::cout << "hello world" << std::endl;

It's not that the use of std::endl; is wrong, it's simply that it is not needed. The same applies to your nesting of if { } else { } within your various functions where you can simply return if you if condition is met making the else superfluous and leading to unnecessary levels of code indention. For example print() can simply be:

    void print()
    {
        if (!head) {
            std::cout << "No students in list.\n";
            return;
        }

        for (Student *temp = head; temp; temp = temp->next)
            std::cout <<"\nStudent Information:\n  Student ID: " << temp->ID 
                    << "\n  Student Name: " << temp->name 
                    << "\n  Student Major: " << temp->major << '\n';
    }

We will leave it to you to make the remaining comparisons of the changes to your code. The only other note is try and limit your variables to the scope they are needed. You only allocate a new Student if the user selects add from the menu. Otherwise you are just needlessly allocating for all other menu choices. While you may have done this to avoid the error if you tried to declare and allocate within your case 1: statement, you simply need to enclose the entire case in a block, e.g. { .... } and you are free to declare anything you need within that code block, e.g.

        case 1: {
                std::string id {}, name {}, major {};

                std::cout << "\nEnter Student ID: ";
                getline (std::cin, id);
                std::cout << "Enter Student Name: ";
                getline (std::cin, name);
                std::cout << "Enter Student Major: ";
                getline(std::cin, major);

                Student *s1 = new Student(id, name, major);
                cs.addStudent(s1);
                break;
            }

or

        case 5: {
                int index;

                if (cs.isEmpty()) {
                    std::cout << "\nlist is empty.\n";
                    break;
                }

                std::cout << "\nEnter index to print (zero based): ";
                if (!(std::cin >> index)) {
                    std::cerr << "\nerror: invalid integer.\n";
                    std::cin.clear();
                    std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
                    continue;
                }

                Student *s = cs.at(index);
                if (s)
                    cs.print (s->ID);
                break;
            }

(note: from my comment below your question, this is where you must validate the return from at() before calling print())

If you put all the pieces together, you would have:

#include <iostream>
#include <string>
#include <limits>

class Student
{
public:
    std::string ID {}, name {}, major {};
    Student *next;

    Student()
    {
        ID = "0";
        name = "0";
        major = "0";
        next = NULL;
    }

    Student(std::string ids, std::string names, std::string majors)
    {
        ID = ids;
        name = names;
        major = majors;
        next = NULL;
    }
};

class Classroom
{
public:
    Student* head;

    Classroom() { head = NULL; }
    Classroom(Student* n) { head = n; }

    void addStudent(Student *n)
    {
        Student* current; 
        if (head == NULL || head->name >= n->name)  { 
            n->next = head; 
            head = n;
            return;
        }

        current = head; 
        while (current->next && current->next->name < n->name) 
            current = current->next; 

        n->next = current->next; 
        current->next = n; 
    } 

    void removeStudent(std::string id)
    {
        if (!head) {
            std::cout << "No students exist in this list.\n";
            return;
        }

        Student **ppcurrent = &head,
                *pcurrent = head;

        while (pcurrent)
        {
            if (pcurrent->ID == id) {
                *ppcurrent = pcurrent->next;
                return;
            }
            ppcurrent = &pcurrent->next;
            pcurrent = pcurrent->next;
        }

        std::cout << "No student exists with this ID.\n";
    }

    void print()
    {
        if (!head) {
            std::cout << "No students in list.\n";
            return;
        }

        for (Student *temp = head; temp; temp = temp->next)
            std::cout <<"\nStudent Information:\n  Student ID: " << temp->ID 
                    << "\n  Student Name: " << temp->name 
                    << "\n  Student Major: " << temp->major << '\n';
    }

    void print(std::string id)
    {
        if (!head) {
            std::cout << "No students in list.\n";
            return;
        }

        for (Student *temp = head; temp; temp = temp->next) {
            if (temp->ID == id) {
                std::cout <<"\nStudent Information:\n  Student ID: " << temp->ID 
                        << "\n  Student Name: " << temp->name 
                        << "\n  Student Major: " << temp->major << '\n';
                return;
            }
        }

        std::cerr << "error: ID '" << id << "' not found.\n";
    }

    bool isEmpty()
    {
        if (head)
            return false;

        return true;
    }

    int getSize()
    {
        int count = 0;
        Student* temp = head;

        while (temp) {
            temp = temp->next;
            count++;
        }

        return count;
    }

    Student *at(int index)
    {
        Student *temp = head;
        int i = 0;              /* C/C++ use zero based indexes */

        for (; i < index && temp; i++, temp = temp->next) {}

        if (!temp)
            std::cerr << "error: index not found.\n";

        return temp;
    }
};


int main()
{

    Classroom cs;
    int choice;

    do
    {
        std::cout <<"\nPlease select an option below: (Enter 0 to exit)\n"
                    "  1. Add student.\n"
                    "  2. Remove student.\n"
                    "  3. Print all student.\n"
                    "  4. Print specific student.\n"
                    "  5. Print student at index.\n"
                    "  6. Print number of students.\n"
                    "  7. Check if student list is empty.\n\n"
                    "choice: ";

        if (!(std::cin >> choice)) {
            std::cerr << "\nerror: invalid integer.\n";
            std::cin.clear();
            std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
            continue;
        }
        std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');

        switch (choice)
        {
        case 0:
            break;
        case 1: {
                std::string id {}, name {}, major {};

                std::cout << "\nEnter Student ID: ";
                getline (std::cin, id);
                std::cout << "Enter Student Name: ";
                getline (std::cin, name);
                std::cout << "Enter Student Major: ";
                getline(std::cin, major);

                Student *s1 = new Student(id, name, major);
                cs.addStudent(s1);
                break;
            }
        case 2: {
                std::string id {};

                std::cout << "\nEnter Student ID of the Student to be removed: ";
                getline (std::cin, id);

                cs.removeStudent(id);
                break;
            }
        case 3:
            cs.print();
            break;
        case 4: {
                std::string id;

                std::cout << "\nEnter ID of student to print: ";
                getline (std::cin, id);

                cs.print(id);
                break;
            }
        case 5: {
                int index;

                if (cs.isEmpty()) {
                    std::cout << "\nlist is empty.\n";
                    break;
                }

                std::cout << "\nEnter index to print (zero based): ";
                if (!(std::cin >> index)) {
                    std::cerr << "\nerror: invalid integer.\n";
                    std::cin.clear();
                    std::cin.ignore (std::numeric_limits<std::streamsize>::max(), '\n');
                    continue;
                }

                Student *s = cs.at(index);
                if (s)
                    cs.print (s->ID);
                break;
            }
        case 6:
            std::cout << "\nThere are "<< cs.getSize() << " students.\n";
            break;
        case 7:
            if (cs.isEmpty())
                std::cout<< "\nNo Students Exists.\n";
            else
                std::cout << "\nThe list is not empty.\n";
            break;
        default:
            std::cout << "\nEnter valid option (0-7)\n";
        }
    } while (choice);

    return 0;
}

Take time to understand the changes made and if you have further questions, just drop a comment below.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thank you. This was beyond helpful. For the at() function I was missing ```Student *s=cs.at(index); if(s) cs.print(s->ID);``` . I also made some updates based on your other feedback. Thank you for taking the time to explain and provide reference material. – Casey Page Jun 06 '20 at 14:34
  • I made an update to the code, but I am now running into an issue with the addstudent function based on the parameters the function is supposed to take. I've posted the new code. The student ID is printing out, but both the name and major are blank. – Casey Page Jun 06 '20 at 14:50
  • I'll take a look when I get a chance. Probably be an hour or so. – David C. Rankin Jun 07 '20 at 00:14
0

After going through your code, there are some flaws which need to be addressed.

void addStudent(Student *n)
    {
        Student* current; 
    if (head == NULL || head->name >= n->name) 
    { 
        n->next = head; 
        head = n; 
    } 
    else
    { 
        current = head; 
        while (current->next!=NULL && 
               current->next->name < n->name) 
        { 
            current = current->next; 
        } 
        n->next = current->next; 
        current->next = n; 
    }

In the above code,

  • Condition of string comparison need to be corrected. Using >= may not yield always the desired result as per alphabetical order is concerned. Reason being that it compares length or first element both. Instead "compare" function of string can be used.
  • The condition "if(head == NULL || head->name..." will jump into a situation of operating on a null pointer. So, use nested if statement instead of clubbing both condition under single if.

Coming to "at()" function:

Student* at(int index)
    {
    Student* temp = head;
    int count=1;
    if(head==NULL) 
    {
    cout << "No students exist." << endl;
    }
    else
    {
      while(temp!=NULL)
      {
        if(count==index)
        {
          return(temp);
          break;
        }
        else
        {
          count++;  
          temp = temp->next; 
        }
      }
      while(temp==NULL)
      {
      cout << "Index not found."<< endl;
      break;
      }  
    }
  • You need to de-reference the pointer in order to get the value (under switch condition, case 5) as you are returning a pointer.
  • Your function is not returning a value in all condition. You must finally return a pointer under each condition.
  • Also, instead of "while(temp == NULL)", you need to use "if(temp == NULL)"

Lastly use "nullptr" instead of "NULL" if you are working on C++11 or above version and use inbuilt containers like std::list if possible.

user3505805
  • 147
  • 1
  • 9
  • Also, you need to take care while working on pointers. There must be always a null check before operating on a pointer. – user3505805 Jun 06 '20 at 03:26