C ++ в чем разница между использованием. или -> для связанного списка - PullRequest
3 голосов
/ 14 июня 2011

предположим, у меня есть структура.

struct node {
 string info;
 node link*;

}

В чем разница между

node k;
node b;

k.info = "string";
b.info = "string";
k.link = &b;

и

node *k;
node *b;
k = new node;
b = new node;    
k->info = "string";
b->info = "string";
k->link = b;

с точки зрения выделения памяти?Являются ли оба примера правильными и создают правильный связанный список? Добавлено: В большинстве книг используется второй пример, почему?Есть ли обратная сторона в использовании первого примера?

Ответы [ 9 ]

6 голосов
/ 14 июня 2011

Да. оба технически правильны.

В первом примере память находится в стеке, во втором - в куче.

Когда память находится в стеке, «компилятор» отвечает за освобождение памяти (когда переменные выходят из области видимости). Поскольку ваш связанный список может пережить переменные, которые вы явно создали, это может вызвать всевозможные проблемы. Например, в первом примере, если b выходит из области видимости, а k - нет, то k будет иметь висячий указатель, который ведет к неопределенному поведению (или плохо).

Когда память находится в куче, вы должны управлять памятью, что может вызвать проблемы, если вы забудете ее освободить. Это сложнее, чем кажется, учитывая исключения. Я предлагаю вам использовать интеллектуальную указку . Это наиболее распространенный и безопасный способ использования связанного списка.

2 голосов
/ 14 июня 2011

Речь идет не о связанном списке.

  • . используется для ссылки на компонент структуры, когда известен объект структуры
  • -> используется для ссылкикомпонент структуры, когда известен адрес объекта структуры

Как

node k, *m, *n;

m = &k;
n = new node;

k.info  = "Hello";   // k is a node type object so use directly . operator
m->info = "Hi";      // m is a pointer to an object to type node, so use -> operator
n->info = "Man";     // n is a pointer to an object to type node, so use -> operator
*(m).info = "This";  // *(m) refers to an object itself, we use . operator on it
*(n).info = "Is a test"; // *(n) refers to an object itself, we use . opeartor on it

Все они имеют допустимый синтаксис

  • Когда вы делаете node k; внутри функции она обычно располагается в стеке
  • Когда вы делаете static node k; или объявляете node k; глобальным, она размещается в .data или аналогичном разделе исполняемого файла
  • Когда вы используете new для выделения некоторой памяти, она обычно выделяется из кучи

В книгах используется второй пример, потому что вы заранее не знаете, сколько узлов будет использовано, поэтому нецелесообразно выделять узел как локальные переменные с тоннами определений переменных.Вместо этого указатель используется для временного выделения памяти, их инициализации и затем связывания с соответствующей позицией списка.В будущем адресные ссылки ссылок указателей в связанных списках позволят нам получить доступ к узлам связанного списка в порядке связывания.Таким образом, мы использовали node *k; k = new node;, поэтому у нас есть адрес для объекта типа node, поэтому естественно использовать k->info;, но вы можете использовать любой синтаксис.Вы должны помнить, что в левой части -> должен быть адрес типа класса / структуры, к которому вы хотите обратиться, а в левой части оператора . должен бытьСам объект.

PS.Вы должны освободить память на delete (которую вы выделили с помощью new) после того, как вы закончили использовать.

2 голосов
/ 14 июня 2011

Оба являются правильными и создадут правильный связанный список, но в первом примере используется автоматическое распределение (глобальное или в стеке, в рамках, зависит от контекста), а во втором - динамическое (для каждого new выв конечном итоге придется вызвать delete).

Когда использовать, что зависит от логики вашей программы, но обычно связанные списки выполняются с использованием динамического выделения (ваш второй пример)), из моего личного опыта.

1 голос
/ 14 июня 2011

Как уже указывалось, разница в двух формах заключается в том, что вторая включает в себя динамическое распределение с ручным управлением временем жизни объектов.Поскольку второе является более сложным, можно спросить, почему оно когда-либо используется.Есть две причины: первая (уже упоминавшаяся) состоит в том, что время жизни объекта не соответствует времени жизни, напрямую управляемому компилятором.Во-вторых, вы заранее не знаете размер (или количество элементов).Причина, по которой большинство реализаций списка будут динамически распределять узлы, заключается в том, что вы обычно не знаете точно, сколько узлов вам потребуется заранее.Весь смысл списка в том, что это динамическая структура без фиксированного размера.

1 голос
/ 14 июня 2011

Технически правильны оба.

Разница: во-первых, память будет выделяться в стеке, и вам не нужно заботиться об освобождении памяти и т. Д. Компилятор позаботится обо всем.Во-вторых, память будет выделяться в куче, и вам нужно позаботиться об освобождении памяти.

Почему большинство книг использует Второй способ: хотя вам нужно позаботиться о распределении памяти (используя new, malloc () и т. Д.) и освобождение памяти (delete, free ()), есть несколько преимуществ:

  1. Во многих случаях вам необходимо получить доступ к памяти вне области видимости переменной (например, за пределами функции где переменнаяопределено).Вы не сможете сделать это, если не выделите память в куче.Однако, если вы используете указатели, вы можете сделать это очень легко.

  2. Вы можете выполнять сложные и умные операции, используя указатели.Однако это затрудняет понимание вашего кода.

Я бы посоветовал вам привыкнуть к второму способу, так как он очень мощный и умный (за счет сложности кода).

1 голос
/ 14 июня 2011

Разница в том, где удаляются узлы.

В первом примере они удаляются автоматически по возвращении из функции, в которой они были объявлены. Вы не можете удалить их вручную.

Во втором примере вам нужно удалить узлы, используя ключевое слово «delete».Это означает, что они не удаляются автоматически, но дает вам больше гибкости в том, как долго будут зависать узлы.

0 голосов
/ 14 июня 2011

Использование оператора -> является обязательным при работе с динамически размещаемыми объектами. Например: представьте, что у вас есть класс List, у которого есть метод getRoot (), который возвращает Nodes. Узлы также имеют метод getNext (), который дает вам следующий узел в списке.

*List myList;

Предположим, мы знаем, что myList - это список с 5 узлами. Список и 5 узлов расположены в куче (динамически распределяются). Теперь мы хотим получить доступ к последнему узлу.

myList->getRoot()->getNext()->getNext()->getNext()->getRoot();
(*(*(*(*(*myList).geRoot() ).getNext()).getNext()).getNext()).getNext();

Эти два предложения эквивалентны и вернут пятый и последний узел. Для здравомыслия программиста используйте первый.

0 голосов
/ 14 июня 2011

Да. Оба примера верны. Указатели на объекты могут получать доступ к функциям в структурах и классах с помощью оператора ->. Память для указателей будет выделяться в куче. Оператор . используется объектами для доступа к функциям. Память для них будет в стеке.

0 голосов
/ 14 июня 2011

Это https://users.cs.jmu.edu/bernstdh/web/common/lectures/slides_cpp_dynamic-memory.php должно четко объяснить динамическое распределение памяти

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...