数据结构之不带头结点和带头结点的单链表操作

// list.h 
#pragma once
#ifndef __LIST_H
#define __LIST_H

typedef struct _LinkNode {
    void* data;
    struct _LinkNode* next;
} LinkList, LinkNode;

typedef struct _List {
    LinkList* head;
    LinkList* tail;
    int (*Compar)(const void* key1, const void* key2);
    void (*Destroy)(void* data);
    void (*Display)(void* data);
} List;

#define LIST_DATA(node) ((node)->data)
#define LIST_NEXT(node) ((node)->next)
#define LIST_HEAD(L)    ((L)->head)
#define LIST_TAIL(L)    ((L)->tail)     

void ListInit(List* L,
    void (*Destroy)(void* data),
    int (*Compar)(const void* key1, const void* key2),
    void (*Display)(void* data));
void ListDestroy(List* L);
int  ListEmpty(List* L);
int  ListInsNext(List* L, LinkNode* node, const void* data);
int  ListRemNext(List* L, LinkNode* node, void** data);
int  ListSize(List* L);
int  ListInsByIdx(List* L, int idx, const void* data);
int  ListRemByIdx(List* L, int idx, void** data);
LinkNode* ListSearch(List* L, const void* data);
void ListDisplay(List* L);

void ListInit_WH(List* L,
    void (*Destroy)(void* data),
    int (*Compar)(const void* key1, const void* key2),
    void (*Display)(void* data));
void ListDestroy_WH(List* L);
int  ListEmpty_WH(List* L);
int  ListInsNext_WH(List* L, LinkNode* node, const void* data);
int  ListRemNext_WH(List* L, LinkNode* node, void** data);
int  ListSize_WH(List* L);
int  ListInsByIdx_WH(List* L, int idx, const void* data);
int  ListRemByIdx_WH(List* L, int idx, void** data);
LinkNode* ListSearch_WH(List* L, const void* data);
void ListDisplay_WH(List* L);

#endif

// list.c 
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "list.h"


/* 不带头结点的单链表操作 */
void ListInit(List* L,
    void (*Destroy)(void* data),
    int (*Compar)(const void* key1, const void* key2),
    void (*Display)(void* data))
{
    LIST_HEAD(L) = NULL;
    LIST_TAIL(L) = NULL;
    L->Destroy = Destroy;
    L->Compar = Compar;
    L->Display = Display;

    return;
}

void ListDestroy(List* L)
{
    LinkNode* p;

    for (p = LIST_HEAD(L); p != NULL; p = LIST_NEXT(p)) {
        L->Destroy(LIST_DATA(p));
        free(p);
    }

    (void)memset(L, 0, sizeof(List));
    return;
}

int  ListEmpty(List* L)
{
    if (!LIST_HEAD(L)) {
        return 1;
    }
    else {
        return 0;
    }
}

int  ListInsNext(List* L, LinkNode* node, const void* data)
{
    LinkNode* newNode;

    
    newNode = (LinkNode*)malloc(sizeof(LinkNode));
    if (!newNode) { return -1; }
    newNode->data = (void*)data;
    newNode->next = NULL;

    /* 当node为NULL时,表示在链表第一个结点之前插入 */
    if (node == NULL) {
        if (ListEmpty(L)) {
            LIST_TAIL(L) = newNode;
        }
        newNode->next = LIST_HEAD(L);
        LIST_HEAD(L) = newNode;
    }
    else {
        if (node->next == NULL) {
            LIST_TAIL(L) = newNode;
        }
        newNode->next = node->next;
        node->next = newNode;
    }
    return 0;
}

/* node为NULL,表示删除第一个结点 */
int  ListRemNext(List* L, LinkNode* node, void** data)
{
    LinkNode* p;

    if (ListEmpty(L)) {
        *data = NULL;
        return 0;
    }

    if (node == NULL) {
        p = LIST_HEAD(L);
        *data = p->data;
        LIST_HEAD(L) = p->next;
        if (p->next == NULL) { /* 链表只有一个结点 */
            LIST_TAIL(L) = NULL;
        }
    }
    else {
        if (node == LIST_TAIL(L)) {
            *data = NULL;
            return 0;
        }
        else {
            p = node->next;
            *data = p->data;
            node->next = p->next;
            if (p->next == NULL) {
                LIST_TAIL(L) = node;
            }
        }
    }
    free(p);
    return 0;
}

