Как да освободите памет от динамичен структурен масив

Като човек, който никога не се е занимавал с освобождаване на памет и т.н., получих задачата да създам динамичен масив от структури и да създам функции за добавяне или изтриване на елементи от масив. При изтриване трябва да освободя паметта, която вече не е необходима.

когато изтривам 2-ри елемент от масив с размер 3, премествам 3-тия елемент на 2-ра позиция и след това изтривам последния. При изтриване на последното винаги ми излиза грешка... Има ли някой, който да ми намери решение?

struct myFriend {
    myFriend() {
        number=0;
        hobbys = new char*[10];
    }
    int number;
    char* name;
    char** hobbys;
};
int main() {
    myFriend* friendList = new myFriend[10];

    myFriend* tempFriend = new myFriend;
    tempFriend->number=1;
    tempFriend->name = "ABC";

    myFriend* tempFriend2 = new myFriend;
    tempFriend2->number=2;
    tempFriend->name = "XYZ";

    myFriend* tempFriend3 = new myFriend;
    tempFriend3->number=3;
    tempFriend3->name = "123";

    friendList[0] = *tempFriend;
    friendList[1] = *tempFriend2;
    friendList[2] = *tempFriend3;

    friendList[1] = friendList[2]; //move 3rd element on 2nd position
    delete &(friendList[2]); //and delete 3rd element to free memory
}

person Sebastian    schedule 22.04.2011    source източник
comment
Вместо това трябва да използвате std::vector и std::string.   -  person sharptooth    schedule 22.04.2011
comment
За задачата се казва, че използва масиви от знаци. Но може би това ще се промени. Въпреки това, това не е решение на истинския проблем :( ;)   -  person Sebastian    schedule 22.04.2011
comment
Ще трябва да следите всеки указател, върнат от new и delete точно веднъж. Също така трябва да следите всеки указател, върнат от new [], и да използвате delete[] върху този указател точно веднъж. Познайте защо повече харесваме вектор и низ!   -  person Bo Persson    schedule 22.04.2011


Отговори (6)


Защо създадохте временни променливи? Те дори не са необходими.

Ако използвате std::vector и std::string, проблемът, пред който сте изправени, ще изчезне автоматично:

std::vector<myFriend> friendList(10);

friendList[0]->number=1;
friendList[0]->name = "ABC";

friendList[1]->number=2;
friendList[1]->name = "XYZ";

friendList[2]->number=3;
friendList[2]->name = "123";

За да работи, трябва да предефинирате структурата си като:

struct myFriend {
    int number;
    std::string name;
    std::vector<std::string> hobbys;
};

Ако бъдете помолени да работите със сурови указатели, тогава трябва да правите нещо подобно:

struct Friend 
{
    int    number;
    char*  name;
};

Friend * friends = new Friend[3];

friends[0]->number=1;
friends[0]->name = new char[4];
strcpy(friends[0]->name, "ABC");

//similarly for other : friends[1] and friends[2]

//this is how you should be deleting the allocated memory.
delete [] friends[0]->name;
delete [] friends[1]->name;
delete [] friends[2]->name;

delete [] friends; //and finally this!

И ако направите някое от следните неща, това би било погрешно и би предизвикало недефинирано поведение:

delete friends[2];    //wrong
delete &(friends[2]); //wrong
person Nawaz    schedule 22.04.2011
comment
Проблемът е, че е задача да се научите как да работите с масиви. Може би е позволено да се използват низове, но за съжаление няма начин да се работи с вектори. Трябва да има някакво друго решение, предполагам. - person Sebastian; 22.04.2011
comment
@Sebastian: Добре. Тогава нека обясним как трябва да работите с указатели. - person Nawaz; 22.04.2011
comment
@Nawaz: Знам как да работя с указатели. Просто не знам как да изтрия елемент от масив. Мога просто да изтрия целия масив, но не това е целта. И ние трябва да работим с такива масиви. Динамичните масиви (дефиниращи следващия и/или предишен елемент в структурата) също не са разрешени - това би било някак лесно. - person Sebastian; 22.04.2011
comment
@Blazes: Ами сега. да... нека го поправя. - person Nawaz; 22.04.2011

Невъзможно е да се изтрие подмножество от масив, разпределен от new []

myFriend* friendList = new myFriend[10];

Имате един цял масив

+------------------------------------------------------------------+
|  friendList[0]  |  friendList[1]  |    .....  |   friendList[9]  | 
+------------------------------------------------------------------+

Не можете delete &(friendList[2]). Получавате от C++ цял масив от 10 елемента. Този масив започва от friendList (или &(friendList[0])).

operator delete с указател към адреса, върнат от new (т.е. friendList), е валиден само.

person Alexey Malistov    schedule 22.04.2011
comment
Вие сте на място. Това, от което се нуждае, е набор от указатели. Публикувах решението по-горе, но го маркирах като полезна точка. - person Blazes; 22.04.2011

Две неща забелязах. (1) Очевидно трябва да „създавате функции за добавяне или изтриване на елементи“, но не сте го направили, създали сте само една функция. (2) Вие правите работата си по-трудна, отколкото трябва, като използвате структура, която също трябва да управлява паметта. Предлагам ви да използвате по-проста структура.

Вашата задача всъщност е да направите прост "векторен" клас, така че ви предлагам да го направите. Започнете със структура, която е празна. Ако учителят изисква да използвате структурата myFriend, както е написано, можете да добавите това в след, когато завършите създаването на вашите векторни функции. Ще предположа, че все още нямате право да правите клас, защото повечето инструктори правят грешката да оставят това за последно.

