结构体和链表(有头双向链表)

结构体和链表

1. 结构体
1.1 结构体概述
  • 目前所学数据类型仅有基本数据类型 short int long,float double,char 。
  • 以上类型无法用于描述较为复杂的数据情况。例如: Person 人,无法使用单一类型完成对于当前 Person 的信息描述,本身 Person 信息具备一定【复合程度】,包括 ID 姓名,性别,年龄,信息。。。。

通过一定的手段将数据进行整合用于描述一个完整的数据类型,在 C 语言中采用的方式就是【 struct 结构体方式】,结构体是面向对象的雏形。

1.2 C语言结构体定义格式

使用结构体定义关键字: struct

struct 结构体数据类型名称
{
    结构体成员变量1【数据类型 变量名】;
    结构体成员变量2【数据类型 变量名】;
    结构体成员变量3【数据类型 变量名】;
    结构体成员变量4【数据类型 变量名】;
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 自定义一个结构体类型,采用多种不同的基本数据类型
// 整合为一个结构体类型,整个结构体类型的完整名称 struct person
struct person
{
    int id;
    char name[32];
    short age;
    char info[64];
    float salary;
};

/* 
类型定义 typedef,可以给予数据类型重新定义名称
size_t ==> typedef unsigned long size_t

typedef struct student {..} Student;
    Student 就是当前结构体的别名,可以直接使用,并且操作更为方便
*/
typedef struct student
{
    int id;
    char name[32];
    char class_name[16];
    int age;
    float score;
} Student; 


int main(int argc, char const *argv[])
{      
    /*
    struct person 是完整的数据类型 p1 是结构体变量
    */
    struct person p1;

    /*
    Student ==> typedef struct student 可以认为 Student 和 struct student 完全一致。
    Student 是一个数据类型
    */
    Student stu1;
    return 0;
}
1.3 结构体变量定义和初始化操作
typedef struct student
{
    int id;
    char name[32];
    short age;
    float score;
} Student;

int main(int argc, char const *argv[])
{
    /*
    定义结构体变量
    */
    Student stu;  

    /*
    【结构体变量】操作结构体内部数据,需要通过 . 运算符
        . ==> 的
    */
    // 赋值操作 
    stu.id = 23;
    // 使用 C 语言字符串拷贝函数
    strcpy(stu.name, "James");
    stu.age = 39;
    stu.score = 25.8F;

    // 取值操作
    printf("ID : %d\n", stu.id);
    printf("Name : %s\n", stu.name);
    printf("Age : %d\n", stu.age);
    printf("Score : %f\n", stu.score);

    return 0;
}
1.4 结构体类型堆区定义
typedef struct student
{
    int id;
    char name[32];
    short age;
    float score;
    char class_name[32];
} Student;

int main(int argc, char const *argv[])
{
    /*
    malloc 申请空间不会对空间进行擦除操作,为了防止野值问题,可以通过 memset 函数对申请的空间进行空间擦除。
    memset(void * ptr, char ch, size_t size);
            用户提供目标空间首地址,同时限制字节个数,将空间中的所有数据替换为目标字符 ch
    memset(stu1, 0, sizeof(Student));
            将 Student 对应的堆区空间进行擦除操作,使用的是【字符集编号为 0 的字符】该字符二进制数据为 		    0000 0000,相当于整个申请的内存空间都是 0
    */
    Student *stu1 = (Student *)malloc(sizeof(Student));
    memset(stu1, 0, sizeof(Student));

    Student *stu2 = (Student *)calloc(1, sizeof(Student));

    /*
    【结构体指针】想要操作结构体的数据内容需要使用 -> 运算符
        -> ===> 的
    */
    stu1->id = 1;
    strcpy(stu1->name, "Tom");
    stu1->age = 5;
    stu1->score = 100.0F;
    strcpy(stu1->class_name, "IOT-2303");

    stu2->id = 2;
    strcpy(stu2->name, "Jerry");
    stu2->age = 3;
    stu2->score = 100000.0F;
    strcpy(stu2->class_name, "IOT-2303");

    // 取值操作
    printf("ID : %d\n", stu1->id);
    printf("Name : %s\n", stu1->name);
    printf("Age : %d\n", stu1->age);
    printf("Score : %f\n", stu1->score);
    printf("ClassName : %s\n", stu1->class_name);

    printf("\n");

    printf("ID : %d\n", stu2->id);
    printf("Name : %s\n", stu2->name);
    printf("Age : %d\n", stu2->age);
    printf("Score : %f\n", stu2->score);
    printf("ClassName : %s\n", stu2->class_name);

    free(stu2);
    free(stu1);
    stu1 = NULL;
    stu2 = NULL;
}
1.5 结构体数组【重点】
typedef struct student
{
    int id;
    char name[32];
    short age;
} Student;

int main(int argc, char const *argv[])
{   
    // 定义了一个数组,数组中存储的元素都是 Student * 结构体类型指针
    // Student * student_array[10] = {NULL}
    Student ** student_array = (Student **)calloc(10, sizeof(Student *));

    for (int i = 0; i < 10; i++) 
    {   
        // 申请内存堆区空间,数据类型强转为 Student *
        Student * stu = (Student *)malloc(sizeof(Student));
        // Student * stu = (Student *)calloc(1, sizeof(Student));
        memset(stu, 0, sizeof(Student));

        // 赋值必要数据
        stu->id = i + 1;
        stu->age = i + 5;
        strcpy(stu->name, "Tom");

        // 数组中存储目标 Student 结构体指针
        student_array[i] = stu;
    }

    // 循环展示数据
    for (int i = 0; i < 10; i++) 
    {
        printf("ID : %d, Name : %s, age : %d\n",
            student_array[i]->id,
            student_array[i]->name,
            student_array[i]->age);
    }
    
    // 循环释放 malloc 申请的内存空间
    for (int i = 0; i < 10; i++) 
    {
        free(student_array[i]);
        student_array[i] = NULL;
    }

    free(student_array);
    student_array = NULL;

    return 0;
}
1.6 结构体嵌套

结构体作为另一个结构体的组成部分

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct birthday
{
    char year[8];
    char month[4];
    char day[4];
} Birthday;

typedef struct person
{
    int id;
    char name[32];
    // Person 结构体中使用的自定义结构体类型 Birthday 作为成员变量
    Birthday birthday;
} Person;

int main(int argc, char const *argv[])
{
    Person * p = (Person *)calloc(1, sizeof(Person));

    p->id = 1;
    strcpy(p->name, "James");

    // 通过 Person 赋值内部 Birthday 结构体数据
    strcpy(p->birthday.year, "1985");
    strcpy(p->birthday.month, "12");
    strcpy(p->birthday.day, "30");

    printf("ID : %d\n", p->id);
    printf("Name : %s\n", p->name);
    printf("Year : %s\n", p->birthday.year);
    printf("Month : %s\n", p->birthday.month);
    printf("Day : %s\n", p->birthday.day);

    free(p);
    p = NULL;

    return 0;
}
2. 链表【重点】
2.1 概述

非连续存储空间的数据结构,常用的链表形式

