C++ 动态数组每次添加都会将大小增加 1 - 错误

Posted

技术标签:

【中文标题】C++ 动态数组每次添加都会将大小增加 1 - 错误【英文标题】:C++ Dynamic Array Increase Size By 1 with each add - ERROR 【发布时间】:2020-02-17 20:56:00 【问题描述】:

我正在为一个 OO C++ 课程分配作业,但我很难过。任务是将等级 (int) 添加到动态数组并将该数组的大小增加 1。该数组必须从零开始。以下是给我们的规格:

规范包。

    规范 B1 - 动态阵列 在堆上创建一个数组。将学生成绩存储在其中。 规范 B2 - 添加元素 以 0 大小开始数组,每次添加时将其增加一 带有菜单选项 1 的新乐谱。

我在我们的书中查看了网络上的许多示例,但我只是把头撞到墙上。我已经移动了一些东西,试图把东西放在不同的顺序等等。我想出了下面的代码,它就是行不通。我很确定有经验的人会告诉我我忘记了逗号,但是就这样。我理解基本概念是创建动态数组,创建一个大一号的临时数组,然后将原始数组的元素复制到临时数组,然后将动态数组指向临时数组的内存地址,删除旧阵列然后清洗冲洗重复,但我似乎不能这样做。我需要帮助 :) 目前我收到一条堆损坏错误消息。这是代码。

#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;
    

【问题讨论】:

一个问题是您正在修改grades 的本地副本。您的更改不会传回给调用者。 所有这些东西都用指针填充而不是使用 std::vector 有什么原因吗?还有,new int[0] = ? @MichaelChourdakis - 也许是因为他们正在做课堂作业。我认为讨论最佳实践的最佳地点是在答案中,这样您就可以充分充实您的担忧。 【参考方案1】:

在调试这样的事情时,请始终尝试逐行浏览代码以获取典型输入。 (我自己的数据结构教授的智慧之言)

在这种情况下,它看起来与您的数组大小有关。 您初始化一个大小为 0 的 int 数组。 在您的 first 调用 addGrade() 函数中,您 1. 从控制台获取输入。 (到目前为止一切顺利) 2.然后你设置grades[0]等于你的输入 3. 然后调整数组的大小。

问题在于步骤 2 和 3 的顺序。您从大小为 0 的数组开始,因此您无法将 grades[0] 设置为任何内容,因为您尚未为其分配内存。您必须首先调整数组大小,然后复制新(和旧)数据。

希望这次能有所帮助,但下次作业中出现错误时,请尝试利用它来测试一些调试技术(无论它们多么原始)。我相信你的教授会很乐意就如何调试这个问题提出建议。

【讨论】:

【参考方案2】:

您的主要问题是您将 指针 传递给 grades 到您的 addGrade 函数:

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

int grades[] 实际上是作为int *grades 传递的)

当您这样做时,addGrade 会收到一个 指针的副本,并且由于您没有返回新指针,因此您在 addGrade 中对 grades 所做的任何操作都不会被看到调用函数。函数返回时,所有更改都将丢失。

您有两个(实际上是三个)选项(1)将 reference 传递给指针 grades,或(2)将 addGrade 的返回类型更改为 int * 并返回指向分配的新内存块的指针并将其分配给grades 回到main(),以及 (3) - 注意,您可以将grades 的指针的地址传递给@ 987654335@ 和 int** 就像在 C 中一样)

在这里,保留您的 void 返回类型,明智的做法是传递对指针 grades 的引用,例如

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

(一般注意,真正明智的做法是使用提供的 STL std::vector&lt;int&gt; 而不是基本类型 int*,但我知道这是一个学习练习)

接下来对调试有很大帮助的是将您的实现与您的用户界面分开,这意味着不要将用户界面和代码的实际数据处理逻辑混合在一起。将它们分开。它使编码和测试实现必须更容易。然后在你的核心数据处理代码完成后添加接口。

例如,让我们单独编写addGrade 函数和一个简短的接口,为其提供测试等级。唯一的接口是将整数发送到addGrade 进行测试,然后循环输出结果,例如

#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 */

现在,任何调试都仅限于 gradeAdd 函数本身的逻辑,不受菜单等的干扰。上面,gradeAdd 现在引用了 grades 的指针以及对 @ 中 grades 的任何更改987654349@ 现在从main() 变为原始grades,而不是指针的副本。编译测试:

输入文件示例

包含 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 函数已被读取以合并到代码的其余部分中,您可以确信该函数没有问题。任何进一步的调试都被简化了。如果您有任何问题,请查看并告诉我。

【讨论】:

以上是关于C++ 动态数组每次添加都会将大小增加 1 - 错误的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中每次单击鼠标都会更改字符串的字体

为啥这个函数每次运行时都会将对象两次附加到数组中?

已经初始化我的二维矩阵后动态增加行数

将结构数组从 C# 传递到 C++

如何在 Xcode 中调整 UIStackView 的子视图大小?

为啥每次使用 VBA 保存 word 文档时文件大小都会增加?