I need help with my C++ program

I need some help here. I am having to follow some very specific, very stupid, criteria for this program. It is required to have two classes: class Student and class Roster. Those cannot go away. Nor can the enumerated data type, enum DegreeProgram defined in degree.h. Anyway, the following method is defined in roster.cpp

void Roster::add(string& studentID, string& firstName, string& lastName, string& emailAddress,
                 int age, int daysInCourse1, int daysInCourse2, int daysInCourse3, DegreeProgram degreeProgram)
{
    /* The daysInCourseArray[] constructs an array using the daysInCourse parameters given to the function.
     * Then, for the rest of the parameters (and the newly constructed daysInCourseArray[]), it instantiates a new
     * Student class that is intended to be added to the classRosterArray of pointers. */
    int daysInCourseArray[3] = {daysInCourse1, daysInCourse2, daysInCourse3};
    Student addStudent(studentID, firstName, lastName, emailAddress, age, daysInCourseArray, degreeProgram);

    /* The function treats the roster like a stack. rosterIndex points to a different index in the classRosterArray
     * each time the function is run. rosterIndex starts at -1, as defined by the constructor, and this function
     * increments it to 0 before performing an operation using the local student instance variable. The next time the
     * function is run, rosterIndex should still be pointing to 0, and so the function will increment it once again to 1.
     * This creates an iterable approach despite the asynchronous connections to the function each time. */
    if (rosterIndex - 1 < 5) *classRosterArray[++rosterIndex] = addStudent;
}

My compiler is saying that my instance of Student is invoking an undefined reference to the Constructor, but I have both a default constructor and an overloaded constructor defined in student.cpp.

//default constructor that sets all variables to empty strings
Student::Student()
{
    setStudentID("");
    setFirstName("");
    setLastName("");
    setEmail("");
    setAge(0);
    int defaultDaysInCourse[3] = {0,1,2};
    setDaysInCourse(defaultDaysInCourse);
    setDegreeProgram(SOFTWARE);
}


//overloaded constructor
Student::Student(string& studentID, string& firstName, string& lastName, string& email,
                              int age, int daysInCourse[3], DegreeProgram degree)
{
    setStudentID(studentID);
    setFirstName(firstName);
    setLastName(lastName);
    setEmail(email);
    setAge(age);
    setDaysInCourse(daysInCourse);
    setDegreeProgram(degree);
}

NVM Now it compiles, but segfaults… I didn’t even edit it.
I am wondering if there is some fundamental misunderstanding of mine that is causing my problems.

Curious why you’re passing strings by reference but nothing else? Strings are technically arrays in C and if you redefine a string you might end up with an overflow because the allocated memory doesn’t expand.

1 Like

This is C++ and I am using C++ strings. I was using reference parameters to avoid unnecessary copying of the strings.

The string serves as function input and output. The string parameter must be pass by reference, achieved using & (yellow highlighted), so that the function modifies the original string argument (userStr) and not a copy.

Sometimes a programmer defines a vector or string parameter as pass by reference even though the function does not modify the parameter, to prevent the performance and memory overhead of copying the argument that would otherwise occur.

The keyword const can be prepended to a function’s vector or string parameter to prevent the function from modifying the parameter. Programmers commonly make a large vector or string input parameter pass by reference, to gain efficiency, while also making the parameter const, to prevent assignment.

This is what is written in my textbook.

1 Like

P.S. If I did something wrong with pointers, please do not ask me why I did it. I don’t know why. Pointers do not make sense to me. I understand pointers as a concept (they point to a variable in memory), but how to use them is a completely different thing. I tried watching this video, Pointers in C - YouTube, and that helped me a lot. But I still feel like a lot is to be desired - particularly where an array of pointers is concerned. And also passing arrays into functions as parameters has been something of a challenge for me in this program.

1 Like

It’s been a while since I’ve dealt with C/C++ but my recollection is that you only need to use the ampersand in the function declaration. So when you use the function you don’t need the ampersand. So your main.cpp shouldn’t have ampersands. But like I said, I haven’t touched C++ in about 5 years.

I didn’t pass ampersand to through main at first. But I got a cast error when I tried to compile it with make. Besides, the main function is wrong. I am supposed to pass the data like this anyway, ffs.

