0

I'm student working on a problem involving arrays, pointers, structs and de/referencing. I understand about what each means on its own and general initializing of each, but I'm feeling like I'm still not using them properly in my program. What I have runs/works just fine but I'd like to know if there are better ways to be using these??

The general program requirements are as follows: (I've included lines that I think cover each in question. I want to be sure I'm using and incorporating each line properly into the program. Mainly where I am using StudentData[i].testArray)

1 - Use a struct to store following data: name, id#, test (pointer to array of test scores), average, letter grade.

struct StudentData {...};

2 - Ask for number of students and number of tests (same for each student)

3 - Dynamically allocate an array of structures

StudentData *allStudents;
allStudents = new StudentData[numStudents];

4 - Each structure's Tests member should point to a dynamically allocated array of test scores.

double *testsArray;
for (int i=0; i < numStudents; i++)
{
    allStudents[i].testsArray = new double[numTests];
}
//does this need to be deleted with a for loop as well??

5 - Compute test average and letter grade

6 - Display Information

Full code:

// Course Grade
// A program to gather and display a collection of student information. 
// Programmer Name : Chuck
// C++ II

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

//structure to hold data for each student
struct StudentData
{
    string studentName;
    int studentID;
    double *testsArray;     //pointer to an array of test scores
    double testAverage;     //calculated average test score
    char courseGrade;       //calculated course letter grade (A-F)
};


//function prototypes
string getName();
int getID();
double getScore(int count);
char calcGradeLetter(double *testAverage);
void displayData (StudentData allStudents[], int sCount);



int main()
{   cout << "   Welcome to the Test Score Storer. This program will gather test\n";
    cout << "scores for a given student name/id#. We'll start with some questions.\n\n";

    int numTests;
    cout << "How many test scores per student? : ";
    cin  >> numTests;

    int numStudents;
    cout << "How many students?                : ";
    cin  >> numStudents;

    cout << "Creating Database...";
    //create array to hold all studentData structs
    StudentData *allStudents;
    allStudents = new StudentData[numStudents];
    cout << "...";
    //create a tests member array for each student struct
    double *testsArray;
    for (int i=0; i < numStudents; i++)
    {
        allStudents[i].testsArray = new double[numTests];
    }
    cout <<"...Done!\n";
    system ("pause");
    system ("CLS");


    //TRYING
    for (int i=0; i < numStudents; i++)
    {
        cout << "Student " << i+1 << " of " << numStudents << endl;
        cout << "=================================\n";
        allStudents[i].studentName = getName();
        allStudents[i].studentID = getID();
        double testTotal = 0.0;
        for (int j=0; j < numTests; j++)
        {
            allStudents[i].testsArray[j] = getScore(j);
            testTotal += allStudents[i].testsArray[j];
            allStudents[i].testAverage = testTotal/numTests;
        }
        //calculate letter grade
        allStudents[i].courseGrade = calcGradeLetter(&allStudents[i].testAverage);
        cout << "Student,  " << allStudents[i].studentName << ", completed.\n";
        system ("pause");
        system ("CLS");
    }

    //Display all collected student data in chart form
    displayData(allStudents, numStudents);


    delete [] allStudents;
    system ("pause");
    return (0);
}


//===========FUNCTIONS===========FUNCTIONS===========FUNCTIONS
string getName()
{
    string name;
    cout << "Student Name  : ";
    cin  >> name;
    return name;
}

int getID()
{
    int IDnum;
    cout << "Student ID    : ";
    cin  >> IDnum;
    return IDnum;
}

double getScore(int count)
{
    double score;
    score = -500;
    while (score <0)
    {   cout << "Test Score #" << count+1 <<" : ";
        cin  >> score;
        if (score < 0)
        {
            cout << "Scores cannot be less than 0, try again.\n";
        }
    }   
    return score;
}


char calcGradeLetter(double *testAverage)
{
    if (*testAverage >= 90) return 'A';
    else if (*testAverage >= 80) return 'B';
    else if (*testAverage >= 70) return 'C';
    else if (*testAverage <  70) return 'F';
    else return ('X');
}

void displayData(StudentData allStudents[], int sCount)
{
    cout << "Student Data Chart:\n";
    cout << "=================================================\n";
    cout << "NAME               ID#          TEST AVE.   GRADE\n";
    for (int i = 0; i < sCount; i++)
    {
        cout << left << setw(18) << allStudents[i].studentName << " "
             << left << setw(12) << allStudents[i].studentID << " "
             << left << setw(11) << setprecision(4) << allStudents[i].testAverage << " "
             << allStudents[i].courseGrade << endl;
    }
    cout << "\nTable complete. ";
    }

This is my first post on Stack, sorry if this lands in the wrong place or comes across as homework needy, I'm not just looking for an easy answer. I am a new developer, eager to learn. This topic is just something I'm having a lot of trouble working with.

Pierre Fourgeaud
  • 14,290
  • 1
  • 38
  • 62
