Eu trabalhei com listas vinculadas antes extensivamente em Java, mas sou muito novo em C ++. Eu estava usando essa classe de nó que me foi dada em um projeto muito bem
class Node
{
public:
Node(int data);
int m_data;
Node *m_next;
};
mas eu tinha uma pergunta que não foi respondida muito bem. Por que é necessário usar
Node *m_next;
para apontar para o próximo nó na lista em vez de
Node m_next;
Eu entendo que é melhor usar a versão do ponteiro; Não vou discutir fatos, mas não sei por que é melhor. Eu recebi uma resposta não tão clara sobre como o ponteiro é melhor para alocação de memória e fiquei pensando se alguém aqui poderia me ajudar a entender isso melhor.
c++
pointers
linked-list
m0meni
fonte
fonte
Node m_next
não é uma referência a um nó, é armazenamento para o todo emNode
si.Respostas:
Não é apenas melhor, é a única maneira possível.
Se você armazenasse um
Node
objeto dentro de si, o que seriasizeof(Node)
? Seriasizeof(int) + sizeof(Node)
, o que seria igualsizeof(int) + (sizeof(int) + sizeof(Node))
, o que seria igualsizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node)))
, etc. ao infinito.Um objeto como esse não pode existir. É impossível .
fonte
Em Java
armazena um ponteiro para outro nó. Você não tem escolha. Em C ++
significa a mesma coisa. A diferença é que, em C ++, você pode realmente armazenar o objeto em oposição a um ponteiro para ele. É por isso que você tem que dizer que deseja um ponteiro. Em C ++:
significa armazenar o nó aqui (e isso claramente não pode funcionar para uma lista - você acaba com uma estrutura definida recursivamente).
fonte
Node
seria chamado, o que instanciaria outra nova instância ... e você obteria pseudo-recursão interminável. Não é realmente uma questão de tamanho em termos completamente estritos e literais, pois é uma questão de desempenho.Node
que está realmente dentro da primeiraNode
. Então, você cria um ponteiro, que é essencialmente como o Java lida com objetos, em vez de primitivos. Quando você chama um método ou cria uma variável, o Java não armazena uma cópia do objeto ou mesmo do próprio objeto; ele armazena uma referência a um objeto, que é essencialmente um ponteiro com um pouco de uma luva infantil enrolada em torno dele. É isso que as duas respostas estão dizendo essencialmente.C ++ não é Java. Quando você escreve
em Java, é o mesmo que escrever
em C ++. Em Java, o ponteiro está implícito, em C ++, é explícito. Se você escrever
em C ++, você coloca uma instância
Node
ali dentro do objeto que está definindo. Está sempre lá e não pode ser omitido, não pode ser alocadonew
e não pode ser removido. Esse efeito é impossível de obter em Java e é totalmente diferente do que Java faz com a mesma sintaxe.fonte
Você usa um ponteiro, caso contrário, seu código:
… Não seria compilado, pois o compilador não pode calcular o tamanho de
Node
. Isso ocorre porque depende de si mesmo - o que significa que o compilador não pode decidir quanta memória consumiria.fonte
k == sizeof(Node)
retém e o seu tipo possui dados, ele também deve ser retidosizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)
e depoissizeof(Node) > sizeof(Node)
.aleph_0
funciona. (Just ser excessivamente pedante :-))sizeof
seja um tipo integral não assinado, então existe a esperança de tamanhos transfinitos ou mesmo reais. (sendo ainda mais pedante: p!)Node
nem sequer é definido antes do final deste trecho, portanto você não pode usá-lo realmente por dentro. Permitir que um ponteiro declarar implicitamente para a frente para uma classe ainda não declarada é uma pequena trapaça permitida pelo idioma para possibilitar tais estruturas, sem a necessidade de lançar ponteiros explicitamente o tempo todo.O último (
Node m_next
) teria que conter o nó. Não apontaria para isso. E então não haveria ligação de elementos.fonte
A abordagem que você descreve é compatível não apenas com C ++, mas também com a sua (principalmente) linguagem subconjunto C . Aprender a desenvolver uma lista vinculada no estilo C é uma boa maneira de se apresentar a técnicas de programação de baixo nível (como gerenciamento manual de memória), mas geralmente não é uma prática recomendada para o desenvolvimento moderno de C ++.
Abaixo, implementei quatro variações de como gerenciar uma lista de itens em C ++.
raw_pointer_demo
usa a mesma abordagem que a sua - gerenciamento manual de memória necessário com o uso de ponteiros brutos. O uso de C ++ aqui é apenas para açúcar sintático , e a abordagem usada é compatível com a linguagem C.shared_pointer_demo
lista, o gerenciamento ainda é feito manualmente, mas o gerenciamento de memória é automático (não usa ponteiros brutos). Isso é muito semelhante ao que você provavelmente já experimentou com Java.std_list_demo
usa olist
contêiner da biblioteca padrão . Isso mostra como as coisas ficam mais fáceis se você confiar nas bibliotecas existentes, em vez de criar suas próprias.std_vector_demo
usa ovector
contêiner da biblioteca padrão . Isso gerencia o armazenamento da lista em uma única alocação de memória contígua. Em outras palavras, não há indicadores para elementos individuais. Para certos casos bastante extremos, isso pode se tornar significativamente ineficiente. Para casos típicos, no entanto, essa é a melhor prática recomendada para o gerenciamento de listas em C ++ .Nota: De todos esses itens, apenas os que
raw_pointer_demo
realmente exigem que a lista seja destruída explicitamente para evitar "vazamento" de memória. Os outros três métodos destruiriam automaticamente a lista e seu conteúdo quando o contêiner ficar fora do escopo (na conclusão da função). O ponto é: o C ++ é capaz de ser muito "semelhante ao Java" a esse respeito - mas somente se você optar por desenvolver seu programa usando as ferramentas de alto nível à sua disposição.fonte
Node_reference
declaração acima aborda uma das diferenças mais interessantes no nível da linguagem entre Java e C ++. Em Java, declarar um objeto do tipoNode
implicitamente usaria uma referência a um objeto alocado separadamente. No C ++, você tem a opção de alocação de referência (ponteiro) vs. alocação direta (pilha) - portanto, você deve lidar com a distinção explicitamente. Na maioria dos casos, você usaria alocação direta, embora não para elementos da lista.Visão geral
Existem 2 maneiras de referenciar e alocar objetos em C ++, enquanto em Java há apenas uma maneira.
Para explicar isso, os diagramas a seguir mostram como os objetos são armazenados na memória.
1.1 Itens C ++ sem ponteiros
Aviso : A sintaxe C ++ usada neste exemplo é semelhante à sintaxe em Java. Mas, a alocação de memória é diferente.
1.2 Itens C ++ usando ponteiros
Se você verificar a diferença entre os dois lados, verá que, na primeira técnica, o item de endereço é alocado no cliente, enquanto na segunda maneira, você deve criar cada endereço explicitamente.
Aviso: Java aloca objetos na memória como esta segunda técnica, mas a sintaxe é como a primeira maneira, o que pode ser confuso para os novatos no "C ++".
Implementação
Portanto, o exemplo da sua lista pode ser algo semelhante ao exemplo a seguir.
Resumo
Como uma lista vinculada tem uma quantidade variável de itens, a memória é alocada conforme necessário e, conforme disponível.
ATUALIZAR:
Também vale a pena mencionar, como @haccks comentou em seu post.
Às vezes, referências ou ponteiros de objeto indicam itens aninhados (também conhecido como "Composição UML").
E, às vezes, referências ou ponteiros de objeto indicam itens externos (também conhecido como "Agregação UML").
Porém, itens aninhados da mesma classe, não podem ser aplicados com a técnica "no-pointer".
fonte
Em uma nota lateral, se o primeiro membro de uma classe ou estrutura for o próximo ponteiro (portanto, nenhuma função virtual ou qualquer outro recurso de uma classe que significaria o próximo não será o primeiro membro de uma classe ou estrutura), então você pode usar uma classe ou estrutura "base" com apenas um próximo ponteiro e usar código comum para operações básicas de lista vinculada, como anexar, inserir antes, recuperar de frente, .... Isso ocorre porque o C / C ++ garante que o endereço do primeiro membro de uma classe ou estrutura seja o mesmo que o endereço da classe ou estrutura. A classe ou estrutura do nó base teria apenas um próximo ponteiro a ser usado pelas funções básicas da lista vinculada, e a conversão de tipo seria usada conforme necessário para converter entre o tipo de nó base e os tipos de nó "derivados". Nota lateral - em C ++, se a classe do nó base tiver apenas um próximo ponteiro,
fonte
O motivo é que, quando você cria um
Node
objeto, o compilador precisa alocar memória para esse objeto e para isso o tamanho do objeto é calculado.O tamanho do ponteiro para qualquer tipo é conhecido pelo compilador e, portanto, com o ponteiro auto-referencial, o tamanho do objeto pode ser calculado.
Se
Node m_node
for usado, o compilador não tem idéia do tamanho deNode
e ficará preso em uma recursão infinita de cálculosizeof(Node)
. Lembre-se sempre: uma classe não pode conter um membro de seu próprio tipo .fonte
Porque isso em C ++
é equivalente a isso em Java
onde ambos criam um novo objeto de
MyClass
usando o construtor padrão.fonte
É claro que há uma resposta trivial.
Se eles não vinculassem um nó ao outro por um ponteiro, eles não seriam listas vinculadas .
A existência de listas vinculadas é porque queremos ser capazes de encadear objetos. Por exemplo: já temos um objeto de algum lugar. Agora, queremos colocar esse objeto real (não uma cópia) no final de uma fila, por exemplo. Isso é obtido adicionando um link do último elemento já na fila à entrada que estamos adicionando. Em termos de máquina, é preencher uma palavra com o endereço do próximo elemento.
fonte