Я раньше много работал со связанными списками на Java, но я новичок в C ++. Я отлично использовал этот класс узла, который мне дали в проекте.
class Node
{
public:
Node(int data);
int m_data;
Node *m_next;
};
но у меня был один вопрос, на который я не очень хорошо ответил. Зачем нужно использовать
Node *m_next;
чтобы указать на следующий узел в списке вместо
Node m_next;
Я понимаю, что лучше использовать указательную версию; Не буду спорить с фактами, но не знаю, почему так лучше. Я получил не очень четкий ответ о том, как указатель лучше подходит для распределения памяти, и мне было интересно, может ли кто-нибудь здесь помочь мне лучше понять это.
c++
pointers
linked-list
m0meni
источник
источник
Node m_next
не является ссылкой на узел, это хранилище для всегоNode
самого себя.Ответы:
Это не просто лучше, это единственно возможный способ.
Если бы вы сохранили
Node
объект внутри себя, что былоsizeof(Node)
бы? Было быsizeof(int) + sizeof(Node)
, что было бы равноsizeof(int) + (sizeof(int) + sizeof(Node))
, что было бы равно иsizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))
т. Д. До бесконечности.Такой объект не может существовать. Это невозможно .
источник
В Java
хранит указатель на другой узел. У вас нет выбора. В C ++
означает то же самое. Разница в том, что в C ++ вы можете хранить объект, а не указатель на него. Вот почему вы должны сказать, что вам нужен указатель. В C ++:
означает хранить узел прямо здесь (и это явно не может работать для списка - вы получаете рекурсивно определенную структуру).
источник
Node
будет вызван собственный конструктор члена , который создаст другой новый экземпляр ... и вы получите бесконечную псевдорекурсию. На самом деле это не столько проблема размера, если говорить строго и буквально, сколько проблема производительности.Node
что на самом деле находится внутри первогоNode
. Таким образом, вы создаете указатель, который, по сути, является тем, как Java обрабатывает объекты, а не примитивы. Когда вы вызываете метод или создаете переменную, Java не сохраняет копию объекта или даже сам объект; он хранит ссылку на объект, который по сути представляет собой указатель с обернутым вокруг него кусочком детской перчатки. Об этом, по сути, говорят оба ответа.C ++ - это не Java. Когда ты пишешь
в Java это то же самое, что писать
в C ++. В Java указатель неявный, в C ++ - явный. Если вы напишете
в C ++ вы помещаете экземпляр
Node
прямо внутри объекта, который вы определяете. Он всегда присутствует, и его нельзя пропустить, его нельзя выделитьnew
и удалить. Этого эффекта невозможно достичь в Java, и он полностью отличается от того, что делает Java с тем же синтаксисом.источник
Вы используете указатель, иначе ваш код:
… Не компилируется, поскольку компилятор не может вычислить размер
Node
. Это потому, что это зависит от самого себя, а это означает, что компилятор не может решить, сколько памяти он будет потреблять.источник
k == sizeof(Node)
удержания и у вашего типа есть данные, он также должен будет их удерживать,sizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)
а затемsizeof(Node) > sizeof(Node)
.aleph_0
работает. (Просто излишне педантично :-))sizeof
было беззнаковым целым типом, поэтому есть надежда на трансфинитные или даже реальные размеры. (будучи еще более педантичным!: p)Node
он даже не определен до конца этого фрагмента, поэтому вы не можете использовать его внутри. Разрешение неявно объявлять указатели на еще не объявленный класс - это небольшая хитрость, разрешенная языком для того, чтобы сделать такие структуры возможными, без необходимости постоянно приводить указатели явно.Последний (
Node m_next
) должен содержать узел. Это не будет указывать на это. И тогда не было бы связывания элементов.источник
Подход , который вы описываете совместим не только с C ++, но и с его ( в основном) языка подмножество C . Обучение разработке связанного списка в стиле C - хороший способ познакомиться с методами низкоуровневого программирования (такими как ручное управление памятью), но обычно это не лучшая практика для современной разработки на C ++.
Ниже я реализовал четыре варианта управления списком элементов в C ++.
raw_pointer_demo
использует тот же подход, что и ваш - требуется ручное управление памятью с использованием необработанных указателей. Здесь C ++ используется только для синтаксического сахара , а используемый подход в остальном совместим с языком C.shared_pointer_demo
списке управление по-прежнему выполняется вручную, но управление памятью автоматическое (не использует исходные указатели). Это очень похоже на то, что вы, вероятно, испытали с Java.std_list_demo
используетlist
контейнер стандартной библиотеки . Это показывает, насколько легче становится, если вы полагаетесь на существующие библиотеки, а не на собственные.std_vector_demo
используетvector
контейнер стандартной библиотеки . Это управляет хранением списка в едином непрерывном распределении памяти. Другими словами, нет указателей на отдельные элементы. В некоторых довольно крайних случаях это может стать существенно неэффективным. Однако для типичных случаев это рекомендуемый передовой метод управления списками в C ++ .Примечание: из всего этого только
raw_pointer_demo
фактически требует, чтобы список был явно уничтожен, чтобы избежать «утечки» памяти. Остальные три метода автоматически уничтожат список и его содержимое, когда контейнер выйдет за пределы области видимости (при завершении функции). Дело в том, что C ++ в этом отношении может быть очень «Java-подобным», но только если вы решите разрабатывать свою программу с использованием имеющихся в вашем распоряжении высокоуровневых инструментов.источник
Node_reference
выше объявление касается одного из наиболее интересных различий на уровне языков между Java и C ++. В Java при объявлении объекта типаNode
неявно используется ссылка на отдельно выделенный объект. В C ++ у вас есть выбор между выделением ссылки (указатель) или прямым (стек), поэтому вы должны явно обрабатывать различие. В большинстве случаев вы будете использовать прямое размещение, но не для элементов списка.обзор
В C ++ есть 2 способа ссылаться на объекты и выделять их, а в Java - только один.
Чтобы объяснить это, на следующих диаграммах показано, как объекты хранятся в памяти.
1.1 Элементы C ++ без указателей
Предупреждение : синтаксис C ++, используемый в этом примере, аналогичен синтаксису в Java. Но распределение памяти другое.
1.2 Элементы C ++ с использованием указателей
Если вы проверите разницу между обоими способами, вы увидите, что в первом методе элемент адреса распределяется внутри клиента, а во втором способе вы должны создавать каждый адрес явно.
Предупреждение: Java выделяет объекты в памяти, как этот второй метод, но синтаксис аналогичен первому способу, что может сбить с толку новичков в «C ++».
Реализация
Таким образом, ваш пример списка может быть чем-то похожим на следующий пример.
Резюме
Поскольку связанный список содержит переменное количество элементов, память выделяется по мере необходимости и по мере доступности.
ОБНОВИТЬ:
Также стоит упомянуть, как @haccks прокомментировал свой пост.
Иногда ссылки или указатели на объекты указывают на вложенные элементы (также известные как «композиция UML»).
А иногда ссылки или указатели объектов указывают на внешние элементы (также известные как «агрегирование UML»).
Но вложенные элементы одного и того же класса не могут применяться с техникой «без указателя».
источник
С другой стороны, если самый первый член класса или структуры является следующим указателем (поэтому нет виртуальных функций или любой другой функции класса, которая означала бы, что следующий не является первым членом класса или структуры), тогда вы может использовать «базовый» класс или структуру только со следующим указателем, а также использовать общий код для основных операций со связанными списками, таких как добавление, вставка перед, извлечение с начала, .... Это связано с тем, что C / C ++ гарантирует, что адрес первого члена класса или структуры совпадает с адресом класса или структуры. Класс или структура базового узла будет иметь только следующий указатель, который будет использоваться базовыми функциями связанного списка, тогда при необходимости будет использоваться приведение типов для преобразования между типом базового узла и типами «производных» узлов. Боковое примечание - в C ++, если класс базового узла имеет только следующий указатель,
источник
Причина в том, что при создании
Node
объекта компилятор должен выделить для этого объекта память, и для этого рассчитывается размер объекта.Размер указателя на любой тип известен компилятору, поэтому размер объекта может быть вычислен с помощью самореферентного указателя.
Если
Node m_node
вместо этого используется, то компилятор не имеет представления о размере,Node
и он застрянет в бесконечной рекурсии вычисленийsizeof(Node)
. Всегда помните: класс не может содержать члена своего собственного типа .источник
Потому что это в C ++
эквивалентно этому в Java
где они оба создают новый объект с
MyClass
использованием конструктора по умолчанию.источник
Конечно, есть банальный ответ.
Если бы они не связали один узел с другим указателем, они не были бы связанными списками .
Связанные списки существуют потому, что мы хотим объединять объекты в цепочки. Например: у нас уже откуда-то есть объект. Теперь мы хотим поместить этот фактический объект (а не копию), например, в конец очереди. Это достигается путем добавления ссылки из последнего элемента, уже находящегося в очереди, на запись, которую мы добавляем. Говоря машинным языком, это заполнение слова адресом следующего элемента.
источник