En general, conozco dos formas de diseñar una estructura de datos de lista enlazada genérica en C. Y me pregunto cuál es mejor. Antes de hacer la pregunta, voy a introducir ambos métodos en breve:Dos formas de implementar una lista vinculada: ¿cuál es mejor?
Un método consiste en construir funciones alrededor de una estructura como la siguiente:
struct list_element {
struct list_element *prev;
struct list_element *next;
void *data;
};
Obviamente, el puntero apunta a los datos de la carga útil. La estructura del elemento de lista está fuera de la carga útil. Esto es, p. cómo glib ha diseñado su facilidad de lista de doble enlace: http://library.gnome.org/devel/glib/2.26/glib-Doubly-Linked-Lists.html
Otro método es la forma en que se hace en el kernel de Linux: http://isis.poly.edu/kulesh/stuff/src/klist/. No hay puntero de vacío en la carga útil en la estructura del elemento de lista. En cambio, la estructura de lista de elementos está incluido en la estructura de carga útil:
struct list_element {
struct list_element *prev;
struct list_element *next;
};
struct person {
char name[20];
unsigned int age;
struct list_element list_entry;
};
Una macro especial se utiliza para obtener un puntero a la carga útil struct dado un puntero a la list_entry, su nombre en el canto estructura de carga útil y el tipo de la estructura de carga útil (la macro list_entry()).
Finalmente, aquí está la pregunta: ¿Cuál es la ventaja de este último de los dos métodos para construir una lista vinculada? Algunas veces escuché a la gente decir que el segundo es más "genérico" que el primero, pero ¿por qué? Incluso diría que el primer método es más genérico porque las estructuras de carga útil son independientes de la implementación de la lista, que no es el caso con el segundo método.
Una desventaja más del segundo método es que si desea colocar la carga en más de una lista, debe incluir un elemento struct list_element para cada lista en la estructura de carga.
Editar: Para resumir hasta ahora vi dos respuestas que eran importantes para mí:
- Con el primer método: la eliminación de una carga útil de la lista consiste en bucle a través de la lista completa hasta que el elemento de la lista que apunta a la carga útil se encuentra. No necesita hacer esto con el segundo método. (Respuesta de Patrick)
- Con el primer método, debe hacer dos malloc() s para cada elemento: uno para la carga útil y otro para la estructura del elemento de lista. Con el segundo método, un malloc() es suficiente. (Respuesta de Roddy)
¿Por qué quiere elementos en su lista que no contienen ningún dato? Tal vez debería tener en cuenta que en el primer método, todos los punteros vacíos de una lista apuntan siempre a estructuras del mismo tipo. – Bart
La segunda opción no exige que todos los nodos sean del mismo tipo. Si modifica los requisitos un poco para que 'struct list_element' sea siempre el primer miembro, entonces los métodos' next' y 'prev' son fáciles de implementar genéricamente, y pueden apuntar a cualquier tipo de estructura, lo que no tiene por qué ser el mismo tipo de estructura que el último nodo. (Si cree que esto no es seguro y es peligroso, no puede ser menos inseguro que 'void *'.) –
@Bart Es posible que tenga un algoritmo que requiera temporalmente nodos de lista sin datos. Tal vez estés reorganizando los datos, cambiando su orden. – kazanaki