带头结点链表例题

编程思想:编程时先搭好框架,比较难处理的部分用汉字表示,以后慢慢一步一步进行处理

空链的定义

#include<stdio.h>

typedef struct POINT{
    int row;
    int col;
    struct POINT *next;
}POINT; 


void main(void){
    POINT head1 = {0};  

//此为不完全赋初值,指针被赋值为NULL。若不赋值,则head1中的三个成员是有值的,其值为垃圾数据
//此链为一个空链(不关心数据域,只要指针域为空即可),空链是链表的起始点。
}

输出链表中的错误

void showPoints(POINT head){
    POINT *p;

 

    printf("\n输入的点坐标如下:\n");
    //for(p = head.next; p->next; p = p->next){   //错误形式     //分析:若终止条件写成p->next,则无法输出最后一个点
    for(p = head.next; p; p = p->next){              //正确形式      //小错误:for(p = head.next, p, p = p->next){
        if(p == head.next){                                  //只是为了让格式更好看
            showPoint(*p);
        }else{
            printf(",");                                         //只是为了让格式更好看
            showPoint(*p);
        }
    }
}
1)for循环终止条件的确定

2)for(  ;   ;)

3)格式问题

 

销毁链表中的错误

void destroyPointLink(POINT *head){
    POINT *p;

 

//正确形式
    while(head->next){
        p = head->next;                  
        head->next = p->next;
        free(p);
    }

 

/*  //错误形式2:

    //错误形式2分析:p = head->next; head->next = p->next; 这两条语句可以实现p的移位,所以不需要for循环进行移位
    while(head->next){
        for(p = head->next; p->next; p = p->next){  
            head->next = p->next;
            free(p);
        }
*/

 

/*     //错误形式1:
    while(head->next){
        for(p = head->next; p->next; p = p->next)  
            ;
        free(p);
    }
*/   
}

错误形式1分析如下:

一般人的思维都是从后往前释放,则:

1)第一次循环时,找到末节点,释放。灰色表示已经释放,但是释放之后,p节点的前一个节点的链域值并未受到任何改变,依然指向一个已经释放的空间
2)当下一个循环,从第一个节点开始,遍历到好像应该是最后一个节点(灰色前面的那个节点),它的链域值不为NULL,
还要继续向下移动,这样p指向一个已经释放的空间,指向没有关系,但是要进行访问就不可以了,这就是非法内存访问。

正确思路分析如下:

 

正确形式为从前往后释放,理解如上,终止条件理解如下:

插入链表中的分析

void insertPoint(POINT *head){
    /*
    1.输入新点坐标
    2.输入指定点坐标
    3.找与oldPoint的row和col相同的前驱节点
    4.完成插入
    */

 

    POINT *p;    //需要插入的新点
    POINT oldPoint; //需要插入新点的位置节点
    int row;
    int col;
    POINT *q;   //q表示oldPoint的前驱节点

 

    //1.输入新点坐标(newPoint)
    printf("请输入一个新点的坐标(例如:3 4),任意一个输入为0表示结束:");
    scanf("%d%d", &row, &col);

    p = (POINT *)malloc(sizeof(POINT));    //malloc()一定要记得加头文件
    p->row = row;
    p->col = col;
    p->next = NULL;

 

    //2.输入指定点坐标(oldPoint)
    printf("请输入指定插入的点(进行左插入,若指定点不存在,则新点追加到末尾)");
    scanf("%d%d", &oldPoint.row, &oldPoint.col);

 

    //3.找与oldPoint的row和col相同的前驱节点
    q = findPrePoint(*head, oldPoint);
        //1. q == NULL;                                  用户指定的点是第一个有效节点
         //2. q != NULL && q->next == NULL;  说明m指向末节点,表明用户所指定的点不存在;
        //3. q != NULL && q->next != NULL;  说明用户指定的点不但存在,而且不是第一个有效节点

 

    //4.完成插入

 

//自己代码
    if(q == NULL){
        p->next = head->next;
        head->next = p;
    }else if(q->next == NULL){
        q->next = p;
    }else{
        p->next = q->next;
        q->next = p;
    }

 

//老师代码(代码优化)
    /*
    if(q = NULL){
        q = head;
    }
    p->next = q->next;
    q->next = p;
*/
}

 