LinkNode* ListSearch(List* L, const void* data)
{
    LinkNode* p;

    for (p = LIST_HEAD(L); p != NULL; p = LIST_NEXT(p)) {
        if (L->Compar(p->data, (void*)data) == 0) {
            return p;
        }
    }
    return NULL;
}

int ListSize(List* L)
{
    int size = 0;
    LinkNode* p;

    for (p = LIST_HEAD(L); p != NULL; p = LIST_NEXT(p)) {
        size++;
    }

    return size;
}

int ListInsByIdx(List* L, int idx, const void* data)
{
    int size;
    int i;
    LinkNode* p;

    size = ListSize(L);
    if (idx < 1 || idx > size + 1) {
        return -1;
    }

    if (idx == 1) {
        return ListInsNext(L, NULL, data);
    }

    if (idx == size + 1) {
        return ListInsNext(L, LIST_TAIL(L), data);
    }

    i = 1;
    p = LIST_HEAD(L);
    while (p && (i < idx - 1)) {
        p = LIST_NEXT(p);
        i++;
    }

    return ListInsNext(L, p, data);
}

int ListRemByIdx(List* L, int idx, void** data)
{
    int size;
    int i;
    LinkNode* p;

    size = ListSize(L);
    if (idx < 1 || idx > size) {
        return -1;
    }

    if (idx == 1) {
        return ListRemNext(L, NULL, data);
    }

    i = 1;
    p = LIST_HEAD(L);
    while (p && (i < idx - 1)) {
        p = LIST_NEXT(p);
        i++;
    }

    return ListRemNext(L, p, data);
}

void  ListDisplay(List* L)
{
    LinkNode* p;

    if (ListEmpty(L)) {
        printf("\nList is Empty.");
        return ;
    }

    p = LIST_HEAD(L)->data;
    printf("\nHEAD:%d", *(int*)p);
    p = LIST_TAIL(L)->data;
    printf("\nTAIL:%d\n", *(int*)p);

    for (p = LIST_HEAD(L); p != NULL; p = LIST_NEXT(p)) {
        L->Display(p->data);
    }

    return;
}

/* 带头结点的单链表操作 */
void ListInit_WH(List* L,
    void (*Destroy)(void* data),
    int (*Compar)(const void* key1, const void* key2),
    void (*Display)(void* data))
{
    LinkNode* p;

    p = (LinkNode*)malloc(sizeof(LinkNode));
    if (!p) { return ; }
    p->next = NULL;

    LIST_HEAD(L) = p;
    LIST_TAIL(L) = p;
    L->Destroy = Destroy;
    L->Compar = Compar;
    L->Display = Display;

    return;
}

void ListDestroy_WH(List* L)
{
    LinkNode* p;

    for (p = LIST_HEAD(L)->next; p != NULL; p = LIST_NEXT(p)) {
        L->Destroy(LIST_DATA(p));
        free(p);
    }

    if (LIST_HEAD(L)) { free(LIST_HEAD(L)); }
    (void)memset(L, 0, sizeof(List));
    return;
}

int  ListEmpty_WH(List* L)
{
    LinkNode* p = LIST_HEAD(L)->next;

    if (!p) {
        return 1;
    }
    else {
        return 0;
    }
}

int  ListInsNext_WH(List* L, LinkNode* node, const void* data)
{
    LinkNode* newNode;

    if (!node) { return -1; }

    newNode = (LinkNode*)malloc(sizeof(LinkNode));
    if (!newNode) { return -1; }
    newNode->data = (void*)data;
    newNode->next = NULL;

    if (node->next == NULL) {
        LIST_TAIL(L) = newNode;
    }
    newNode->next = node->next;
    node->next = newNode;
    
    return 0;
}

int  ListRemNext_WH(List* L, LinkNode* node, void** data)
{
    LinkNode* p;

    if (ListEmpty_WH(L)) {
        *data = NULL;
        return 0;
    }

    if (node == LIST_TAIL(L)) {
        *data = NULL;
        return 0;
    }
    else {
        p = node->next;
        *data = p->data;
        node->next = p->next;
        if (p->next == NULL) {
            LIST_TAIL(L) = node;
        }
    }
    
    free(p);
    return 0;
}

LinkNode* ListSearch_WH(List* L, const void* data)
{
    LinkNode* p;

    for (p = LIST_HEAD(L)->next; p != NULL; p = LIST_NEXT(p)) {
        if (L->Compar(p->data, (void*)data) == 0) {
            return p;
        }
    }
    return NULL;
}

