C ++ Dynami c Массив Увеличение размера на 1 с каждым добавлением - ОШИБКА - PullRequest
1 голос
/ 17 февраля 2020

Я работаю над заданием для класса OO C ++, и я в тупике. Назначение состоит в том, чтобы добавить оценку (int) к динамическому массиву c и увеличить размер этого массива на 1. Массив должен начинаться с нуля. Вот спецификации, данные нам:

Комплект спецификаций.

  1. Спецификация B1 - Dynami c Array Создайте массив в куче. Сохраняйте в нем результаты учеников.
  2. Спецификация B2 - Добавление элементов Начинайте массив с размера 0 и увеличивайте его на единицу каждый раз, когда добавляете новый балл с помощью пункта меню 1.

I Я посмотрел на многочисленные примеры в Интернете в нашей книге, и я просто бьюсь головой о стену. Я перемещал вещи вокруг, пытался поместить вещи в разные порядки и т.д. c. Я придумал следующий код, и он просто не будет работать. Я уверен, что кто-то с опытом просто скажет мне, что я забыл запятую, но здесь идет. Я понимаю, что основная идея c состоит в том, чтобы создать динамический массив c, создать временный массив, который на один размер больше, затем скопировать элементы исходного массива во временный массив и затем просто указать динамически c массив к адресу памяти временного массива, удалите старый массив, затем wa sh rinse repeat, но я просто не могу это сделать. Мне нужна помощь :) В настоящее время я получаю сообщение об ошибке повреждения кучи. Вот код.

#include <iostream>
#include <cstdlib> // do not use
#include <ctime> // for seeding random numbers
#include <string> 

using namespace std;

//Global Variables

//Function Prototypes
void ProgramGreeting(); // All programs will have this method - Draw a happy litte tree
int mainMenu(int[], int*, int*); //Main Menu
void addGrade(int[], int*, int*); // Add a grade to the list. Takes the array  and the number of items in the array
void displayGrades(int[], int); // Display all the grades. Takes the array and the number of items in the array
void processGrades(int[], int); // Process all the grades. Takes the array and the number of items in the array
char letterGrade(int); // Return a letter grade
void Unittest(); // All CISP400 programs should have this.

int main()
{
    // Specification B1 - Dynamic Array
    int* grades = NULL;
    int max = 0;
    int numofGrades = 0; //A counter for the size of the dynamic array
    grades = new int[max];

    ProgramGreeting();
    mainMenu(grades, &numofGrades, &max);


    delete[] grades;

    return 0;
}

void ProgramGreeting()
{
    // Specification C1 - Program Greeting Function
    cout << "Welcome to GPA Analyzer!" << endl;
    cout << "Written by William Graves" << endl;
    cout << "This assignment is due on February 16, 2020" << endl;
}


int mainMenu(int grades[], int *numofGrades, int *max)
{
    char ans;

    do {

        cout << "Main Menu" << endl;
        cout << "--------------" << endl;
        cout << "1) Add Grade" << endl;
        cout << "2) Display All Grades" << endl;
        cout << "3) Process All Grades" << endl;
        cout << "4) Quit" << endl << endl;
        cout << "Enter your choice: ";
        cin >> ans;

        switch (ans) {
        case '1': //The user selected add grade
            addGrade(grades, numofGrades, max);
            break;
        case '2': //Display all grades
            displayGrades(grades, *numofGrades);
            break;
        case '3': //Display all grades
            processGrades(grades, *numofGrades);
            break;
        case '4': //The user chose to exit.
            cout << "Exit time. ";
            return 1000;
            break;
            // Specification C4 - Bulletproof Menu
        default:
            cout << "Your selection of '" << ans << "' is invalid. Try again." << endl;
            break;
        }
    } while (1);

}

