Я знаю, что векторы существуют, но это просто для практики. Я пытался увеличить размер массивов в куче. Уже был задан один квест, связанный с этим, но я все равно не смог найти свою ошибку.
структура такая: 1 массив в стеке, содержащий 2 указателя на массив int. Эти два массива int находятся в куче. Программа проверяет, есть ли пустое место в 1-м массиве, и если нет, копирует ранее существовавшие элементы во 2-й массив > удаляет 1-й > создает 1-й с 1-м размером > копирует элементы обратно > затем добавляет последний элемент.
Проблема в добавлении нового элемента. Ошибка говорит: «Буфер переполнен при записи в arr[0]».
Почему последний элемент не находится внутри границы массива? (Также в моем коде я считал элемент = 0 пустым.)
#include <iostream>
using namespace std;
int main()
{
cout << "We are trying to create Vector like mechanism.\n \n" << endl;
int* arr[2]{ nullptr }; // declared an array which contains ptrs
arr[0] = new int[3] {}; // making 1st array on heap
for (int i{1}; i > 0; i++) {
char c1; // choice 1
cout << "Do you want to add elements : "; cin >> c1;
if (c1 == 'y' || c1 == 'Y') {
int e; // element to be entered
bool a{false}; // this is a checkbox that the element is succesfully entered or not
int n = sizeof(arr[0]) / sizeof(int); // num of elements in arr[0]
for (int j{}; j < n; j++) {
if ((arr[0])[j] == 0) { // to check wether the arrays have vacancy or not
cout << "Enter the element to be entered : "; cin >> e;
(arr[0])[j] = e;
a = true;
break;
}
}
if (a == false) {
// copy 1 to 2 -> delete 1 > create new at 1 > copy 2 to 1 > add new element to 1 > delete 2
arr[1] = new int[n]; // creating 2nd array
for (int k{}; k < n; k++) { // copying the elements
(arr[1])[k] = (arr[0])[k];
}
delete[] arr[0]; // deleting 1st
arr[0] = new int[n + 1] {}; // recreating 1st
for (int l{}; l < n; l++) { // copying elements back to 1st
(arr[0])[l] = (arr[1])[l];
}
for (int t{}; t < n + 1; t++) {
cout << (arr[0])[t] << " ";
}
delete[] arr[1]; // deleting 2nd array
cout << "Enter the element to be entered : "; cin >> e;
(arr[0])[n + 1] = e; // inputing the new element
a = true;
}
}
else if (c1 == 'n' || c1 == 'N') {
cout << "OK, no new element was entered." << endl;
break;
}
else {
cout << "Enter a valid choice. ie.( Y/y for YES and N/n for NO)" << endl;
}
}
delete[] arr[0]; delete[] arr[1];
}
int n = sizeof(arr[0]) / sizeof(int); // num of elements in arr[0]
Это не способ определения количества элементов в arr[0]
.
Не суть вашей проблемы (которая заключается в том, что то, что вы выбрали для практики, никогда не будет полезным, поскольку ни один код C++ никогда не должен использовать такой тип управления памятью в динамических массивах в стиле C), а из любопытства: вы копируете элементы arr[0]
в новый больший массив (назовем его bigger_array
), но затем скопируйте элементы bigger_array
обратно в другой новый массив. Я что-то неправильно понимаю? Почему бы не использовать bigger_array
в качестве локальной переменной? На самом деле вам не нужно запоминать два массива; Просто превратите arr
в int*
(вместо int*[]
) и сделайте arr = bigger_array
.
Обратите внимание, что вам не понадобятся два указателя, одного динамически выделяемого буфера памяти должно быть достаточно. Не усложняйте задачу больше, чем нужно. И, по крайней мере, убедитесь, что вы помещаете все в класс. (голое новое удаление не должно появляться в клиентском коде)
Можем ли мы создать вектороподобный механизм? -- Ответ «да». Проблема в том, что вы видите, что это не так просто и просто, как вы думали. Слишком много новичков пытаются эмулировать что-то, что требует необходимого понимания указателей и динамически выделяемой памяти.
Примечание. Предпочитайте исправить существующий вопрос, а не удалить его. Сервер все помнит и совершенно не прощает ошибок. Когда сервер определяет, когда вы можете задать следующий вопрос, он учитывает ВСЕ ваши вопросы, включая удаленные, и вопрос с нулевым баллом считается недостаточно хорошим. Задавайте слишком много плохо полученных вопросов, и вы замедлитесь примерно до 1 вопроса каждые 6 месяцев, и при таких темпах может потребоваться очень много времени, чтобы задать достаточно хорошо принятых вопросов, чтобы вернуть вашу учетную запись в благосклонность сервера.
Гораздо проще отлаживать неинтерактивные программы. В этом случае не только есть дополнительный ввод-вывод, который требует хотя бы беглой проверки, но теперь мне нужно выяснить, какие входные данные вы предоставили, чтобы воспроизвести вашу проблему. Минимальный воспроизводимый пример не будет иметь никаких входных данных или условного поведения, если только вы не пытаетесь отладить именно это.
Артур О'Двайер объясняет все, что вам нужно знать, и предоставляет исходный код для векторного класса «сделай сам» в этом превосходном докладе с CppCon 2019: Назад к основам: RAII и правило нуля . Вот шпаргалка со всем исходным кодом. Настоятельно рекомендуется всем, кто впервые знакомится с динамически выделяемыми структурами данных.
Судя по коду в вопросе, ОП еще не изучал классы.
Когда он это сделает, он обнаружит, что приведенные ниже функции удивительно похожи на функции-члены, описанные в видео, которое я цитировал в комментариях.
Артур О'Двайер объясняет все, что вам нужно знать, и предоставляет исходный код для векторного класса «сделай сам» в этом превосходном докладе с CppCon 2019: Назад к основам: RAII и правило нуля.
Меня впечатлило то, что относительно новый программист C++ заинтересован в изучении динамически выделяемых массивов. Согласитесь, это довольно сложная тема. Более того, в производственном коде не следует изобретать велосипед. Класс std::vector
справляется со своей задачей лучше, чем вы могли бы.
Тем не менее, стоит узнать, как это делается. Рано или поздно все программисты C++ начинают этим заниматься.
Код в ФП довольно далек от того, чему учит О'Двайер, и я не пытался его «исправить». Кроме того, я не показываю, как это делается с классами RAII, что является предметом доклада О'Двайера. Вместо этого я попытался придерживаться тех функций, которые уже понимает ОП.
В программе ниже много поясняющих комментариев. Особое внимание обратите на функцию my_vec_resize
. Здесь объясняется тщательная последовательность действий, которую необходимо использовать для безопасного увеличения (или уменьшения) размера массива. Также обратите внимание, что функции my_vec_destruct
, my_vec_clone
и my_vec_assign
эффективно реализуют Правило трёх , которое является отличительной чертой класса RAII.
Функция main
внизу — это тестовый драйвер, демонстрирующий, как эти функции работают вместе. Это хорошее место, чтобы начать читать. В функции main
нет указателей, а также вызовов оператора new[]
или оператора delete[]
. Эти низкоуровневые детали реализации были скрыты в семействе функций my_vec
, как и должно быть.
// main.cpp
#include <algorithm> // copy, equal, min, swap
#include <cstddef> // size_t
#include <iostream> // cout, ostream
struct my_vec
{
int* data{ nullptr };
std::size_t size{};
};
my_vec my_vec_construct(std::size_t const n)
{
// Analogous to "constructor" function of a class.
my_vec v;
v.data = new int[n] {}; // could throw `std::bad_alloc`
v.size = n;
return v;
}
void my_vec_destruct(my_vec& v) noexcept
{
// Analogous to "destructor" function of a class.
delete[] v.data;
v.data = nullptr;
v.size = 0u;
}
my_vec my_vec_clone(my_vec const& source)
{
// Return a "deep" copy of the argument `source`.
// Analogous to "copy constructor" function of a class.
my_vec target{ my_vec_construct(source.size) };
// Use the Standard Library function `std::copy`, to copy the elements.
// It has three arguments:
// 1. pointer to beginning of source data
// 2. pointer to "one-beyond-the-last" source data
// 3. pointer to target
// These pointers can also be "iterators," but that's another subject.
std::copy(source.data, source.data + source.size, target.data);
return target;
}
void swap(my_vec& a, my_vec& b) noexcept
{
using std::swap;
swap(a.data, b.data);
swap(a.size, b.size);
}
void my_vec_assign(my_vec const& source, my_vec& target)
{
// Use the "copy-and-swap" idiom to assign a copy of argument `source`
// to the second argument `target`.
// Analogous to "copy-assignment" operator of a class.
//
// Note: the "cloning" operation could throw `std::bad_alloc`.
// When it does, we leave `target` unaltered.
my_vec tmp{ my_vec_clone(source) };
swap(tmp, target);
my_vec_destruct(tmp); // avoid memory leaks!
}
void my_vec_resize(my_vec& v, std::size_t const new_size)
{
// Analogous to "resize" function of class `std::vector`.
// It is possible that the allocation will fail. That is why
// we attempt the allocation first, before doing anything else.
// If it fails, we leave the original vector unaltered.
//
// When operator `new` fails, it will throw a `std::bad_alloc`.
// That will interrupt this function, and probably cause the
// program to abort. Otherwise, when the allocation works, we
// continue execution below, where data elements are copied.
auto p{ new int[new_size] {} }; // elements are initialized to 0.
// Allocation succeeded. Copy the elements.
// We use `std::min`, in case the `new_size` is smaller
// than the old size.
std::size_t size_to_copy{ std::min(v.size, new_size) };
std::copy(v.data, v.data + size_to_copy, p);
// Important: avoid memory leaks. Delete the existing array.
// Be sure to use the array form of operator `delete`.
delete[] v.data;
// Now, you can set `v.data` to point to the new array.
v.data = p;
p = nullptr; // optional, `p` is a "local" variable that will be
// discarded anyway.
// Don't forget to update the size.
v.size = new_size;
}
void my_vec_grow(my_vec& v) {
// Double the size of v.
std::size_t new_size{ v.size == 0u ? 2u : v.size + v.size };
my_vec_resize(v, new_size);
}
bool my_vec_empty(my_vec const& v) noexcept {
return v.size == 0u;
}
bool operator==(my_vec const& a, my_vec const& b) noexcept {
// `std::equal` has the same three arguments as `std::copy`.
return a.size == b.size
&& std::equal(a.data, a.data + a.size, b.data);
}
bool operator!=(my_vec const& a, my_vec const& b) noexcept {
return !(a == b);
}
std::ostream& operator<<(std::ostream& ost, my_vec const& v)
{
ost.put('[');
if (!my_vec_empty(v)) {
ost << v.data[0u];
for (std::size_t i{ 1u }; i < v.size; ++i) {
ost << ',' << v.data[i];
}
}
ost.put(']');
return ost;
}
int main()
{
// Constructor sets all elements to 0.
my_vec a{ my_vec_construct(2) };
std::cout << "Post construction --> a: " << a << "\n\n";
// Assign a couple of values
a.data[0] = 7;
a.data[1] = 11;
std::cout << "Post assign elements --> a: " << a << "\n\n";
// Try cloning.
auto b{ my_vec_clone(a) };
std::cout << "Clone --> b: " << b << "\n\n";
// Resize b.
my_vec_resize(b, 4);
std::cout << "Post resize --> b: " << b << "\n\n";
// Check whether a == b.
std::cout << "Check equality (a == b) --> "
<< a << (a == b ? " == " : " != ") << b << "\n\n";
// Try assignment (a is target)
my_vec_assign(b, a);
std::cout << "Post assignment my_vec_assign(b, a) --> a: " << a << "\n\n";
// Check whether a == b.
std::cout << "Check equality (a == b) --> "
<< a << (a == b ? " == " : " != ") << b << "\n\n";
// IMPORTANT: Don't leak memory.
my_vec_destruct(a);
my_vec_destruct(b);
std::cout << "Post destruct --> a: " << a << ", b: " << b << "\n\n";
return 0;
}
// end file: main.cpp
Post construction --> a: [0,0]
Post assign elements --> a: [7,11]
Clone --> b: [7,11]
Post resize --> b: [7,11,0,0]
Check equality (a == b) --> [7,11] != [7,11,0,0]
Post assignment my_vec_assign(b, a) --> a: [7,11,0,0]
Check equality (a == b) --> [7,11,0,0] == [7,11,0,0]
Post destruct --> a: [], b: []