Notas del lenguaje C: habilidades de implementación de nodos de listas vinculadas: el uso mágico de la estructura

    Habilidades de implementación de nodos de listas vinculadas: el uso mágico de la estructura

La capacidad del autor es limitada. Si encuentra algún error durante el proceso de lectura, comuníquese conmigo para señalar los errores y evitar que los lectores aprendan el conocimiento incorrecto más tarde. ¡Gracias!


Mierda

Aunque el lenguaje C solo proporciona una sintaxis muy simple, no afecta a los programadores del lenguaje C que usen C para lograr muchas funciones avanzadas sorprendentes.

Este artículo presenta una técnica para implementar nodos de lista vinculados que son muy comunes en el lenguaje C.

Tal vez haya leído varios libros en lenguaje C y haya visto introducciones relevantes, pero no le importa mucho, así que aquí aprenderemos en detalle.

A continuación, describiremos la implementación de un nodo de lista vinculada. No se decepcione. Su implementación puede no ser tan simple como cree.

Definición de nodo

typedef struct _LIST_ENTRY {
  struct _LIST_ENTRY *Next;
} LIST_ENTRY, *PLIST_ENTRY;

LIST_ENTRY representa un nodo de la lista doblemente vinculada. A continuación se muestra un puntero al siguiente nodo.

Pero para los nodos anteriores, no podemos usarlo, porque no puede contener ninguna información adicional, excepto que puede representar un nodo.

Bueno, aquí asumimos que queremos crear una lista vinculada que represente a los alumnos. Primero definamos la estructura del alumno.

typedef struct _STUDENT {
  char name[64];
  int  age;
} STUDENT, *PSTUDENT;

Escribimos una estructura que representa a los estudiantes en cuestión, es muy simple, porque aquí solo la usamos para ilustrar si usamos LIST_ENTRY, y no queremos explicar cómo construir un sistema de gestión de estudiantes.

Para que la estructura ESTUDIANTE se convierta en un nodo de la lista vinculada, necesitamos fusionarlos, luego nuestra estructura ESTUDIANTE se vuelve así:

typedef struct _STUDENT {
  LIST_ENTRY list_entry;
  char name[64];
  int  age;
} STUDENT, *PSTUDENT;

Tenga en cuenta que anidamos la estructura LIST_ENTRY al comienzo de la estructura ESTUDIANTE, lo que hará que la implementación posterior sea mucho más simple. Ciertamente es posible colocarla en otras posiciones, pero complicará las cosas.

Para utilizar

Ahora que la estructura está definida, echemos un vistazo a cómo usamos esta estructura y la inteligencia de esta estructura, que es lo que este artículo quiere expresar.

Una vez más, este artículo es para describir la belleza del uso de esta estructura y no pretende implementar una lista enlazada completa, por lo que solo se proporciona la versión más rudimentaria.

#define GET_STUDENT(address, type, field) ((type *)( \
  (char *)(address) - \
  (char *)(&((type *)0)->field)))

PLIST_ENTRY list_header = NULL; // 链表头

// 在链表的尾部添加一个新的节点
int add_student(char* name, int age) {
  // create a student with the given parameters
  PSTUDENT student = malloc(sizeof(STUDENT));
  if (student == NULL)
    return -1;
  memset(student, 0, sizeof(STUDENT));

  strcpy(student->name, name);
  student->age = age;

  if (list_header == NULL) {
    list_header = &student->list_entry;
  } else {
    PLIST_ENTRY p = list_header;
    while (p->Next) {
      p = p->Next;
    }
    p->Next = &student->list_entry;
    // student->list_entry.Next is NULL
  }
}

int main() {

  // 添加两个节点
  add_student("student abc", 22);
  add_student("student ijk", 25);

  // 遍历整个链表
  请注意这里!!!!
  /////////////////////////////////////////////////////
  for (p = list_header; p != NULL; p = p->Next) {
    // get the student struct
    PSTUDENT student = GET_STUDENT(p, STUDENT, list_entry);
    // PSTUDENT student = (PSTUDENT)(((char*)p - (char*)(&((PSTUDENT)0)->list_entry)));
    printf("student name: %s, student age: %d\n", student->name, student->age);
  }
  /////////////////////////////////////////////////////

  // 省略释放内存的代码
  return 0;
}