void addGrade(int grades[], int *numofGrades, int *max) // Add a grade to the list. Takes the array  and the number of items in the array
{
    int gradeEntry = 0;
    cout << "Enter the grade: ";
    cin >> gradeEntry;
    if (gradeEntry <= 100 && gradeEntry >= 0) //verify that the grade entered is between 0 and 100
    {
        grades[*numofGrades] = gradeEntry;
        cout << "Grade of " << gradeEntry << " added successfully.";
        *numofGrades = *numofGrades + 1;
        if (*numofGrades >= *max)
        {
            *max += 1;
            //create a temporary array a size bigger:
            int* tempArray = new int[*max];
            //copy the contents of the old array to the newly allocated array
            for (int i = 0; i < *numofGrades; i++)
            {
                tempArray[i] = grades[i];
            }
            //get rid of the old array.
            delete[] grades;
            //change the memory location.
            grades = tempArray;
        }
        return;
    }
    else
    {
        cout << "Error occured. User entered: " << gradeEntry << " The grade must be an integer between 0 and 100. No grade added." << endl;
        return;
    }
}

Ответы [ 2 ]

0 голосов
/ 18 февраля 2020

Ваша основная проблема заключается в том, что вы передаете указатель на grades вашей addGrade функции:

void addGrade(int grades[], int *numofGrades, int *max)

(int grades[] фактически передается как int *grades)

Когда вы делаете это, addGrade получает копию указателя , и поскольку вы не возвращаете новый указатель, ничего, что вы делаете с grades в addGrade, никогда не видно вернуться в вызывающую функцию. Все изменения теряются при возврате функции.

У вас есть два (на самом деле три) варианта (1) передать ссылку на указатель grades или (2) изменить тип возвращаемого значения от addGrade до int * и вернуть указатель на новый выделенный блок памяти и присвоить его grades обратно в main(), и (3) - заметку, вы можете передать адрес указатель для grades на addGrade с int**, как в C)

Здесь, при сохранении вашего void возвращаемого типа, разумно сделать ссылку на указатель grades, например

void addGrade (int*& grades, int *ngrades, int gradetoadd)

(с общим примечанием, что действительно разумно использовать STL std::vector<int> вместо основного типа int*, но я понимаю это это учебное упражнение)

Следующее, что очень поможет при отладке, это Отделение вашей реализации от вашего пользовательского интерфейса , то есть не смешивайте пользовательский интерфейс и реальный лог обработки данных c вашего кода. Держите их отдельно. Это делает кодирование и тестирование реализацией должно быть проще. Затем добавьте интерфейс после того, как ваш основной код обработки данных будет сделан.

Например, давайте просто напишем только функцию addGrade и короткий интерфейс, который передает ее оценки для тестирования. Единственным интерфейсом будет отправка целых чисел на addGrade для тестирования и al oop для выдачи результатов, например,

#include <iostream>
#include <iomanip>
#include <cstring>

void addGrade (int*& grades, int *ngrades, int gradetoadd)
{
    int *newgrades = new int[*ngrades + 1];     /* allocate +1 integer */

    if (*ngrades) { /* if reallocating existing block of mem */
        memcpy (newgrades, grades, *ngrades * sizeof *grades);    /* copy */
        delete[] grades;       /* delete old */
    }
    grades = newgrades;        /* assign new block of mem to pointer */

    grades[(*ngrades)++] = gradetoadd;   /* add grade, increment ngrades */
}

int main (void) {

    int *grades = NULL,         /* a pointer to block of mem holding grades */
        ngrades = 0,            /* number of grades stored */
        tmp;                    /* temporary integer for input */

    while (std::cin >> tmp)                 /* while integer read */
        addGrade (grades, &ngrades, tmp);  /* add to grades, passing address of ptr */

    for (int i = 0; i < ngrades; i++)   /* output storged grades */
        std::cout << "grade[" << std::setw(2) << i << "] : " << grades[i] << '\n';

    delete[] grades;    /* free allocated block of memory */
}