插入时分析如下:
 

排序时的分析

void sortByRow_add(POINT *head){
    POINT *pre;
    POINT *cur;
    POINT tmp;

    printf("\n下面进行排序(按行坐标升序排序)操作:");
    printf("\n目前已经存在的节点有:");
    showPoints(*head);

    for(pre = head->next; pre; pre = pre->next){
        for(cur = pre->next; cur; cur = cur->next){
            if(pre->row > cur->row){
                //交换整个节点(但是最终只有数据域正确交换,指针域对应不正确
                tmp = *pre;
                *pre = *cur;
                *cur = tmp;
                //交换指针域
                tmp.next = pre->next;
                pre->next = cur->next;
                cur->next = tmp.next;
            }
        }
    }
}

问题:为什么排序时还要继续交换指针域?

解答:见博客 https://blog.csdn.net/tennysonsky/article/details/51076340 非常详细

 

*********************************************************************************************************************************

下面这个问题和本题无关,只是记录在此而已:

问题:如何在不引入tmp节点的情况下进行两个节点的交换?

(1)两个节点相邻

(2)两个节点不相邻 找不见了,嘤嘤嘤,有机会再碰吧。。。

带头结点链表大例题

//带头结点链表大例题

//用链表实现对屏幕上点的管理,并用菜单的形式进行显示
//1.录入功能:用户从键盘输入若干个点坐标信息,用链表形式存储,若用户输入的坐标中,任意一个为0,则,结束输入。
//2.显示功能
//3.插入功能
//4.删除
//5.查找
//6.排序
//7.菜单

/*
1 2
4 6
78 34
56 89
3 9
0 4

*/

#include<stdio.h>
#include<malloc.h>     
#include"MEC_menu.h"     //正确形式
//#include<MEC_menu.h>   //错误形式
/*
1.头文件#include <> :
  表示引用标准库头文件,编译器会从系统配置的库环境中去寻找
2.头文件#include "":
  一般表示用户自己定义使用的头文件,因为这些文件放在工程目录(也就是编译器的当前目录)下,而不是放在公共头文件目录下。
  编译器默认会从当前文件夹中寻找,如果找不到,则到系统默认库环境中去寻找。如果用<>则找不到头文件。
  不过保险的话,用""肯定可以找到所有头文件,包括系统库函数头文件和自己定义的头文件
*/
#include<conio.h>



typedef struct POINT{
	int row;
	int col;
	struct POINT *next;
}  POINT;

typedef unsigned char  boolean;

#define TRUE 1
#define FALSE 0

#define EXIT_SYSTEM  5

void initPointLink(POINT *head);
void showPoints(POINT head);    //显示所有点坐标
void showPoint(POINT node);     //显示一个点坐标
void destroyPointLink(POINT *head);
POINT *findPrePoint(POINT head, POINT oldPoint);
void insertPoint(POINT *head);   
void deletePoint(POINT *head);
void sortByRow_add(POINT *head);

void doFunction(int functionIndex, POINT *head);

void doInitPointLink(POINT *head);
void doInsertPoint(POINT *head);
void doDeletePoint(POINT *head);
void doShowPoints(POINT head);
void doSortByRow_add(POINT *head);




void doSortByRow_add(POINT *head){
	char *title = "[坐标点按行值升序排列]";

	printf("按任意键进行排序:");
	getch();
	system("cls");

	printf("\n");
	showStringAlignCenter(title);
	sortByRow_add(head);
	printf("\n排序后已经存在的点有:");
	showPoints(*head);
}

void doShowPoints(POINT head){
	char *title = "[显示坐标点功能]";

	printf("按任意键进行显示:");
	getch();
	system("cls");

	printf("\n");
	showStringAlignCenter(title);
	showPoints(head);
}

void doDeletePoint(POINT *head){
	char *title = "[删除节点功能]";

	printf("按任意键进行插入:");
	getch();
	system("cls");

	printf("\n");
	showStringAlignCenter(title);
	deletePoint(head);
	printf("按任意键继续......\n");
	getch();
	printf("删除后已经存在的点有:");
	showPoints(*head);
}