const string studentData[] =
{"A1,John,Smith,John1989@gm ail.com,20,30,35,40,SECURITY",
"A2,Suzan,Erickson,Erickson_1990@gmailcom,19,50,30,40,NETWORK",
"A3,Jack,Napoli,The_lawyer99yahoo.com,19,20,40,33,SOFTWARE",
"A4,Erin,Black,[email protected],22,50,58,40,SECURITY", "A5,[firstname],
[lastname],[emailaddress],[age], [numberofdaystocomplete3courses],SOFTWARE"

i.e. as an array of constant strings.

Okay, so I understand pointers, but pointer syntax gives me headache… That is what I was trying to say earlier.

1 Like

So I am at work and only have access to the MS compiler (eww). What compiler are you using?

Right now there are issues with the included libraries that I am trying to resolve (we do not use C++ in this shop so it may just be an environment setup issue for me.)

A very quick debug shows that your problem lies with line 40 in roster.cpp.

I think, what is happening here, is that you have declared a local function addStudent which returns a Student object. I cannot find the function definition anywhere in your code, only the function declaration.

Also, as a general advice I’d say you’re trying to be far too clever with every single line in your code. Less is more in this case - code for readability, not speed. Right now it’s far too messy to keep track of what is happening.

[edit] Nevermind, I see now - you are trying to create a local Student instance called addStudent. This is actually a terrible way to do it. A better way is to use new to invoke the constructor on a dynamic object.

Student *steve = new Student(...);

Don’t forget to use delete when you are done. Might I also suggest you place your roster in an std::list instead? You were allowed to use any datastructure that is part of the STL and the list structure is dynamic… :slight_smile: [/edit]

5 Likes

It’s actually not that complicated once you wrap your head around it. A pointer contains the address of a block of data (e.g. an integer). So far, so good.

An array is stored in memory one element after the other. The array variable itself is nothing more than a pointer to the first element of that block of data, which is why you can pass it to a function as a pointer.
When you access a specific element of an array, the memory offset within the array (index times size of 1 element) is calculated and added to that pointer. The result is a pointer to the element in question which is then dereferenced.

And finally, an array of pointers is just that, an array filled with pointers. Each element contains the location of the actual data, kind of like the table of contents in a book.
This is particularly useful if you want to store elements of different sizes, because the previous approach assumes all elements are the same size. By storing pointers in the array, you can store the actual data wherever it fits and all of the entries are the same size.

1 Like

I am using the GNU C++ compiler. There’s a Makefile as part of the code that I defined. It will compile it on Linux with a simple make command. Because I am using CLion ( which has been a huge help in keeping my errors in line), it has made stuff for CMake as well, except I ignored the cmake-build-debug directory in git thinking it was unimportant. It will get pushed to the repository in the next code push.

I fixed the code in question on my end. It now says
auto *addStudent = new Student(...)
As for the roster itself, unfortunately, no I am not allowed to use any datastructure. The instructions explicitly require the classRosterArray to be an array of pointers. Read the README. Hopefully the class’s destructor deletes everything it needs too.

Trust me, I know that less code is more. If I were allowed to do so, I would axe both Classes in favor of cleaner, simpler code. As it is, a lot of that mess is just more of those in charge telling engineers how do their job - metaphorically speaking. Again, read the README. Every single class method is one that is required as per the project instructions. Only three of them are ones that I made myself.

I tried to clarify later on in the thread. I understand pointers, so this did not help. It is pointer syntax that gives me a headache.

1 Like

To be more specific, the ones that I defined actually help the readability of my code in some cases, or they help facilitate requirements. In the student class, I defined the getAverageDaysInCourse() method in order to simplify the roster class’s printAverageDaysInCourse() method. Additionally, the roster class also defines the getByID method, which is called two separate times in two different methods. This helps me keep away from redundant code. And finally, the roster class defines a getStudentObject() method which is useful as a getter for a single index in the classRosterArray of pointers. Besides, this one is even required in the project’s requirements - though it’s less obvious.

this is good advice in general.

write for readability first, then optimise IF required, and profile before optimising.

there’s zero point in optimising code that doesn’t need it and zero point in optimising the 90% of the code which is 10% of the runtime (the old 90/10 rule). Even if you do the grunt work on 90% of the code to cut that vast amount of code’s time in half (and you won’t) - you’re saving 5% of the runtime. Spend your time on the 10% hot-spot, and only then if you need it and the readability/maintain-ability trade off is worth it.

Make code run properly first, then profile, then optimise hotspots.

1 Like

I think for most people that have trouble with pointers, they actually grasp this concept quite well. It’s more like when do you pass by reference, when do you not. When do you use ampersand notation, when do you use asterisks notation. What is de-referencing a pointer, how do you do it, when do you do it, why do you do it. It’s more those concepts that people have problems with; me included.

2 Likes

@CodeDragon57 @anon35662433 I’ll do my best to give you a brief overview on pointer syntax.

The basics:
A variable declared with an * is a pointer.

Example:

int* x; // pointer to an integer
int *x; // same thing, slightly different notation

The & is used to get a pointer to a variable.

Example:

int x; // integer variable
int* px = &x; // pointer to x

“Dereferencing” is a fancy word for “accessing the memory location stored in the pointer” and is done with an *.

Example:

int x;
int* px;
*px = 5; // write 5 to the location stored in px, i.e. write 5 to x

If you were to do this instead:

px = 5;

px would point to the memory location 5 and x would remain unchanged. If you then attempted to dereference px, you would get a segmentation fault.
Long story short, if you want to access the pointer target, you have to dereference it.

Call by reference/call by value:
“Call by reference” means you pass data to a function in the form of a pointer (i.e. a reference). When you do this, the actual data is not copied onto the stack, only the pointer is. Since there exists only one copy of the data, anything you do to it in the function will affect the rest of you’re program.

Example:

int x = 0;

// x is 0
foo(&x);
// foo() writes 5 to the location passed as the argument => x is now 5

// a function with a pointer as argument
void foo(int* p)
{
    *p = 5;
}

“Call by value” on the other hand means directly passing data to a function. The data is copied onto the stack, which is why any modifications made in the function will not affect anything outside of said function.

Example:

int x = 0;

// x is 0
foo(x);
// the value stored in x is copied onto the stack, so foo() cannot access x directly => x is still 0

// a function with an integer as argument
void foo(int p)
{
    p = 5;
}

Generally, if you want to avoid side effects of functions you use call by value. On the other hand, if you for example want to return multiple values from a function, you can use call by reference and pass multiple pointers to the function.

Example:

int x = 0;
int y = 0;

foo(&x, &y); // x == 5, y == 10

void foo(int* p, int* q)
{
    *p = 5;
    *q = 10;
}

Another reason to use call by reference is passing large amounts of data to a function. If you were to do this using call by value, all of the data would be copied onto the stack which is slow and can potentially lead to a stack overflow™.

Example:

int x[10000]; // large array

foo(x); // no & here, because an array is already a pointer

void foo(int* x)
{
    // do something
}

Arrays vs. pointers:
In the previous example I passed an array to the function, even though it calls for a pointer. Why does that work? Well, as I’ve said before, arrays are just pointers to their first element and you can use them just like pointers.

Example:

int x[10];
int* p;

p = x; // no dereferencing here because we actually want the address of x

x[5] = 7; // write to the 6th element

// what the compiler generates from this:
*(x + 5 * sizeof(int)) = 7; // take the location of the first element, move 5 time the size of an element to the right and write to that location

I think that’s enough for now. Let me know if you have more questions.

3 Likes

Please stop getting onto me about how unreadable this code is. It sucks, I know. I would change it if I could, but I AM REQUIRED to write it this way!!!

Hey, thanks for the help with the new keyword; but unfortunately nothing changed about my program’s ability to compile. For some reason it still complains about an undefined reference to

Student::Student(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, int, int*, DegreeProgram)

I needed to add student.o to the make file build arguments. It compiles after that, but throws errors in several areas when executing.

g++ -Wall -g main.cpp roster.o student.o -o ClassRosterExec
1 Like

Sorry if we sound harsh about your code, as veterans it’s hard not to flinch and automatically give advice on pitfalls to avoid in the future. Don’t take it personally, the OCD gods must be satisfied ya know… :wink:

Ok, so what you have here is a datastructure api that is an array at the core. Instead of launching ourselves into classes at the very start, let’s focus on one thing first. Try writing a simple test program that validates the students. Try this main program and build from there:

int main(void) {
    Student *sArr[5] = { 0 };     // Array of pointers
    sArr[0] = new Student();      // Test base constructor
    sArr[1] = new Student(...);   // Test advanced constructor

    sArr[0]->print();
    sArr[1]->print();

    delete sArr[0];
    delete sArr[1];
}

Once this code work, try:

int main(void) {
    auto *ros = new Roster();     // Instantiate roster object
    ros.add("A","B","C"...);      // Add first student
    ros.add("X","Y","Z"...);      // Add second student
    ros.printAll();
    delete ros;
}

These two should help you get started.

2 Likes