  • 有头双向链表【功能最完整】
  • 无头双向链表
  • 有头单向链表
  • 无头单向链表
  • 循环链表
3. 有头双向链表
3.1 有头双向链表结构格式
  • 结点 Node:

    存储目标数据,具备连接前一个和后一个结点的能力

  • 头结点 Head:

    存储当前链表中第一个结点地址和最后一个结点地址,同时存储当前链表中的有效元素个数

  • 头结点:

    数据类型名称 Linked_Head

// 头结点定义,数据类型重命名为 LinkedHead
typedef struct head
{	
    // 结点 Node 指针类型,存储链表中第一个 Node 结点地址
    struct node * first;
    // 结点 Node 指针类型,存储链表中最后一个 Node 结点地址
    struct node * last;
    // 当前双向链表中有效结点个数
    int size;
} LinkedHead;
  • 结点 :

    数据存储节点,数据类型名称 Node,当前结点存储数据为 Student 类型

typedef struct node
{	
    // 结点 Node 指针类型,当前 Node 结点存储上一个结点的首地址
    struct node * prev;
    // 结点 Node 指针类型,当前 Node 结点存储下一个结点的首地址
    struct node * next;
    // Node 结点中存储的数据内容,当前 Node 存储 Student 类型,存储数据为指针类型
    Student * stu;
    
} Node;
3.2 有头双向链表图例

在这里插入图片描述

3.3 有头双向链表相关代码实现代码实现
3.3.1 流程概述
  • 链表结构体声明

