2012-04-12 10 views
1

Ich arbeite an einer Hausaufgabe mit ein paar spezifischen Anforderungen. Es muss eine Klasse namens TestScores geben, die ein Array von Scores als Argument akzeptiert. Wenn eine Punktzahl negativ oder größer als 100 ist, wird eine Ausnahme ausgelöst. Schließlich muss eine Mitgliedsfunktion einen Durchschnittswert für alle Punktzahlen enthalten. Ich war nicht klug genug, einen Weg zu finden, das Array nur in den Konstruktor zu übergeben, also fügte ich auch ein int hinzu, das die Größe des Arrays angibt.Segmentierungsfehler und mysteriöse Schleife Verhalten

Ich habe den Code ausgeführt (ich bin noch nicht einmal dazu gekommen, die Ausnahmen zu testen), ich bekomme immer einen Segmentierungsfehler. Valgrind und GDB haben eher wenig hilfreich, die Ausgabe-Nachrichten wie:

==9765== Jump to the invalid address stated on the next line 
==9765== at 0x2200000017: ??? 

Noch mehr geheimnisvoll (für mich zumindest), in der for-Schleife in dem Client-Code, mein Inkrementierer, i, wird irgendwie von 0 bis einem gestoßen scheinbar zufällige zweistellige Zahl direkt nach dem Erstellen des TestScores-Objekts. In früheren Versionen, bevor ich mit rand() begann, um das Array aufzufüllen, habe ich einfach nie inkrementiert und die Endlosschleife gemacht.

Hier ist der Inhalt von TestScores.cpp:

#include <iostream> 
using std::cout; 
using std::endl; 
#include "TestScores.h" 
#include <stdexcept> 
using std::runtime_error; 

// Constructor. 
TestScores::TestScores(int a[], int s): 
_SIZE(s), _scores() 
{ 
    // Look at each item in a[], see if any of them are invalid numbers, and 
    // only if the number is ok do we populate _scores[] with the value. 
    for (int i = 0; i < _SIZE; ++i) 
    { 
     if (a[i] < 0) 
     { 
     throw runtime_error ("Negative Score"); 
     } 
     else if (a[i] > 100) 
     { 
     throw runtime_error ("Excessive Score"); 
     } 
     _scores[i] = a[i]; 
     cout << _scores[i] << " "; 
    } 
    cout << endl; 
} 

// Finds the arithmetic mean of all the scores, using _size as the number of 
// scores. 
double TestScores::mean() 
{ 
    double total = 0; 
    for (int i = 0; i < _SIZE; ++i) 
    { 
     total += _scores[i]; 
    } 
    return total/_SIZE; 
} 

// median() creates an array that orderes the test scores by value and then 
// locates the middle value. 
double TestScores::median() 
{ 
    // Copy the array so we can sort it while preserving the original. 
    int a[_SIZE]; 
    for (int i = 0; i < _SIZE; ++i) 
    { 
     a[i] = _scores[i]; 
    } 

    // Sort the array using selection sort. 
    for (int i = 0; i < _SIZE; ++i) 
    { 
     int min = a[i]; 

     for (int j = i + 1; j < _SIZE; ++j) 
     { 
     if (a[j] < min) 
     { 
      min = a[j]; 
      a[j] = a[i]; 
      a[i] = min; 
     } 
     } 
    } 

    // Now that array is ordered, just pick one of the middle values. 
    return a[_SIZE/2]; 
} 

Und hier ist der Client-Code:

#include <iostream> 
#include "TestScores.h" 
#include <stdexcept> 
#include <cstdlib> 
#include <ctime> 
using std::exception; 
using std::cout; 
using std::endl; 

int main() 
{ 
    const int NUM_STUDENTS = 20, 
      NUM_TESTS = 4; 
    int test [NUM_TESTS][NUM_STUDENTS]; 

    // Make random seed to populate the arrays with data. 
    unsigned seed = time(0); 
    srand(seed); 

    // Populate the scores for the individual tests graded for the semester. 
    // These will all be values between 0 and 100. 
    for (int i = 0; i < NUM_TESTS; ++i) 
    { 
     for (int j = 0; j < NUM_STUDENTS; ++j) 
     { 
     test[i][j] = rand() % 100; 
     cout << test[i][j] << " "; 
     } 
     cout << endl; 
    } 

    // Now we have the data, find the mean and median results for each test. 
    // All values should be valid, but we'll handle exceptions here. 
    for (int i = 0; i < NUM_TESTS; ++i) 
    { 
     cout << "For Test #" << i + 1 << endl; 
     try 
     { 
     cout << "i = " << i << endl; // i = 0 here. 
     TestScores results(test[i], NUM_STUDENTS); 
     cout << "i = " << i << endl; // i = some random number here. 
     cout << "Mean: " << results.mean() << endl; 
     cout << "Median:" << results.median() << endl << endl; 
     } 
     catch (exception &e) 
     { 
     cout << "Error, invalid score: " << e.what() << endl; 
     } 
     cout << "For Test #" << i + 1 << endl; 
    } 

    return 0; 
} 

Edit: Der Header wurde auch angefordert:

#ifndef TEST_SCORES_H 
#define TEST_SCORES_H 

class TestScores 
{ 
    private: 
     const int _SIZE; 
     int _scores[]; 

    public: 
     // Constructor 
     TestScores(int a[], int); 

