Я пишу программу в C++, которая должна использовать динамически распределенные массивы из различных структур (в отдельных файлах). Много раз мне нужно инициировать эти массивы внутри функции. Обычно после их запуска я записываю данные в массив структуры или даже массив внутри массива структур, а затем позже использую данные. Поэтому я не использую delete внутри функции.
Например, вот одна из структур, которые я использую, структуру студента:
struct Student {
int id; // student ID
char gradeOption; // either G or P
double totalScore;
std::string studentName;
int* rawScores = NULL; // array that holds raw scores for a student
// if no scores are entered for a specific ID, we check for NULL
// we can then set the scores to 0
std::string* finalGrade; // final grade given in course
};
И вот функция для ввода исходных баллов.
// input raw scores for each id
void inputRawScores(int gradedArtifacts, int id, Student* student) {
student[id].rawScores = new int[gradedArtifacts];
for(int i = 0; i < gradedArtifacts; i++) {
std::cin >> student[id].rawScores[i];
}
}
В моем файле драйвера ученики также инициализируются значением. Показаны здесь:
Student* students = new Student[numOfStudents]; // array of students
Проблема в том, что я использую эти исходные баллы и массив студентов для вычислений в отдельном файле и использую их для вывода в других файлах и другими способами. Как я могу удалить некоторые из них?
Также я понимаю, что использование delete
удалит структуру и указатели внутри структуры, но не объекты, на которые указывают указатели. Поэтому я беру эту связь в первый вопрос, и я не могу просто delete
в конце моей программы.
Редактировать: Извините, как многие другие отметили, что я должен был указать ограничения, которые у меня есть в проекте. Мне не разрешено использовать: классы, векторы, функции внутри структур (например, конструкторы, деструкторы).
Учитывая ваши недавно опубликованные ограничения, я думаю, вы могли бы просто реализовать функцию удаления, чтобы проходить через массив Student
и выполнять ручную очистку.
Во-первых, мы создаем функцию, которая удаляет динамические объекты одного студента. Обратите внимание, что мы могли бы использовать Student&
как тип параметра, но, учитывая информацию в вашем вопросе, я не уверен, что вы уже изучили ссылки, или если вам разрешено использовать их. Поэтому мы придерживаемся указателя:
void cleanupStudent(Student* student) {
delete[] student->rawScores;
delete student->finalGrade;
// EDIT: Setting pointers back to NULL after deletion provides
// some improved safety and is good practice.
student->rawScores = NULL;
student->finalGrade = NULL;
}
После этого мы создаем функцию, которая позволяет удалить полный массив Student
, перейдя через все элементы в массиве и вызывая функцию очистки:
void deleteStudents(Student* students, int studentCount) {
for(int i = 0; i < studentCount; i++) {
cleanupStudent(&students[i]);
}
delete[] students;
}
Здесь обратите внимание на символ амперсанда (&students[i]
), который нам нужен, чтобы получить указатель на объект (который требуется как параметр для функции очистки). После этого сам массив студентов удаляется.
Вы можете вызвать эти функции следующим образом:
int numOfStudents = 16;
Student* students = new Student[numOfStudents];
deleteStudents(students, numOfStudents);
Или с одним учеником:
Student* student = new Student;
cleanupStudent(student);
delete student;
Как вы могли заметить, мы иногда используем delete
и иногда delete[]
. Первый освобождает память, которая была выделена new
. Последнее делает то же самое в памяти, которое было выделено new[]
. Это очень важно, потому что вы получите ошибки во время выполнения. Кроме того, всегда убедитесь, что указатель EVERY в вашей структуре инициализирован с помощью NULL
(C) или nullptr
(C++).
Поскольку кажется, что вы просто изучаете C/C++, очень важно упомянуть, что вышеуказанный код очень опасен, и вы можете studentCount
реальными проблемами, если, например, studentCount
не соответствует фактическому количеству элементов в массиве. Но на данный момент, я думаю, вы не знали бы (или не допускались) делать лучше.
EDIT: Я заметил, что ваш член finalGrade
имеет тип std::string*
. Есть ли причина, по которой это указатель? Потому что, если вы просто хотите сохранить строку, вы можете просто сделать std::string
, без причины для указателя. Пожалуйста, не путайте C-String типа char*
со строкой STL std::string
.
new std::string[]
вам также придется delete[]
оценки в функции очистки. В настоящее время мы делаем только регулярное delete
.
Не делай этого.
Ваша система плохо разработана, ваша struct
должна быть class
и внутренне обрабатывать память rawScores
- использование std::vector
будет самой легкой частью, но даже если вы используете обычные указатели, ключ заключается в том, что информация о том, сколько из них существует, и где они хранятся, следует отслеживать в class
.
Другими словами, структура student
должна отслеживать КАК МНОГО элементов, а также выделять/освобождать память по мере необходимости. Это не должно быть сделано в функции inputRawScores
- эта функция может вызвать функцию setNumRawScores
и вызвать функцию setRawScore(n, value)
, но не распределять память в функции считывателя. Это входит в функцию-член в структуре student
. Затем введите метод деструктора для вашего student
, который отвечает за освобождение памяти.
Конечно, использование std::vector
будет "скрывать" все это от вас, и вам просто нужно установить размер (или использовать push_back
).
Было бы очень утомительно эффективно удалять все, что вы назначили, используя новое в этом случае, когда вы передаете указатели на другие модули/функции.
Здесь у вас есть два варианта:
Либо замените эти динамические массивы (rawscores/finalGrade) соответствующими векторами.
Вы можете использовать интеллектуальные указатели (я думаю, auto_ptr будет достаточным, если вы не используете контейнеры), чтобы заботиться об управлении памятью.
РЕДАКТИРОВАТЬ:-
Одна из основных проблем, с которой вам приходится обращаться, если вы создаете необработанный указатель ученика, принадлежит вам. Скажем, вы выделили память для ученика *, а затем передаете этот объект нескольким модулям/функциям. Вы должны учесть, что вы не вызываете delete, когда ваш указатель все еще используется в каком-либо другом модуле. Также он также не должен вызывать удаление, когда он уже удален в некотором модуле. Вот почему я указал вам на два варианта...
Этот ответ устарел, так как владелец этих вопросов указал некоторые ограничения, которые не позволяют конструкторам/деструкторам.
Насколько я упускаю ваш вопрос, вы не знаете, как удалить фактические динамически выделенные rawScores
и finalGrad
в объекте Student
, правильно? Если это так, ответ довольно прост, вы просто используете деструктор:
struct Student {
int id; // student ID
char gradeOption; // either G or P
double totalScore;
std::string studentName;
int* rawScores = nullptr;
std::string* finalGrade = nullptr;
// Disable copy and move assignment
Student& operator=(const Student& rhs) = delete;
Student& operator=(Student&& rhs) = delete;
// Disable copy and move construction
Student(const Student& rhs) = delete;
Student(Student&& rhs) = delete;
Student() {
// Initialize members.
}
// Destructor
~Student() {
delete[] rawScores;
delete finalGrade;
}
};
Как только вы освободите объект Student
с помощью оператора delete
:
Student* students = new Student[numOfStudents];
delete[] students; // <-- deallocation
Деструктор вызывается для каждого объекта, который, в свою очередь, удаляет динамически выделенные объекты внутри.
Вам не нужно беспокоиться о том, что указатели имеют NULL
или нет, вызывая delete
указателя, который имеет NULL
, действителен. Кроме того, сначала инициализируйте ВСЕ указатели с помощью nullptr
при построении объекта (как показано выше).
Пока вы не смешиваете время автономной работы на разных границах EXE/DLL, вы можете delete
массив Student
из любой точки вашей программы, независимо от того, в каком файле вы его выделяете/освобождаете.
Надеюсь, это ответит на ваш вопрос. Если нет, будьте более конкретны в отношении проблемы, которая у вас есть.
EDIT: Как указано в комментариях, вы должны отключить семантику копирования/перемещения, если вы делаете это таким образом (или реализуете их). Но отключение их практически привязало бы вас к динамическому распределению структуры Student
и уменьшало бы гибкость большинства контейнеров STL. В противном случае вы можете также использовать std::vector
вместо динамически распределенных массивов, как указано в других анверах.
delete []
после удаления всех других динамически размещаемых объектов, хранящихся в приложенииStudent
.new
вообще.