Теперь любая отладка ограничена логами c gradeAdd сама функция не загромождена меню, и т. д. c. Выше gradeAdd теперь принимает ссылку на указатель для grades, и любые изменения grades в addGrade теперь вносятся в исходный grades из main() вместо копии указателя. Компилировать и тестировать:

Пример входного файла

Файл с 20 классами 50-100:

$ cat dat/grades.txt
74
61
67
75
73
86
95
54
93
99
68
100
95
84
50
58
79
86
98
80

Пример использования / вывода

Просто перенаправьте файл dat/grades.txt в качестве ввода на stdin в вашу тестовую программу:

$ ./bin/addgrade < dat/grades.txt
grade[ 0] : 74
grade[ 1] : 61
grade[ 2] : 67
grade[ 3] : 75
grade[ 4] : 73
grade[ 5] : 86
grade[ 6] : 95
grade[ 7] : 54
grade[ 8] : 93
grade[ 9] : 99
grade[10] : 68
grade[11] : 100
grade[12] : 95
grade[13] : 84
grade[14] : 50
grade[15] : 58
grade[16] : 79
grade[17] : 86
grade[18] : 98
grade[19] : 80

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанности в отношении любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так, (2) он может быть освобожден , когда он больше не нужен.

Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить, что вы освобождаете всю выделенную память.

Для Linux valgrind это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через нее.

$ valgrind ./bin/addgrade < dat/grades.txt
==4612== Memcheck, a memory error detector
==4612== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4612== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==4612== Command: ./bin/addgrade
==4612==
grade[ 0] : 74
grade[ 1] : 61
grade[ 2] : 67
grade[ 3] : 75
grade[ 4] : 73
grade[ 5] : 86
grade[ 6] : 95
grade[ 7] : 54
grade[ 8] : 93
grade[ 9] : 99
grade[10] : 68
grade[11] : 100
grade[12] : 95
grade[13] : 84
grade[14] : 50
grade[15] : 58
grade[16] : 79
grade[17] : 86
grade[18] : 98
grade[19] : 80
==4612==
==4612== HEAP SUMMARY:
==4612==     in use at exit: 0 bytes in 0 blocks
==4612==   total heap usage: 23 allocs, 23 frees, 78,664 bytes allocated
==4612==
==4612== All heap blocks were freed -- no leaks are possible
==4612==
==4612== For counts of detected and suppressed errors, rerun with: -v
==4612== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Всегда подтверждайте, что вы освободили всю выделенную память и что в ней нет ошибок памяти. (Вся свободная память и отсутствие ошибок является подтверждением того, что вы используете память, которую вы правильно распределили)

Теперь вы прочитали функцию gradeAdd, чтобы включить ее в оставшуюся часть кода, и вы можете быть уверены, что там нет никаких проблем с этой функцией. Любая дальнейшая отладка упрощается. Посмотрите вещи и дайте мне знать, если у вас есть вопросы.

0 голосов
/ 18 февраля 2020

При отладке чего-либо подобного, всегда пытайтесь пройтись по коду построчно для типичного ввода. (Слова мудрости от моего собственного Data Structures prof)

В этом случае похоже, что это связано с размером вашего массива. Вы инициализируете массив int размера 0. В вашем first вызове функции addGrade() вы
1. получаете ввод с консоли. (пока все хорошо)
2. Затем вы устанавливаете grades[0] равным вашему вводу
3. Затем вы изменяете размер массива.

Проблема заключается в порядке шагов 2 и 3. Вы начинаете с массива размером 0, поэтому вы не можете установить grades[0] для чего-либо, потому что вы еще не выделили для него память. Сначала вы должны изменить размер массива, а затем скопировать ваши новые (и старые) данные.

Надеюсь, что это поможет в этот раз, но в следующий раз при обнаружении ошибки в назначении попробуйте использовать ее как шанс проверить некоторые методы отладки (какими бы примитивными они ни были). Я уверен, что ваш профессор с удовольствием даст рекомендации о том, как отладить это.

...