void doInsertPoint(POINT *head){
	char *title = "[插入节点功能]";

	printf("按任意键进行插入:");
	getch();
	system("cls");

	printf("\n");
	showStringAlignCenter(title);
	insertPoint(head);
	printf("按任意键继续......\n");
	getch();
	printf("\n插入后已经存在的点有:");
	showPoints(*head);
}
void doInitPointLink(POINT *head){
	char *title = "[录入节点功能]";

	printf("按任意键进行录入:");
	getch();
	system("cls");
	
	printf("\n");
	showStringAlignCenter(title);
    initPointLink(head);
	printf("按任意键继续......\n");
	getch();
	printf("\n录入后已经存在的点有:");
	showPoints(*head);
}


void doFunction(int functionIndex, POINT *head){
	switch(functionIndex){
	case 0: doInitPointLink(head); break;
	case 1: doInsertPoint(head); break;
	case 2: doDeletePoint(head); break;
	case 3: doShowPoints(*head); break;
	case 4: doSortByRow_add(head); break;
	}
}


void sortByRow_add(POINT *head){
	POINT *pre;
	POINT *cur;
	POINT tmp;

	printf("\n下面进行排序(按行坐标升序排序)操作:");
	printf("\n目前已经存在的节点有:");
	showPoints(*head);

	for(pre = head->next; pre; pre = pre->next){
		for(cur = pre->next; cur; cur = cur->next){
			if(pre->row > cur->row){
				//交换整个节点(但是最终只有数据域正确交换,指针域对应不正确
				tmp = *pre;
				*pre = *cur;
				*cur = tmp;
				//交换指针域
				tmp.next = pre->next;
				pre->next = cur->next;
				cur->next = tmp.next;
			}
		}
	}
}

void deletePoint(POINT *head){
	/*
	1.让用户输入要删除的点
	2.找到要删除点的下标
	3.进行指针的指向转换,删除该点
	*/
	POINT *p;          //p指向要删除的节点
	POINT *prePoint;   //要删除的点的前驱节点
	POINT oldPoint;

	printf("\n下面进行删除操作:");
	printf("\n目前已经存在的节点有:");
	showPoints(*head);

	printf("\n请输入要删除的点的坐标(例如:3 4),任意一个输入为0表示结束:");
	scanf("%d%d", &oldPoint.row, &oldPoint.col);


	prePoint = findPrePoint(*head, oldPoint);
	p = prePoint->next;
/*
	findPrePoint函数返回值有三种情况(假设返回值赋值给m):
	1. m == NULL;                     用户指定的点是第一个有效节点
	2. m != NULL && m->next == NULL;  说明m指向末节点,表明用户所指定的点不存在;
	3. m != NULL && m->next != NULL;  说明用户指定的点不但存在,而且不是第一个有效节点
*/

	if(p == NULL){
		head->next = p->next;
		printf("操作成功,指定点已删除\n");
	}else if(p->next == NULL){
		printf("操作失败,指定点不存在\n");
	}else{
		prePoint->next = p->next;
		printf("操作成功,指定点已删除\n");
	}
}

void insertPoint(POINT *head){
	/*
	1.输入新点坐标
	2.输入指定点坐标
	3.找与oldPoint的row和col相同的前驱节点
	4.完成插入
	*/

	POINT *p;             //需要插入的新点
	POINT oldPoint;       //需要插入新点的位置节点
	int row;
	int col;
	POINT *q;             //q表示oldPoint的前驱节点

	printf("\n下面进行插入操作:");
	printf("\n目前已经存在的节点有:");
	showPoints(*head);
    
	//1.输入新点坐标(newPoint)
	printf("\n请输入一个新点的坐标(例如:3 4),任意一个输入为0表示结束:");
	scanf("%d%d", &row, &col);

	p = (POINT *)malloc(sizeof(POINT));    //malloc()一定要记得加头文件
	p->row = row;
	p->col = col;
	p->next = NULL;

	//2.输入指定点坐标(oldPoint)
	printf("请输入指定插入的点(进行左插入,若指定点不存在,则新点追加到末尾):");
	scanf("%d%d", &oldPoint.row, &oldPoint.col);

	//3.找与oldPoint的row和col相同的前驱节点
	q = findPrePoint(*head, oldPoint);
	    //1. q == NULL;                     用户指定的点是第一个有效节点
     	//2. q != NULL && q->next == NULL;  说明m指向末节点,表明用户所指定的点不存在;
	    //3. q != NULL && q->next != NULL;  说明用户指定的点不但存在,而且不是第一个有效节点

	//4.完成插入

//自己代码
	if(q == NULL){
		p->next = head->next;
		head->next = p;
	}else if(q->next == NULL){
		q->next = p;
	}else{
		p->next = q->next;
		q->next = p;
	}

//老师代码(代码优化)
/*
	if(q = NULL){
		q = head;
	}
	p->next = q->next;
	q->next = p;
*/
}

