Nesta entrevista ao Slashdot, Linus Torvalds é citado como tendo dito:
Eu já vi muitas pessoas que excluem uma entrada de lista vinculada individualmente acompanhando a entrada "anterior" e, em seguida, para excluir a entrada, fazendo algo como
se (anterior) anterior-
> próxima = entrada-> próxima;
else
list_head = entry-> next;e sempre que vejo código assim, simplesmente digo "Essa pessoa não entende ponteiros". E, infelizmente, é bastante comum.
As pessoas que entendem ponteiros simplesmente usam um "ponteiro para o ponteiro de entrada" e inicializam isso com o endereço do list_head. E então, ao percorrer a lista, eles podem remover a entrada sem usar condicionais, apenas executando "* pp = entry-> next".
Como desenvolvedor de PHP, não toquei em indicadores desde a introdução ao C na universidade há uma década. No entanto, sinto que este é um tipo de situação com a qual devo pelo menos estar familiarizado. Sobre o que Linus está falando? Para ser sincero, se me pedissem para implementar uma lista vinculada e remover um item, a maneira 'errada' acima é a maneira que eu faria. O que preciso saber para codificar como Linus diz melhor?
Estou perguntando aqui, e não no Stack Overflow, pois não estou tendo um problema com isso no código de produção.
prev
, em vez de armazenar o nó inteiro, pode simplesmente armazenar a localizaçãoprev.next
, já que essa é a única coisa na qual você está interessado. Um ponteiro para um ponteiro. E se você fizer isso, evite o toloif
, já que agora não tem o caso estranho delist_head
ser um ponteiro de fora de um nó. O ponteiro para o início da lista é semanticamente o mesmo que o ponteiro para o próximo nó.Respostas:
Usando minhas habilidades no L331 MS Paint:
A solução original é apontar para Nós via
curr
. Nesse caso, você verifica se o próximo nó depoiscurr
tem o valor de exclusão e, se sim, redefine o ponteiro docurr
nónext
. O problema é que não há um nó que aponte para o início da lista. Isso significa que deve haver um caso especial para verificar isso.O que Linus (provavelmente) propõe, em vez disso, não é salvar o ponteiro no nó examinado atual, mas o ponteiro no ponteiro no nó atual (rotulado
pp
). A operação é a mesma - se opp
ponteiro apontar para um nó com o valor correto, você redefine opp
ponteiro.A diferença vem no começo da lista. Embora não haja Nó que aponte para o início da lista, na verdade, existe um ponteiro para o início da lista. E é da mesma forma um ponteiro para um nó, assim como outro
next
ponteiro de nós . Portanto, não há necessidade de uma cláusula especial para o início da lista.fonte
list_head
apontar para algo com umnext
nó que aponte para o primeiro item de dados reais (e tenha sidoprev
inicializado para o mesmo objeto fictício). Não gosto da ideia deprev
apontar para algo de tipo diferente, pois esses truques podem introduzir o comportamento indefinido por meio de alias e tornar o código desnecessariamente sensível ao layout da estrutura.prev
apontar para nós, ele aponta para ponteiros. Sempre aponta para algo do mesmo tipo, ou seja, um ponteiro para um Nó. Sua sugestão é essencialmente a mesma -prev
aponte para algo "com umnext
nó". Se você descartar o shell, basta obter olist_head
ponteiro inicial . Ou, em outras palavras - algo que é definido apenas com um ponteiro para o próximo nó, é semanticamente equivalente a um ponteiro para um nó.list_head
enext
manterá o mesmo "tipo" de ponteiro. Não é um problema em C, talvez, mas talvez seja problemático em C ++.Exemplo de código
Se precisarmos de alguma lógica para destruir o nó, basta adicionar uma linha de código no final:
fonte