Half Fast
  • 3
  • 1
  • 5
  • 4
    It's much easier to use `std::vector` instead of dynamic arrays a la `new[]`. – chris Aug 27 '13 at 15:44
  • 3
    If only you knew how much of this cruft *disappears* using `std:;vector<>` in just two places. Other than spurious use of `std::string`, `operator new()` and console output, this may as well be C code. Also, your `StudentData` is *begging* for a constructor and destructor. – WhozCraig Aug 27 '13 at 15:46

2 Answers2

1

The short answer to your question is yes, there are better ways to accomplish the result.

The longer answer is that due to the nature of your assignment and the requirements that you have to deal specifically with memory allocation and deallocation (which, by the way, I don't see where you are deallocating the testscores for each student. You need to loop through each student before deleting it and delete the test scores array), it limits you from using things like std::vector, which would handle all of the array issues for you.

Just to help with the memory management a bit:

struct StudentData
{
    StudnetData() : studentID(0), testsArray(NULL), testAverage(0.0), courseGrade('N')
    {

    }

    ~StudentData()
    {
        delete [] testsArray;
    }

    string studentName;
    int studentID;
    double *testsArray;     //pointer to an array of test scores
    double testAverage;     //calculated average test score
    char courseGrade;       //calculated course letter grade (A-F)
private:
    StudentData(const StudentData&); // unimplemented intentionally
    StudentData& operator= (const StudentData&); // unimplemented intentionally
};

Then, when you delete the student array:

delete [] allStudents;

Any tests they have will also be deleted.

Zac Howland
  • 15,777
  • 1
  • 26
  • 42
  • We've learned about vectors and I see how they could be very useful here but unfortunately we do have to use arrays as noted. Zac, I wasn't sure if deleting the allStudents array would delete the test scores array. I'll loop through and delete as: – Half Fast Aug 27 '13 at 15:51
  • If you add a destructor to your struct, you can have it delete the test scores array when the student is deleted. – Zac Howland Aug 27 '13 at 15:56
  • sorry...forgot to shift+enter then ran out of time. to delete properly would be this for (int x=0; x < numStudents; x++) { delete allStudents[x].testsArray; } – Half Fast Aug 27 '13 at 15:58
  • That is one way to do it. I added to my answer showing another way. – Zac Howland Aug 27 '13 at 16:01
  • The only things I would change in this is skip the empty-string initializer for `studentName` (it isn't needed), toss out the NULL-check in the destructor (it isn't needed), and hide the assignment operator and copy-ctor for the class to ensure a Rule of Three violation is never introduced. Apart from that, this looks to fit the confines of the OP's question. – WhozCraig Aug 27 '13 at 16:08
  • @WhozCraig: True ... old habits die hard. – Zac Howland Aug 27 '13 at 16:12
  • @WhozCraig: Does the "Rule of 3" now become the "Rule of 5" with the move constructor/assignment additions in C++11? – Zac Howland Aug 27 '13 at 16:20
  • @ZacHowland [Pretty much, yeah](http://stackoverflow.com/questions/4782757/rule-of-three-becomes-rule-of-five-with-c11) – WhozCraig Aug 27 '13 at 16:22
  • @WhozCraig: Ah, at least they won't auto-generate if I declare the others as unimplemented private. – Zac Howland Aug 27 '13 at 16:30
  • @ZacHowland I still do that by habit just because the neanderthals here at my work place are slower than government contractors in getting up to light speed with C++11. Ugh. – WhozCraig Aug 27 '13 at 16:35
1

Eh, this is largely fine given that you're asked to use new rather than the standard practice of types like std::vector.

C++ does have tools you could use to wrap up this usage of new to make it easier, safer, more automatic, etc., while still complying with the requirements. You'd essentially be implementing a simplified version of vector. However you probably haven't learned all the necessary parts of C++.

structs TestsArray {
  double *scores;
  // constructors, destructors, etc. to make this behave properly
  // ...
};

structs StudentsArray {
  StudentData *students;
  // basically the same as for TestArray; templates avoid this duplication
};

Also for the grade you might define an enum:

enum Grade { A, B, C, D, F};

This way you're less likely to encounter an invalid grade (e.g., 'e') somewhere.

struct StudentData
{
  string studentName;
  int studentID;
  TestsArray testsArray;
  double testAverage;
  Grade courseGrade;
};


cout << "Creating Database...";
//create array to hold all studentData structs
StudentsArray allStudents(numStudents, numTests);
cout <<"...Done!\n";

// allStudents will automatically be cleaned up at the end

//does this need to be deleted with a for loop as well??

Yes, or at least you need to somehow arrange for every Student's testsArray to be deleted.

bames53
  • 86,085
  • 15
  • 179
  • 244
  • Not necessarily a for-loop, but that is 1 way to do it (adding a destructor to the student structure is another). – Zac Howland Aug 27 '13 at 16:02
  • @ZacHowland Ah, I was just changing that. My preferred method (given that `std::vector` is off the table) would be implementing an array type that takes care of itself (so the `Student` could use the default destructor, and the whole `Student` array could be a similar type (or just another instantiation of the same array template.)). – bames53 Aug 27 '13 at 16:03
  • I concur. I always hated assignments like this that forced you into doing something that is bad practice. At the very least, they should allow for the incorporation of smart pointers. – Zac Howland Aug 27 '13 at 16:09