Analizando

Si has visto su ingenio hasta ahora, no necesitas perder el tiempo para ver la siguiente parte.

¿Cuál es el punto del código anterior? El
punto es la macro GET_STUDENT .

Para facilitar la depuración, proporcionamos un formulario de expansión de macro de 42 líneas para facilitar la depuración.

  1. Lo primero que debe tener en cuenta es que el tipo de cada nodo en nuestra lista vinculada es ESTUDIANTE, no LIST_ENTRY.
  2. Sin embargo, debe tenerse en cuenta que el primer campo en nuestra estructura ESTUDIANTE es LIST_ENTRY, que es el requisito previo para que nuestro GET_STUDETN funcione correctamente.
  3. Entonces, ¿por qué funciona esto?
    Primero, agregue un punto de interrupción en la línea 42 para depurar, y obtenemos los siguientes resultados:

Inserte la descripción de la imagen aquí

Tenga en cuenta que la dirección de p en este momento es la misma que la dirección del alumno. Esto se debe a que LIST_ENTRY se coloca en la primera posición de la estructura ESTUDIANTE, y cuando agregamos un nuevo nodo a la lista, agregamos la estructura STUDETN. En este caso, podemos asignar un puntero de estructura ESTUDIANTE a un puntero de LIST_ENTRY.

Aquí estamos viendo el diseño interno de la misma estructura estudiantil:
Inserte la descripción de la imagen aquí

Aquí vemos primero que la dirección de memoria de nuestro alumno es 0x00000000600049fb0, este valor es el mismo que se muestra en la imagen de arriba, porque mi computadora es una máquina de 64 bits, por lo que la dirección ocupa 8 bytes.
Aquí analizamos estos bytes en detalle significa:
(1) porque el primer campo de nuestra estudiante es LIST_ENTRY y LIST_ENTRY contiene siguiente solamente un puntero al siguiente nodo en la lista y por lo tanto no hay una duda los anteriores ocho caracteres. 10a00400 06000000representar a la primera dirección del siguiente nodo del byte actual Tenga en cuenta que este es un segmento pequeño, por lo que es exactamente lo opuesto a la secuencia de bytes de dirección que se ve en la primera imagen, con el bit más significativo al final.
(2) El valor del byte analizado es la memoria del tercer al último byte Se usa para guardar student-> name
(3) Los dos últimos bytes representan student-> age, y su valor es un código de segmento pequeño 16.
4. A continuación, dejamos que el ciclo continúe, ubiquemos el segundo nodo de la lista vinculada, echemos un vistazo a la memoria Diseño:

Inserte la descripción de la imagen aquí

Verificar lo que hemos dicho más arriba, a la derecha. La segunda dirección de nodo 0x0000000060004a010, que coincidió con el 10a00400 06000000acuerdo, porque sabemos list_entry- primer nodo> es el nodo actual Siguiente señalando sus dos primeros bytes 0 , Como el Siguiente del nodo actual está vacío. El siguiente campo es el mismo que el resumen de (2) (3), no lo explicaremos aquí.

En resumen, la estructura de LIST_ENTRY y STUDENT utiliza inteligentemente las características del diseño de memoria de la estructura del lenguaje C y coloca la estructura de STUDENT en una lista vinculada de LIST_ENTRY. ¿Cuáles son sus ventajas?

Esto nos permite colocar cualquier estructura en la lista vinculada que definimos usando LIST_ENTRY, y no tenemos que definir campos relacionados para cada estructura que deba colocarse en la lista vinculada por separado para que puedan interconectarse. Después de hacerlo, somos iguales a la lógica relacionada con la lista vinculada Extraído de la estructura utilizada para almacenar información, difícilmente podemos prestar atención al tipo de estudiante real almacenado en la lista vinculada al escribir el método de operar la lista vinculada.

Cualquier idea es bienvenida.

Fin…

27 artículos originales publicados · 31 elogiados · 40,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/zhaoruixiang1111/article/details/103284681
Recomendado
Clasificación