int ListSize_WH(List* L)
{
    int size = 0;
    LinkNode* p;

    for (p = LIST_HEAD(L)->next; p != NULL; p = LIST_NEXT(p)) {
        size++;
    }

    return size;
}

int ListInsByIdx_WH(List* L, int idx, const void* data)
{
    int size;
    int i;
    LinkNode* p;

    size = ListSize_WH(L);
    if (idx < 1 || idx > size + 1) {
        return -1;
    }

    if (idx == size + 1) {
        return ListInsNext_WH(L, LIST_TAIL(L), data);
    }
    
    i = 0;
    p = LIST_HEAD(L);
    while (p && (i < idx - 1)) {
        p = LIST_NEXT(p);
        i++;
    }

    return ListInsNext_WH(L, p, data);
}

int ListRemByIdx_WH(List* L, int idx, void** data)
{
    int size;
    int i;
    LinkNode* p;

    size = ListSize(L);
    if (idx < 1 || idx > size) {
        return -1;
    }

    i = 0;
    p = LIST_HEAD(L);
    while (p && (i < idx - 1)) {
        p = LIST_NEXT(p);
        i++;
    }

    return ListRemNext(L, p, data);
}

void  ListDisplay_WH(List* L)
{
    LinkNode* p;

    if (ListEmpty_WH(L)) {
        printf("\nList is Empty.");
        return;
    }

    p = LIST_HEAD(L)->next->data;
    printf("\nHEAD:%d", *(int*)p);
    p = LIST_TAIL(L)->data;
    printf("\nTAIL:%d\n", *(int*)p);

    for (p = LIST_HEAD(L)->next; p != NULL; p = LIST_NEXT(p)) {
        L->Display(p->data);
    }

    return;
}

// main.c
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "list.h"

void Destroy(void* data)
{
    if (data) {
        free((int*)data);
    }
}

int Compar(const void* key1, const void* key2)
{
    return *(int*)key1 - *(int*)key2;
}

void Display(void* data)
{
    printf("%d\t", *(int*)data);
}

void MenuList(void)
{
    printf("\n          单链表的操作");
    printf("\n==================================");
    printf("\n|   1--第一个结点之前插入");
    printf("\n|   2--第一个结点之后插入");
    printf("\n|   3--最后一个结点之后插入");
    printf("\n|   4--指定位置插入");
    printf("\n|   5--删除第一个结点");
    printf("\n|   6--指定位置删除");
    printf("\n|   7--查找");
    printf("\n|   8--显示");
    printf("\n|   0--返回");
    printf("\n==================================");
    printf("\n请输入菜单号(0~8):");
}

