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

C++ Dynamic Array Increase Size By 1 with each add - ERROR

本文关键字:增加 错误 数组 动态 添加 C++      更新时间:2023-10-16

我正在为OO C++类做作业,我被难住了。任务是将 grade (int) 添加到动态数组,并将该数组的大小增加 1。数组必须从零开始。以下是提供给我们的规格:

规范包。

  1. 规范 B1 - 动态阵列 在堆上创建一个数组。将学生分数存储在其中。
  2. 规范 B2 - 添加元素 从大小 0 开始数组,每次添加时将其增加 1 带有菜单选项 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;
}
}

调试此类内容时,请始终尝试逐行浏览代码以获取典型输入。(来自我自己的数据结构教授的智慧之言)

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

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

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

您的主要问题是您正在传递指向addGrade函数的grades指针

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

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

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

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

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

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

(一般注意,真正明智的做法是使用std::vector<int>提供的 STL 而不是基本类型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,并且addGrade中对grades所做的任何更改现在都从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函数被读取为合并到代码的其余部分,您可以确信该函数没有问题。简化了任何进一步的调试。仔细看看,如果你有问题,请告诉我。