  • 创建新的链表头结点函数:

    • 无参有返回值
    • 函数返回值为 LinkedList 类型头结点的首地址
  • 创建新的学生结构体:

    • 有参有返回值
    • 参数为学生相关信息。函数返回值为 Student 类型的学生结构体首地址
  • 创建新结点函数:

    • 有参有返回值
    • 参数为 Student 类型的学生结构体首地址。函数返回值为 Node 类型的新结点首地址
  • 尾插函数(添加数据到链表尾部):

    • 有两种情况

      • 当链表中没有结点时

        head->first = new_node;
        head->last = new_node;
        
      • 当链表中有结点时

        new_node->prev = head->last;
        head->last->next = new_node;
        head->last = new_node;
        
    • 有参有返回值

    • 参数为头结点首地址和新结点首地址。函数返回值 int 类型, 添加成功,返回 EXIT_SUCCESS 告知函数代码成功执行添加操作, 否则返回 EXIT_FAILURE

  • 头插函数(添加数据到链表头部):

    • 有两种情况

      • 当链表中没有结点时

        head->first = new_node;
        head->last = new_node;
        
      • 当链表中有结点时

        new_node->next = head->first;
        head->first->prev = new_node;
        head->first = new_node;
        
    • 有参有返回值

    • 参数为头结点首地址和新结点首地址。函数返回值 int 类型, 添加成功,返回 EXIT_SUCCESS 告知函数代码成功执行添加操作, 否则返回 EXIT_FAILURE

  • 辅助函数:

    • 找指定下标函数
      • 找用户指定的下标地址
      • 有参有返回值
      • 参数为头结点首地址和用户指定的 int 类型的下标。函数返回值为 Node 类型的目标结点地址,如果没有找到,返回 NULL
    • 结点释放函数
      • 释放删除操作中需要进行内存释放的结点,需要释放的是 Student 类型空间和被删除的结点 Node 类型的空间。
      • 有参有返回值
      • 参数为被删除的结点首地址。 函数返回值 int 类型, 释放成功,返回 EXIT_SUCCESS 告知函数代码成功执行释放操作, 否则返回 EXIT_FAILURE
  • 指定下标地址添加函数:

    • 有三种情况

      • 当指定插入位置 index == 0 调用头插函数

      • 当指定插入位置 index == head->size 调用尾插函数

      • 指定插入位置不是以上两种情况时,首先利用找指定下标函数,找到指定下标对应的结点首地址 target

        new_node->prev = target->prev;
        new_node->next = target;
        target->prev->next = new_node;
        target->prev = new_node;
        
    • 有参有返回值函数

    • 参数为头结点首地址,指定下标和新结点首地址。函数返回值 int 类型, 添加成功,返回 EXIT_SUCCESS 告知函数代码成功执行添加操作, 否则返回 EXIT_FAILURE

  • 尾删函数:

    • 有两种情况

      • 当前链表里没有一个结点,直接终止函数运行

      • 当前链表中有结点时,先用临时变量 temp 存储被删除结点地址

        Node * temp = head->last;
        
        head->last = temp->prev;
        head->last->next = NULL;
        temp->prev = NULL;
        