POINT *findPrePoint(POINT head, POINT oldPoint){
	POINT *p;
	POINT *prePoint = NULL;                         //此处一定要赋值

	for(p = head.next; p && ((p->row != oldPoint.row) ||  p->col != oldPoint.col); p = p->next){   
		prePoint = p;
	}
	//for()循环进行的条件是:1)没找完   ( (p->row != oldPoint.row)   ||    (p->col != oldPoint.col)  )
	//                       2)没找到   p

	//理解如下:当p指向最后一个有效节点的时候,prePoint指向倒数第二个有效节点,
	//          此时p的值不为0,满足条件继续执行,这时prePoint指向最后一个有效节点,且p的值被赋值为0,跳出循环
	//          所以,循环结束之后,prePoint指向最后一个有效节点,而p的值为0

	//短路运算:当指针p为0时,则不执行后面的语句,直接短路 
	//(若是执行后面的语句,相当于对内存中最前面的空间进行访问,这是不允许的,是非法内存访问)
	//所以,p与后面的内容是不能交换顺序的

	return prePoint;

/*
	该函数返回值有三种情况(假设返回值赋值给m):
	1. m == NULL;                     用户指定的点是第一个有效节点
	2. m != NULL && m->next == NULL;  说明m指向末节点,表明用户所指定的点不存在;
	3. m != NULL && m->next != NULL;  说明用户指定的点不但存在,而且不是第一个有效节点
*/
}


void destroyPointLink(POINT *head){
	POINT *p;

	while(head->next){
		p = head->next;                 //正确形式
		head->next = p->next;
		free(p);
	}

/*   //错误形式2:p = head->next; head->next = p->next; 这两条语句可以实现p的移位,所以不需要for循环进行移位
	while(head->next){
		for(p = head->next; p->next; p = p->next){   
			head->next = p->next;
			free(p);
		}
*/

/*   //错误形式1:
	while(head->next){
		for(p = head->next; p->next; p = p->next)    
			;
		free(p);
	}
//灰色表示已经释放,但是释放之后,p节点的前一个节点的链域值并未受到任何改变,依然指向一个已经释放的空间
//当下一个循环,从第一个节点开始,遍历到好像应该是最后一个节点(灰色前面的那个节点),它的链域值不为NULL,
//还要继续向下移动,这样p指向一个已经释放的空间,指向没有关系,但是要进行访问就不可以了,这就是非法内存访问。

*/	
}


void showPoint(POINT node){
	printf("(%d %d)", node.row, node.col);
}

void showPoints(POINT head){
	POINT *p;

	//for(p = head.next; p->next; p = p->next){   //错误形式	 //分析:若终止条件写成p->next,则无法输出最后一个点
	for(p = head.next; p; p = p->next){   	      //正确形式      //小错误:for(p = head.next, p, p = p->next){
		if(p == head.next){    		              //只是为了让格式更好看
			showPoint(*p);
		}else{
			printf(",");        		          //只是为了让格式更好看
			showPoint(*p);
		}
	}
	
	printf("\n");
	printf("按任意键继续......\n");	
	getch();

}