     double mean() const, 
      median() const; 
}; 
#endif 

I experimentierte mit der Dynamisierung des Arrays und initialisierte das Array nicht als leer habe meine Probleme behoben, also bin ich am Ende dran. Das führt mich zu ein paar Nachfragen.

Bevor ich dynamisch ging, spielte ich mit der Initialisierung des Arrays, _scores, herum, indem ich versuchte, ihm den Größenwert zu geben, der bereits initialisiert werden sollte. Dies führte zu Compiler-Problemen. Ich habe mit meinem Lehrer darüber gesprochen, und er sagte, dass Sie keinen Platz für ein Array reservieren können, es sei denn, es gibt eine festverdrahtete globale Konstante. Das heißt, Sie können im Konstruktor keinen Größenwert übergeben, um ein Array zu initialisieren. Ist das wahr, und wenn ja, warum?

Wenn ich ein wenig zurücktrete, scheint es mir, dass dynamische Arrays besser sind, wenn Sie viele Werte benötigen, weil Sie dann keinen zusammenhängenden Speicherblock im Speicher benötigen. Wenn Sie also kleine Arrays erstellen, scheint es eine Verschwendung von Speicherplatz und Zeit zu sein, um dynamische Arrays zu erstellen. Ist das unwahr? Soll ich jetzt alle Arrays als dynamisch ausführen? Diese Erfahrung hat sicherlich meine Meinung über den Nutzen von regulären Arrays geändert, zumindest was Klassen betrifft.

Auch wenn ich volle Anerkennung für die Aufgabe bekam, habe ich das Gefühl, dass ich den Geist verletzt habe, indem ich ein Argument für die Größe übergeben habe (da die literale Problembeschreibung lautet: "Der Klassenkonstruktor sollte eine Reihe von Testergebnissen akzeptieren "). Gibt es neben einer festverdrahteten globalen Konstante oder einem Größenargument eine Möglichkeit, nur das Array zu übergeben? Ich schwöre, ich habe eine gute Stunde damit verbracht, mir einen Weg zu überlegen, wie ich das machen könnte.

+3

Können Sie auch 'TestScores.h' posten? – QuantumMechanic

+0

Was ist überhaupt ein Score? – leftaroundabout

+2

Wenn Sie gcc verwenden, fügen Sie die Flags "-g" und "-O0" für alles hinzu und führen Sie dann valgrind erneut aus: Es zeigt Ihnen möglicherweise die Codezeilennummern an. – pzanoni

Antwort

0

Ohne TestScores.h hat man zu erraten, aber wenn man bedenkt, was Sie sagen, über den Wert von i in der Schleife beschädigt werden, wo Sie die TestScores Objekte erstellen, dass Punkte auf Ihr _scores Membervariable nicht richtig initialisiert wird und wenn Sie‘ Wenn Sie versuchen, es zu laden, löschen Sie tatsächlich Speicher.

Sobald TestScores.h sichtbar ist, werde ich diese Antwort erneut unter Berücksichtigung der Datei überprüfen.


Jetzt aktualisiert, dass TestScores.h verfügbar ist.

Das Problem ist, dass Sie _scores nicht initialisieren. Sie reservieren tatsächlich keinen Speicher, um das Array zu halten, geschweige denn, den Zeiger so einzustellen, dass er auf diesen Speicher zeigt. Wenn Sie also versuchen, Dinge in das Array zu speichern, löschen Sie den Speicher irgendwo.

sollte die erste Zeile in Ihrem Konstruktor sein:

_scores = new int[_SIZE]; 

Das Gedächtnis _SIZEint s zu halten zuweisen und setzen _scores auf diesen Speicher zu zeigen. Dann werden Ihre Zuweisungen zu _scores[i] tatsächlich in definierten Speicher Ihres Programms gehen.

Natürlich müssen Sie auch diesen Speicher freigeben (C++ wird es nicht für Sie tun), wenn Instanzen von TestScore zerstört werden. So müssen Sie definieren und einen Destruktor für TestScores zu implementieren und dass destructor muss die Zeile enthält:

delete [] _scores; 

Dadurch wird der Speicherblock frei, dass _scores Punkte. Sie können Dokumente auf der delete Operation lesen, um zu sehen, warum die [] in diesem Fall da sein müssen.

+0

Die ursprüngliche Frage wurde aktualisiert, um TestScores.h anzuzeigen. – beriukay

0

Es scheint, dass Sie _scores überhaupt nicht initialisieren. Sie benötigen _scores = new int[s]; an der Spitze des Konstruktors (und delete[] s; im Destruktor).

Ohne Initialisierung _scores, schreiben Sie Dinge in undefinierten Speicherorten.

+0

Nun, das setzt voraus, dass '_scores'' 'int *' ist, was wir noch nicht wissen. Aber ich nehme auch an, dass es ein Initialisierungsproblem mit '_scores' gibt, das das verursacht. – QuantumMechanic

+0

Beachten Sie auch, dass der Destruktor möglicherweise nicht immer ausgeführt wird, da der Konstruktor werfen kann. –

+0

Ja, es scheint, _scores zu einem dynamischen Array zu machen, alle Probleme wurden gelöst. Aber nachdem ich meine frühere .h hinzugefügt hatte, hatte ich einige weitere Fragen, um das Problem besser zu verstehen. – beriukay