struct MyStruct {
    int value; // start with just one value here. Dealing with pointers is more advanced.
};

MyStruct* array;
int size;
int capacity;

void addMyStruct(MyStruct& value); // adds a MyStruct object to the end.
void removeMyStructAtPosition(int position); // removes the MyStruct object that is at 'position'

// I leave the functions for you to implement, it's your homework after all, but I give some clues below.

void addMyStruct(MyStruct& value) {
    // First check that there is enough capacity in your array to hold the new value. 
    // If not, then make a bigger array, and copy all the contents of the old array to the new one.
    // (The first time through, you will also have to create the array.)
    // Next assign the new value to array[size]; and increment size
}

void removeMyStructAtPosition(int position) {
    // If the position is at end (size - 1,) then simply decrement size.
    // Otherwise you have to push all the structs one to the left (array[i] = array[i + 1])
    // from position to the end of the array.
}

int main() {
    // test your new class here.
    // don't forget to delete or delete [] any memory that you newed.
}
person Daniel T.    schedule 22.04.2011

Размерът на масива е фиксиран на 10, така че не е необходимо да изтривате елементи от него. Но трябва да изтриете елементите name и hobbys на friendList[1]преди да го презапишете). Тук има два проблема:

  1. Вие задавате friendList[0]->name = "ABC"; Тук "ABC" е константа завършващ с нула низ някъде в паметта. Нямате право да го изтриете. Така че трябва да направите копие.
  2. Искате да изтриете hobby[i] винаги, когато е било присвоено. Но във вашия код не можете да разберете дали е присвоен. Така че трябва да зададете всеки елемент на 0 в конструктора, така че по-късно да знаете кои елементи да изтриете.

Подходящото място за изтриване на тези елементи е в деструктора на myFriends.

person TonyK    schedule 22.04.2011
comment
‹‹Тук ABC е постоянен низ, завършващ с нула, някъде в паметта. Нямате право да го изтриете.›› Къде в кода авторът се опитва да го изтрие? - person Serge Dundich; 22.04.2011
comment
Е, това е, което OP иска да добави към своя код: изтриване на неизползвана памет. Така че като цяло елементът name ще трябва да бъде изтрит. Но в това има капан, който посочих предварително. - person TonyK; 22.04.2011

Изглежда смисълът на въпроса е да се управлява динамичен масив. Основният проблем е, че той използва масив от friendList. Използвайте масив от указатели към friendList:

struct myFriend {
    myFriend() {
        number=0;
        hobbys = new char*[10];
    }
    int number;
    char* name;
    char** hobbys;
};
int main() {
    myFriend** friendList = new myFriend*[10];

    myFriend* tempFriend = new myFriend;
    tempFriend->number=1;
    tempFriend->name = "ABC";

    myFriend* tempFriend2 = new myFriend;
    tempFriend2->number=2;
    tempFriend->name = "XYZ";

    myFriend* tempFriend3 = new myFriend;
    tempFriend3->number=3;
    tempFriend3->name = "123";

    friendList[0] = tempFriend;
    friendList[1] = tempFriend2;
    friendList[2] = tempFriend3;

    friendList[1] = friendList[2]; //move 3rd element on 2nd position
    delete friendList[2]; //and delete 3rd element to free memory
}

Но всички останали са прави -- има основни проблеми около разпределянето на памет както за „хоби“, така и за „име“, които трябва да разрешите отделно.

person Blazes    schedule 22.04.2011

За да си направите домашното бих предложил да научите много повече за указателите, операторите new/delete, new[]/delete[] операторите (да не се бъркат с операторите new/delete) и създаването/копирането/конструкторите/деструкторите на обекти. Това са основни функции на C++ и вашата задача е всичко това.

За да посочите някои насоки:

1) Когато динамично разпределяте обекта по този начин

MyType* p = new MyType;

or

MyType* p = new MyType(constructor_parameters);

получавате указателя p към създадения обект (new заделя памет за отделен обект от тип MyType и извиква конструктора на този обект).

След като работата ви с този обект приключи, трябва да се обадите

delete p;

delete извиква деструктора на обекта и след това освобождава памет. Ако не се обадите на delete, паметта ви е изтекла. Ако го извикате повече от веднъж, поведението е недефинирано (вероятно повреда в купчината, която може да доведе до срив на програмата - понякога в много странен момент).

2) Когато динамично разпределяте масив по този начин

MyType* p = new MyType[n];

получавате указателя p към масива от n създаден обект, разположен последователно в паметта (new[] заделя единичен блок памет за n обекти от тип MyType и извиква конструктори по подразбиране за всеки обект).

Не можете да промените броя на елементите в този динамичен масив. Можете само да го изтриете. След като работата ви с този масив приключи, трябва да се обадите

delete[] p;  // not "delete p;"

delete[] извиква деструктора на всеки обект в масива и след това освобождава памет. Ако не се обадите на delete[] паметта ви е изтекла. Ако го извикате повече от веднъж, поведението е недефинирано (вероятно програмен срив). Ако извикате delete вместо delete[], поведението е недефинирано (вероятно деструкторът е извикан само за първия обект и след това се опитва да освободи блок памет - но може да бъде всичко).

3) Когато присвоите структура/клас, тогава се извиква operator=. Ако нямате изрично дефиниран operator= за вашата структура/клас, тогава се генерира имплицитен operator= (извършва присвояване на всеки нестатичен член на вашата структура/клас).

person Serge Dundich    schedule 22.04.2011