void initPointLink(POINT *head){
	int row;
	int col;
	POINT *p;
	POINT *q;   //p指针始终指向末节点

	printf("请输入一个点的坐标(例如:3 4),任意一个输入为0表示结束:");
	scanf("%d%d", &row, &col);
	
	//此处要考虑这个判断语句是一次执行还是多次执行,若是一次执行,则用if语句,若是多次执行,则用while语句
	while(row && col){           
	//小错误:while(row & col){     
    //&是一个位运算符,就是将两个二进制的数逐位相与,就是都是1才是1,只要有一个为0则为0,结果是相与之后的结果。
    //&&是一个逻辑运算符,就是判断两个表达式的真假性,只有两个表达式同时为真才为真,有一个为假则为假,具有短路性质。
		
		//处理一个点坐标 
		p = (POINT *)malloc(sizeof(POINT));    //malloc()一定要记得加头文件
		p->row = row;
		p->col = col;
		p->next = NULL;

		for(q = head; q->next; q = q->next)
			;

		if(head->next == NULL){     //第一个有效节点
			head->next = p;
		}else{                      //非第一个有效节点
			q->next = p;
		}

		q = p;
		printf("请输入一个点的坐标(例如:3 4),任意一个输入为0表示结束:");
		scanf("%d%d", &row, &col);
	}
}


void main(void){
	POINT head1 = {0};  //此为不完全赋初值,指针被赋值为NULL。若不赋值,则head1中的三个成员是有值的,其值为垃圾数据
	                    //此链为一个空链(不关心数据域,只要指针域为空即可)
	int choiceIndex = 0;

	while(choiceIndex != EXIT_SYSTEM){
		showMenu();
		choiceIndex = getUserChoose(menu, itemCount());

		if(choiceIndex < 0){
			printf("无效的输入");
		}else{
			printf("您选择了:%s\n", menu[choiceIndex].item);
			doFunction(choiceIndex, &head1);
		}
	}

	destroyPointLink(&head1);

}

//测试各个子函数
	/*
    initPointLink(&head1);   //说明结构体类型的变量不是指针
	showPoints(head1);


	insertPoint(&head1);   //传值还是传址,主要看需不需要更改head1的值
	                       //不能说需要或是不需要,这样不严谨,应该说可能需要更改
	                       //若是新插入的节点插在了第一个有效节点之前。则需要更改头结点的指向,即需要更改头结点的值
                           //若是新插入的节点不插在第一个有效节点指点,则不需要更改头结点的指向,即不需要更改头结点的值
	                       //既然是可能更改,就一定要考虑到,故传址
	showPoints(head1);     //进行程序验证,必须考虑所有情况

	deletePoint(&head1);
	showPoints(head1);

	sortByRow_add(&head1);
	showPoints(head1);

	destroyPointLink(&head1);
	//showPoints(head1);
	*/

菜单头文件

#include<stdio.h>
#include<conio.h>     //getch()函数
#include<stdlib.h>    //system()函数     standard library   c语言标准库
#include<string.h>    //strlen()函数
#include <ctype.h>    //isalpha()        toupper()函数

#define TRUE 1
#define FALSE 0

typedef unsigned char boolean;                   //boolean表示逻辑值

typedef struct MEC_MENU{
	char *item;
	boolean beShow;
	char hotKey;   
}MEC_MENU;

const char *Title = "微易码屏幕点信息管理小系统"; //const 意思是只能对其内容进行读,不能写 
                                                  //意思是Title这个指针只能指向"微易码屏幕点信息管理小系统"这个字符串
MEC_MENU menu[] = {                               //为什么不	写个数 ????   //menu这个为全局的数组  
//      item                   beShow   hotKey    //哇,好整齐
	"A)信息录入",				TRUE,   'A',     
	"B)在指定点左侧插入新点",	TRUE,	'B',   
	"C)删除指定点信息",			TRUE,	'C',   
	"D)显示所有点信息",			TRUE,	'D',   
	"E)按行坐标升序排序",		TRUE,	'E',   
	"X)退出",					TRUE,	'X',   
};

//这两个宏是为了检测用户的输入是否规则,是为了函数isHotkey服务的
//isHotkey()的功能为:若输入查找到了,则输出下标,输入为查找到,根据情况输出下述两种值
#define NOT_HOTKEY       -1   //此处为什么不给一个0,因为有可能查找到的值的下标为0,不合适
#define ITEM_IS_HIDDEN   -2


#define itemCount()     (sizeof(menu) / sizeof(MEC_MENU))    //计算item的个数