int ListOperation(void)
{
    List L;
    char ch1, ch2;
    int elem = 12345678, idx = 0;
    int* oldElem, * newElem;
    int ret;

    ListInit(&L, Destroy, Compar, Display);

    ch1 = 'y';
    while (ch1 == 'y' || ch1 == 'Y') {
        MenuList();

        scanf_s("%c", &ch2, 1);
        (void)getchar();
        switch (ch2) {
        case '1':
            printf("\n请输入插入的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsNext(&L, NULL, (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
                Destroy((void*)newElem);
            }
            break;
        case '2':
            printf("\n请输入插入的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsNext(&L, LIST_HEAD(&L), (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
            }
            break;
        case '3':
            printf("\n请输入插入的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsNext(&L, LIST_TAIL(&L), (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
            }
            break;
        case '4':
            printf("\n请输入插入的位置(1~%d)和元素值:", ListSize(&L) + 1);
            scanf_s("%d,%d", &idx, &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsByIdx(&L, idx, (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
            }
            break;
        case '5':
            ret = ListRemNext(&L, NULL, (void**)(&oldElem));
            if (!ret) {
                printf("\n删除成功!");
            }
            else {
                printf("\n删除失败!");
            }
            break;
        case '6':
            if (!ListSize(&L)) {
                printf("\n链表序列为空!");
                break;
            }
            printf("\n请输入结点位置(1~%d):", ListSize(&L));
            scanf_s("%d", &idx);
            (void)getchar();
            ret = ListRemByIdx(&L, idx, (void**)(&oldElem));
            if (!ret) {
                printf("\n删除成功!");
            }
            else {
                printf("\n删除失败!");
            }
            break;
        case '7':
            printf("\n请输入要查找的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            if (ListSearch(&L, (void*)(&elem))) {
                printf("\n查找成功!");
            }
            else {
                printf("\n查找失败");
            }
            break;
        case '8':
            printf("\n链表序列:");
            ListDisplay(&L);
            break;
        case '0':
            ch1 = 'n';
            break;
        default:
            printf("\n输入有误,请输入0~8进行选择!");
            break;
        }
    }

    return 0;
}

int ListOperation_WH(void)
{
    List L;
    char ch1, ch2;
    int elem, idx;
    int* oldElem, * newElem;
    int ret;

    ListInit_WH(&L, Destroy, Compar, Display);

    ch1 = 'y';
    while (ch1 == 'y' || ch1 == 'Y') {
        MenuList();

        scanf_s("%c", &ch2, 1);
        (void)getchar();
        switch (ch2) {
        case '1':
            printf("\n请输入插入的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsNext_WH(&L, LIST_HEAD(&L), (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
                Destroy((void*)newElem);
            }
            break;
        case '2':
            printf("\n请输入插入的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsNext_WH(&L, LIST_HEAD(&L)->next, (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
            }
            break;
        case '3':
            printf("\n请输入插入的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsNext_WH(&L, LIST_TAIL(&L), (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
            }
            break;
        case '4':
            printf("\n请输入插入的位置(1~%d)和元素值:", ListSize_WH(&L) + 1);
            scanf_s("%d,%d", &idx, &elem);
            (void)getchar();
            newElem = (int*)malloc(sizeof(int));
            if (!newElem) { exit(1); }
            *newElem = elem;
            ret = ListInsByIdx_WH(&L, idx, (void*)newElem);
            if (!ret) {
                printf("\n插入成功!");
            }
            else {
                printf("\n插入失败");
            }
            break;
        case '5':
            ret = ListRemNext_WH(&L, LIST_HEAD(&L), (void**)(&oldElem));
            if (!ret) {
                printf("\n删除成功!");
            }
            else {
                printf("\n删除失败");
            }
            break;
        case '6':
            if (!ListSize_WH(&L)) {
                printf("\n链表序列为空!");
                break;
            }
            printf("\n请输入结点位置(1~%d):", ListSize_WH(&L));
            scanf_s("%d", &idx);
            (void)getchar();
            ret = ListRemByIdx_WH(&L, idx, (void**)(&oldElem));
            if (!ret) {
                printf("\n删除成功!");
            }
            else {
                printf("\n删除失败");
            }
            break;
        case '7':
            printf("\n请输入要查找的元素值:");
            scanf_s("%d", &elem);
            (void)getchar();
            if (ListSearch_WH(&L, (void*)(&elem))) {
                printf("\n查找成功!");
            }
            else {
                printf("\n查找失败");
            }
            break;
        case '8':
            printf("\n链表序列:");
            ListDisplay_WH(&L);
            break;
        case '0':
            ch1 = 'n';
            break;
        default:
            printf("\n输入有误,请输入0~8进行选择!");
            break;
        }
    }

    return 0;
}

int main(int* argc, char** argv)
{
    (void)ListOperation(); /* 不带头结点的单链表操作 */
    //(void)ListOperation_WH(); /* 带头结点的单链表操作 */
    return 0;
}


执行结果:


          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):1

请输入插入的元素值:100

插入成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):2

请输入插入的元素值:200

插入成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):3

请输入插入的元素值:300

插入成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):8

链表序列:
HEAD:100
TAIL:300
100     200     300
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):4

请输入插入的位置(1~4)和元素值:1,500

插入成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):8

链表序列:
HEAD:500
TAIL:300
500     100     200     300
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):4

请输入插入的位置(1~5)和元素值:5,600

插入成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):8

链表序列:
HEAD:500
TAIL:600
500     100     200     300     600
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):4

请输入插入的位置(1~6)和元素值:2,700

插入成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):8

链表序列:
HEAD:500
TAIL:600
500     700     100     200     300     600
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):5

删除成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):8

链表序列:
HEAD:700
TAIL:600
700     100     200     300     600
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):6

请输入结点位置(1~5):3

删除成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):8

链表序列:
HEAD:700
TAIL:600
700     100     300     600
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):7

请输入要查找的元素值:34

查找失败
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):7

请输入要查找的元素值:700

查找成功!
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):8

链表序列:
HEAD:700
TAIL:600
700     100     300     600
          单链表的操作
==================================
|   1--第一个结点之前插入
|   2--第一个结点之后插入
|   3--最后一个结点之后插入
|   4--指定位置插入
|   5--删除第一个结点
|   6--指定位置删除
|   7--查找
|   8--显示
|   0--返回
==================================
请输入菜单号(0~8):
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值