        调用结点释放函数释放 temp 存储的结点申请的堆区内存空间。

    • 有参有返回值函数

    • 参数为头结点首地址。函数返回值 int 类型, 删除成功,返回 EXIT_SUCCESS 告知函数代码成功执行删除操作, 否则返回 EXIT_FAILURE

  • 头删函数:

    • 有两种情况

      • 当前链表里没有一个结点,直接终止函数运行

      • 当前链表中有结点时,先用临时变量 temp 存储被删除结点地址

        Node * temp = head->last;
        
        head->first = temp->next;
        head->first->prev = NULL;
        temp->next = NULL;
        

        调用结点释放函数释放 temp 存储的结点申请的堆区内存空间。

    • 有参有返回值函数

    • 参数为头结点首地址。函数返回值 int 类型, 删除成功,返回 EXIT_SUCCESS 告知函数代码成功执行删除操作, 否则返回 EXIT_FAILURE

  • 指定下标删除函数:

    • 有三种情况

      • 当指定下标 index == 0 时, 调用头删函数

      • 当指定下标 index == head->size - 1 , 调用尾删函数

      • 指定下标位置不是以上两种情况时,先调用查找指定下标函数,把找到的目标删除结点首地址存储到临时变量 temp

        Node *temp = find_node(head, index);
        
        temp->prev->next = temp->next;
        temp->next->prev = temp->prev;
        temp->prev = NULL;
        temp->next = NULL;
        

        调用结点释放函数释放 temp 存储的结点申请的堆区内存空间。

    • 有参有返回值函数

    • 参数为头结点首地址和指定下标。函数返回值 int 类型, 删除成功,返回 EXIT_SUCCESS 告知函数代码成功执行删除操作, 否则返回 EXIT_FAILURE

  • 展示链表包含学生信息函数:

    • 有参无返回值
    • 参数为链表头结点首地址
3.3.2 链表结构体声明
typedef struct student
{
    int id;
    char name[32];
    int age;
} Student;

// 结点类型 Node
typedef struct node
{   
    // 结点 Node 指针类型,当前 Node 结点存储上一个结点的首地址
    struct node * prev;
    // 结点 Node 指针类型,当前 Node 结点存储下一个结点的首地址
    struct node * next;
    // Node 结点中存储的数据内容,当前 Node 存储 Student 类型,存储数据为指针类型
    Student * stu;
} Node;

// 头结点定义,数据类型重命名为 LinkedHead
typedef struct head
{	
    // 结点 Node 指针类型,存储链表中第一个 Node 结点地址
    Node * first;
    // 结点 Node 指针类型,存储链表中最后一个 Node 结点地址
    Node * last;
    // 当前双向链表中有效结点个数
    int size;
} LinkedHead;
3.3.3 创建新的链表头 LinkedList
/*
创建 LinkedHead 链表头,也可以认为在创建链表基本结构

@return LinkedHead 数据在内存【堆区】首地址
*/
LinkedHead * create_new_linked()
{
    /*
    通过 calloc 函数申请对应的内存空间,申请空间。calloc 会
    内存空间进行擦除操作。
    内存擦除操作,对应内存所有二进制为都是 0,会根据不同的数据
    类型对外的数据情况不同
        如果是指针类型 ==> NULL
        如果是整数类型 ==> 0
        如果是 float 类型 ==> 0.0F
        如果是 double 类型 ==> 0.0
        如果是 char 类型 ==> '\0' 编码中编号为 0 的字符
    */
    return (LinkedHead *)calloc(1, sizeof(LinkedHead));
}
3.3.4 创建 Student 类型数据
/*
根据用户提供的必要参数创建一个 Student 结构体数据,返回值是
对应数据在内存【堆区】空间首地址

@param id   学员 ID 号
@param name 学员姓名
@param age  学员年龄
@return Student 类型在内存【堆区】的空间首地址
*/
Student * create_new_student(int id, char * name, int age) 
{
    // 申请内存堆区空间
    Student * stu = (Student *)calloc(1, sizeof(Student));

    // 给予当前创建的 Student 数据进行赋值操作
    stu->id = id;
    strcpy(stu->name, name);
    stu->age = age;

    // 返回对应空间的首地址。
    return stu;
}
3.3.5 创建新结点 Node
/*
创建一个新的 Node 结点,需要提供的参数是 Student 指针,
指向 Student 数据空间

@param stu Student 类型数据空间首地址
@return 创建的新的 Node 结点空间首地址
*/
Node * create_new_node(Student * stu)
{
    // 申请 Node 对应的数据空间
    Node * node = (Node *)calloc(1, sizeof(Node));
    // 当前新的 Node 结点中的 stu 赋值为当前参数 Student 地址
    node->stu = stu;

    return node;
}
3.3.6 添加数据到链表末尾(尾插函数)

在这里插入图片描述

/*
添加新 Node 到当前双向链表的末尾

@param head     	 LinkedHead 链表头指针
@param new_node Node 结点指针
@return 添加成功,返回 EXIT_SUCCESS, 否则返回 EXIT_FAILURE
*/
int add_last(LinkedHead * head, Node * new_node)
{
    // 用户提供的链表头和 Node 结点数据都是 NULL 
    // 无法进行添加操作
    if (NULL == head || NULL == new_node)
    {   
        printf("用户提供的参数有误!");
        // 添加失败
        return EXIT_FAILURE;
    }

    // 根据当前链表头中的 size 有效元素个数,判断当前链表中是否有其他元素。
    if (0 == head->size) 
    {
        // 情况一: 当前链表中没有任何一个有效元素
        head->first = new_node;
        head->last = new_node;
    }
    else 
    {
        // 情况二: 当前链表中有其他元素
        // new_node 的 prev 指向原本最后一个结点
        new_node->prev = head->last;
        // 原本最后一个结点的 next 指向新结点 new_node
        head->last->next = new_node;
        // head 中的 last 指向新结点 new_node
        head->last = new_node;
    }

    // 链表有效元素个数 += 1
    head->size += 1;

    return EXIT_SUCCESS;
}
3.3.7 添加新结点到链表头部(头插函数)

在这里插入图片描述

/*
添加新 Node 到当前双向链表的开端

@param head     LinkedHead 链表头指针
@param new_node Node 结点指针
@return 添加成功,返回 EXIT_SUCCESS, 否则返回 EXIT_FAILURE
*/
int add_first(LinkedHead *head, Node *new_node)
{
    // 判断用户提供的链表头和结点数据是否为 NULL
    if (NULL == head || NULL == new_node)
    {
        printf("用户提供的参数有误!%s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    if (head->size != 0)
    {
        // 当前链表中有其他结点
        // 【重点】建立新结点和原第一个结点的连接关系
        // 新结点中的 next 存储原本第一个结点地址
        new_node->next = head->first;

        // 原本第一个结点的 prev 保存新结点地址
        head->first->prev = new_node;

        // 头结点 head 中的 first 存储 new_node 结点地址
        head->first = new_node;
    }
    else
    {
        // 当前链表中没有其他结点
        // 链表头中的 first 存储当前 new_node 地址
        head->first = new_node;
        // 链表头中的 last 存储当前 new_node 地址
        head->last = new_node;
    }

    head->size += 1;

    return EXIT_SUCCESS;
}
3.3.8 辅助函数
  • 结点释放函数
/*
【辅助函数】释放删除操作中需要进行内存释放的结点
注意:
    需要释放 Student 类型空间
@param node 需要被释放的结点地址
@return 释放成功,返回 EXIT_SUCCESS, 否则返回 EXIT_FAILURE
*/
int free_node_memory(Node *node)
{
    if (NULL == node)
    {
        printf("结点数据为 NULL %s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    // 1. 释放 Student 空间
    free(node->stu);
    // 2. node 中的 stu 赋值为 NULL
    node->stu = NULL;
    // 3. 释放 Node 空间
    free(node);

    return EXIT_SUCCESS;
}
  • 指定下标获取函数

在这里插入图片描述

/*
【辅助函数】根据用户指定的下标位置,找到目标结点地址,如果没有找到,返回 NULL

@param head  LinkedHead 链表头指针
@param index 指定下标
@return 搜索成功返回目标 Node 结点地址,否则返回 NULL
*/
Node *find_node(LinkedHead *head, int index)
{
    if (NULL == head)
    {
        printf("提供的链表为 NULL, %s:%d\n", __FILE__, __LINE__);
        return NULL;
    }

    if (index < 0 || index > head->size - 1)
    {
        printf("下标不合法!, %s:%d\n", __FILE__, __LINE__);
        return NULL;
    }

    Node *target = NULL;

    if (index < head->size / 2)
    {
        // 从链表中的第一个结点开始搜索目标 index 下标对应的结点地址
        target = head->first->next;
        int count = 1;

        // count 作为下标计数器,如果 count == index 找到目标元素
        while (count != index)
        {
            // 未找到目标下标元素,target 赋值为当前结点的下一个结点。
            target = target->next;
            count++;
        }
    }
    else
    {
        // 从链表中的最后一个结点开始搜索目标 index 下标对应的结点地址
        target = head->last;
        int count = head->size - 1;

        while (count != index)
        {
            target = target->prev;
            count--;
        }
    }

    return target;
}

3.3.9 指定下标地址添加操作

在这里插入图片描述

/*
在当前链表中,指定下标位置添加新的 Node 结点

@param head     LinkedHead 链表头指针
@param index    指定添加数据的下标位置,链表下标参考数组形式,从 0 开始到 size - 1
@param new_node 添加的新结点
@return 添加成功,返回 EXIT_SUCCESS, 否则返回 EXIT_FAILURE
*/
int add(LinkedHead *head, int index, Node *new_node)
{
    // 给定两个指针数据判断是否为 NULL
    if (NULL == head || NULL == new_node)
    {
        printf("提供的链表或者结点为 NULL, %s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    // 判断提供的下标数据是否合法
    if (index < 0 || index > head->size)
    {
        printf("提供的下标越界, %s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    // 第一个大模块: 判断 index 是否为 0 或者 index == size
    if (0 == index)
    {
        // 下标为 0 采用的方式在添加数据到第一个结点
        return add_first(head, new_node);
    }
    else if (index == head->size)
    {
        // 下标数据为 size 采用的添加方式是添加到整个链表末尾
        return add_last(head, new_node);
    }

    // 第二个模块:找到目标下标位置对应的结点地址
    Node *target = find_node(head, index);

    // 第三个模式: 目标位置添加新结点
    // 核心步骤: new_node 和当前 target 结点以及 target 上一个结点连接
    new_node->next = target;
    new_node->prev = target->prev;

    // target 上一个结点和当前 new_node 连接
    new_node->prev->next = new_node;

    // 当前 new_node成为 target 的上一个结点
    target->prev = new_node;

    // 有效元素个数 + 1
    head->size += 1;

    return EXIT_SUCCESS;
}
3.3.10 删除最后一个结点函数

在这里插入图片描述

/*
删除当前链表中的最后一个结点

@param head LinkedHead 链表头指针
@return 删除成功,返回 EXIT_SUCCESS, 否则返回 EXIT_FAILURE
*/
int remove_last(LinkedHead *head)
{
    // 如果用户提供的链表头为 NULL 无法进行任何的代码操作
    if (NULL == head)
    {
        printf("用户提供的参数有误!%s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    // 情况一: 当前链表中没有任何一个元素 0 == size ,终止函数运行。
    if (0 == head->size)
    {
        return EXIT_FAILURE;
    }

    Node *free_node = head->last;

    if (head->size > 1)
    {
        // 链表中存在多个元素
        // 首先利用临时变量,存储当前被删除元素的上一个元素
        Node *temp = head->last->prev;

        // 断开连接 原本最后一个结点的 prev 赋值为 NULL
        head->last->prev = NULL;
        // 断开连接 原本最后一个结点的上一个结点的 next 赋值为 NULL
        temp->next = NULL;

        // 将 head 中的 last 指向 temp
        head->last = temp;
    }
    else
    {
        // 链表中有且只有一个元素
        // 链表头中的 first 和 last 同时赋值为 NULL
        head->first = NULL;
        head->last = NULL;
    }
    // 删除操作执行完毕,需要对 size -= 1
    head->size -= 1;

    return free_node_memory(free_node);
}

3.3.11 删除第一个元素函数

在这里插入图片描述

/*
删除当前链表中的第一个结点

@param head LinkedHead 链表头指针
@return 删除成功,返回 EXIT_SUCCESS, 否则返回 EXIT_FAILURE
*/
int remove_first(LinkedHead *head)
{
    if (NULL == head)
    {
        printf("用户提供的参数有误!%s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    // 情况一: 当前链表中没有元素
    if (0 == head->size)
    {
        return EXIT_FAILURE;
    }

    // 被删除的节点 Node
    Node *delete_node = head->first;

    if (1 == head->size)
    {
        // 情况二: 当前链表中有且只有一个元素
        head->first = NULL;
        head->last = NULL;
    }
    else
    {
        // 情况三: 当前链表中有多个元素
        // 保留第一个结点之后的结点地址
        Node *temp = head->first->next;

        // 断开原第一个结点和之后结点的联系
        head->first->next = NULL;
        temp->prev = NULL;

        // head->first 指向 temp
        head->first = temp;
    }

    // 有效元素个数 - 1
    head->size -= 1;

    return free_node_memory(delete_node);
}
3.3.12 指定下标地址删除操作

在这里插入图片描述

/*
在当前链表中,删除指定下标位置结点

@param head  LinkedHead 链表头指针
@param index 指定删除数据的下标位置,链表下标参考数组形式,从 0 开始到 size - 1
@return 删除成功,返回 EXIT_SUCCESS, 否则返回 EXIT_FAILURE
*/
int remove(LinkedHead *head, int index)
{
    if (NULL == head)
    {
        printf("提供的链表为 NULL, %s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    if (index < 0 || index > head->size - 1)
    {
        printf("下标不合法!, %s:%d\n", __FILE__, __LINE__);
        return EXIT_FAILURE;
    }

    Node *target = find_node(head, index);

    if (NULL == target)
    {
        return EXIT_FAILURE;
    }

    // 删除结点的下一个结点的 prev 赋值为当前删除结点的 prev
    target->next->prev = target->prev;

    // 删除结点的上一个结点的 next 赋值为当前删除结的 next
    target->prev->next = target->next;

    // target 数据进行清理和释放操作
    target->next = NULL;
    target->prev = NULL;

    // 有效元素 - 1
    head->size -= 1;

    return free_node_memory(target);
}
3.3.13 展示函数
void show(LinkedHead * head) 
{
    Node * temp = head->first;
    while (temp != NULL)
    {
        printf("ID : %d, Name : %s, Age : %d\n", 
            temp->stu->id,
            temp->stu->name,
            temp->stu->age
            );

        temp = temp->next;
    }
}
3.3.14 main 函数测试代码
int main(int argc, char const *argv[])
{   
    LinkedHead * head = create_new_linked();

    // 常规写法
    add_last(head, create_new_node(create_new_student(1, "James", 39)));
    add_last(head, create_new_node(create_new_student(2, "Tom", 18)));
    add_last(head, create_new_node(create_new_student(3, "Jerry", 19)));
    add_last(head, create_new_node(create_new_student(4, "Wade", 42)));

    // 展开写法
    Student * stu = create_new_student(5, "YAO", 45);
    Node * node = create_new_node(stu);
    add_last(head, node);

    show(head);

    return 0;
}
  • 41
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值