void showMenu();                                   //参数需要munu[]这个数组,但是这个是全局的,没有必要通过参数传递,所以不需要参数了
void showStringAlignCenter(const char *string);    //为了横向居中,因为纵向居中比较容易,横向比较困难 
int getMaxLengthOfItems(MEC_MENU *menu,int count);  //菜单显示横向居中要以最长字符串为标准,此函数为了获得最大字符串的长度
                                                    //count的意思:由于要比较每一个item的长度,故要对每个item进行遍历,count为item的个数 
                                                    //此处不能直接写那个itemCount(),因为它里面的那个menu和此函数的局部变量重合   
int getUserChoose(MEC_MENU *menu, int itemCount);
int isHotkey(MEC_MENU *menu, int itemCount, int choose);




int isHotkey(MEC_MENU *menu, int itemCount, int choose){
	int index = 0;

	while((index < itemCount)  && (menu[index].hotKey != choose))  //循环条件:没找完 && 没找到
		index++;      
	
	if(index >= itemCount){
		index = NOT_HOTKEY;
	}

	return index;
}

int getUserChoose(MEC_MENU *menu, int itemCount){   
	int choose;
	int index;

	printf("\n\n请输入一个字母进行选择(不区分大小写):");
	choose = getche();                                        //接收一个字符,有回显

	if(isalpha(choose))   
		choose = toupper(choose);
	index = isHotkey(menu, itemCount, choose);           //正确形式
//	index = isHotkey(menu, itemCount(), choose);         //错误形式  自己理解:itemCount()里面的menu是全局变量,和这个函数里面的局部变量重了
	printf("\n");
	return index;

		//isalpha
		//功能:判断字符c是否为英文字母
        //说明:若为英文字母,返回非0(小写字母为2,大写字母为1)。若不是字母,返回0

	   //toupper 
	   //功能:将字符c转换为大写英文字母
       //说明:如果c为小写英文字母,则返回对应的大写字母;否则返回原来的值。

}

int getMaxLengthOfItems(MEC_MENU *menu,int count){
	int i;
	int maxLen = strlen(menu[0].item);

	for(i = 0; i < count; i++){
//		if(strlen(menu[i].item) > (unsigned int)maxLen){         //计算每个item的长度并找出其最大长度
//			maxLen = strlen(menu[i].item);	
		if(strlen((menu+i)->item) > (unsigned int)maxLen){
			maxLen = strlen((menu+i)->item);	
		}
		//经验证,两种方式都正确  老师都是用数组形式
		//(menu+i)->item   <=>    menu[i].item   
		//类似例子见博客 https://blog.csdn.net/weixin_42072280/article/details/82634114
	}

	return maxLen;
}

void showStringAlignCenter(const char *string){   //为什么要加const  因为下文此函数的实参有一次为Title(const char *)类型 
	int leftBlankCount;
	int i;

	leftBlankCount = (80 - strlen(string)) / 2;

	for(i = 0; i < leftBlankCount; i++){
		printf(" ");
	}
	printf("%s\n", string);

}


void showMenu(){
	int i, j;
	int maxLen;

	system("CLS");                       //DOS命令 清屏操作
	//printf("%s\n", Title);             //标题横向不居中形式      
	//此处输出为何直接写指针? 见https://blog.csdn.net/weixin_42072280/article/details/82817691

	showStringAlignCenter(Title);        //标题横向居中形式

	for(i = 0; i < (25-1-itemCount())/2; i++){   //为了在标题和目录中间插入空白行
		printf("\n");
	}

//目录横向不居中形式
	/*
	for(i = 0; i < itemCount(); i++)   //由于要进行动态显示,故此处条件不能直接写数值,故而用sizeof计算出个数
                                       //#define itemCount     (sizeof(menu) / sizeof(MEC_MENU))
	{
		printf("%s\n", menu[i].item);
	}
	*/

//目录横向居中形式
	maxLen = getMaxLengthOfItems(menu,itemCount());


	for(i =0; i < itemCount(); i++){
			for(j = 0; j < (80 - maxLen)/2; j++){
				printf(" ");
			}
			printf("%s\n", menu[i].item);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安安csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值