C语言实现简单的小说编辑器

不知你们搜这个的时候是不是帝国理工的人,反正我是,我当初想上网搜一下别人的代码参考,但是很多都是对不上的,大多数的代码都是英文字符串的。自己也是找了很久办法,算法也是自己用笔画图慢慢推的,期间太辛苦了,所以也不想让你们那么辛苦,纯纯造福学弟学妹们好吧,全代码在下边,自己去参考吧!(千万不要懒得写就全抄,你能搜到的东西,别人也肯定能,查重出来还是挺严重的好吧)

代码很长,我写的算法可能比较绕,但是我已经尽量多一些注释了,就凑合着看吧

bug的话……我已经试了一些了,已经写的我好烦了,目前还没有试出来什么bug,你们看着办吧

不过我估计这个代码只适合文件里只有中文的,混杂着英文跟数字的话就会出一些问题,因为如果要辨识中英文数字的话,很多地方又要分别辨识ascall码的正负性,我觉得好麻烦,就算了,毕竟这只是作业不用于商业用途。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LINE 50       //预设定一行25个字(一个字占两个字节)
#define PAGE 5     //预设定一页最多显示5行字
#define MAXSIZE 100   //预设定一个结点最大存放100个元素	


typedef struct novel{
	char data[MAXSIZE]; //存放数据元素的数组
	int number;         //结点号,方便分页显示
	int para;           //段号
	int lnum;           //行号,规定一行50个字即一个结点为一行,刚好为一个结点
	struct novel* Llink;//前指针
	struct novel* Rlink;//后指针
}Novel;


//编辑器大标题,永远出现在程序的最顶端
void HeadWord()
{

	printf("_____________________________________________________________________________________________________\n\n");
	printf("********                              欢迎使用欢乐小说编辑器!                               ********\n");
	printf("_____________________________________________________________________________________________________\n\n");
}


//文件读入
void ReadNovel(Novel *head, Novel *next, Novel *info)
{
	int i = 0, para = 1, lnum = 1;  //para用于记录当前段号
	char c;
	FILE* fp;  //文件指针

	if ((fp = fopen("novel.txt", "r")) == NULL)
	{
		printf("\n文件无法打开!\n");
		exit(0);
	}
	printf("\n正在装入文件!\n");
	
	while ((c = fgetc(fp)) != EOF)
	{
		if (i == MAXSIZE || c == '\n')  //当结点空间用完或检测到换行,则创建新结点
		{
			if (c == '\n')  //若检测到是换行,则段数加一
			{
				info->data[i] = '\0';  //表明这个结点的有效内容完结了,后面都是无效内容
				para++;
				lnum = 1;
				c = fgetc(fp);
			}
			else lnum++;    //否则就是结点满了,行号加一,段数不变
			
			if (c != EOF)  //若未读完文件则申请结点
			{
				next = (Novel*)malloc(sizeof(Novel)); //申请结点,连接上新结点并让info指向新节点
				info->Rlink = next;
				next->Llink = info;
				next->Rlink = NULL;
				next->para = para;
				next->lnum = lnum;
				next->number = info->number + 1;
				info = next;
				i = 0;
			}
			else  //若已读完则令最后为‘\0’表示后面为无效内容(文件最后一个为换行符)
			{
				info->data[i] = '\0';
				break;
			}
		}

		info->data[i] = c;
		i++;
	}
	if(i != MAXSIZE) info->data[i] = '\0';   //以免文件最后一位不是换行符就不加结束符

	info->Rlink = NULL;  //读取文件完毕
	info = head;  //指针回归到头结点方便后续其他操作
	next = head;
	printf("\n读取文件完毕!\n\n");
	fclose(fp);
}


//从链表里找出对应长度的字符串
int findstr(char str[], char* p, Novel* info, int i)
{
	int str_len = 0, j = 0, k, have;  //have用于记录保存取出数组中已经有的数据数量

	str_len = strlen(str);  //记录输入字符串的长度
	if (i <= MAXSIZE - str_len)  //若i大于,则说明结点剩余文字不足
	{
		k = 0;  //k为暂存取出字符串的数组的数组下标
		for (j = i; j < str_len + i; j++)  //从结点中取出来与输入字符串同等长度的字符串
		{
			if (info->data[j] == '\0')  //若检测到结束符则不需要继续检测下去
			{
				return 1;
			}
			else
			{
				p[k] = info->data[j];
				k++;
			}
		}
	}
	else if (info->Rlink != NULL && info->para == info->Rlink->para) //取出文字需要跨越两个同一段的结点
	{
		k = 0;  //k为暂存取出字符串的数组的数组下标
		for (j = i; j < MAXSIZE; j++)  //从第一个结点中取出字符,直到取完
		{
			p[k] = info->data[j];
			k++;
		}

		have = k;
		info = info->Rlink;  //info指向下个结点

		for (j = 0; j < str_len - have; j++)  //从第二个结点中取出剩余数量的字符
		{
			p[k] = info->data[j];
			k++;
		}

		info = info->Llink;
		return 1;
	}
	else return 1;  //结点剩余字符串少于输入的字符串,且与下个结点不为同一段,或已经是最后一段

	return 0;
}


//搜查小说内容
void SearchNovel(Novel* head, Novel* info, char str[])
{
	int choice = -1, i, detm = 0;  //str_len用于记录输入字符串的长度,detm=1则说明已循环完一个结点,将i置回初值
	char takestr[20] = { '\0' };  //暂存从结点中取出来的字符串,再去与str做比较
	int found = 0;  //found = 0代表还没找到该字符串

	i = 0;  //i记录从结点中取出文字的位置
	while (info != NULL)  //字符串匹对,直到找完文章为止
	{
		detm = findstr(str, takestr, info, i);  //返回值返回当前链表中读到哪个位置了

		if (strcmp(takestr, str) == 0)  //字符串匹对成功
		{
			found = 1;
			printf("\n查找成功!\n");
			printf("你想查找的文字位置在:第%d段,第%d行,第%d个位置\n", info->para, info->lnum, i / 2 + 1);
		}
		if (detm == 1)
		{
			i = 0;
			info = info->Rlink;
		}
		else i = i + 2;  //找出也继续找后面的,直到找完文章为止(由于一个字占两个字节,所以加2)
	}

	if (found == 0) printf("\n查找无果,请仔细检查所输入文字是否有误!\n\n");

	info = head;  //指针回归到头结点方便后续其他操作
}


//删除空结点功能函数
void deletenode(Novel *head, Novel *info)
{
	Novel* q = NULL;  //工具人指针:方便删除结点,被传入来的info就是要被删掉的结点

	if (info->Llink == NULL)  //要删除的结点在为链表第一个结点
	{
		q = info->Rlink;
		q->Llink = NULL;
		free(info);
		head = q;

		while (q != NULL)
		{
			q->number--;
			q = q->Rlink;
		}
	}
	else if (info->Rlink == NULL)  //要删除的结点在为链表最后一个结点
	{
		q = info->Llink;
		info->Llink->Rlink == NULL;
		free(info);
	}
	else   //要删除的结点在中间
	{
		q = info->Llink;
		q->Rlink = info->Rlink;  //删除结点
		info->Rlink->Llink = q;
		q = q->Rlink;
		free(info);

		while (q != NULL)
		{
			q->number--;
			q = q->Rlink;
		}
	}
}


//删除所给位置的字符串
void deletestr(Novel* head, Novel* info, int i, char str[])
{
	int str_len, j, k;

	printf("\n准备删除第%d段,第%d行,第%d个位置的内容...\n", info->para, info->lnum, i);
	str_len = strlen(str);  //计算要删除的字符串长度

	i = 2 * i - 2;
	if (info->para != info->Rlink->para)  //若删除的位置的字符串在该段的最后一行
	{
		if (info->data[str_len] == '\0') deletenode(head, info);  //若最后一行要删完,则删除结点
		else
		{
			for (j = i; j < MAXSIZE - str_len; j++)  //删除字符串
				info->data[j] = info->data[j + str_len];
		}  
	}
	else //若要删除的字符串不在最后一行
	{
		if (i + str_len > MAXSIZE)  //当要删除字符串位置位于两个同段结点交界处
		{
			for (j = i; j < MAXSIZE; j++)  //将后一结点的内容补去上一结点删去内容处的空隙
			{
				info->data[j] = info->Rlink->data[str_len + j - MAXSIZE];
			}

			info = info->Rlink;
			while (info->para == info->Rlink->para)  //直到遍历完这一段为止
			{
				for (j = 0; j < MAXSIZE - str_len; j++)  //单个结点内后面向前补全内容
				{
					info->data[j] = info->data[j + str_len];
				}

				k = 0;
				for (; j < MAXSIZE; j++)  //直到当前结点删除需要跨越结点
				{
					info->data[j] = info->Rlink->data[k];
					if (info->data[j] == '\0')  //下一结点的内容都为无效内容,删除结点
					{
						deletenode(head, info->Rlink);
						break;
					}
					k++;
				}
				info = info->Rlink;
			}
		}
		else  //当要删除的字符串不在最后一段,且不在两段交界处
		{
			while (info->para == info->Rlink->para)  //直到遍历完这一段为止
			{
				for (j = i; j + str_len < MAXSIZE; j++)  //当前结点删除不需要跨越结点,在第一个结点中删去字符
				{
					info->data[j] = info->data[str_len + j];
				}

				k = 0;
				for (; j < MAXSIZE; j++)  //若当前结点删除需要跨越结点
				{
					info->data[j] = info->Rlink->data[k];
					if (info->data[j] == '\0')  //下一结点的内容都为无效内容,删除结点
					{
						deletenode(head, info->Rlink);
						break;
					}
					k++;
				}

				i = 0;  //将i置零,让下一结点从0开始遍历完
				info = info->Rlink;
			}
		}

		if (info->para == info->Llink->para)  //该段的最后一行
		{
			for (j = 0; j + str_len < MAXSIZE; j++)   
				info->data[j] = info->data[str_len + j];
		}
		
	}
}


//删除小说内容
void DeleteNovel(Novel* head, Novel* info)
{
	int para, lnum, i;  //记录想要删除的文字内容位置,分别为段号、行号和位置
	char str[20] = { '\0' };  //保存输入的字符串,最多10个字
	char takestr[20] = { '\0' };  //暂存从结点中取出来的字符串,再去与str做比较

	printf("\n请输入你想要删除的内容:(最多10个字)\n");
	scanf("%s", &str);

	printf("\n");
	SearchNovel(head, info, str);  //先调用搜查函数找出想要删除的文字内容位置,再选择删除
	printf("\n");

	while (1)
	{
		printf("\n请输入你想要删除的内容的位置:(分别输入段号和行号和第几个字)\n");
		printf("tips:若想要退出删除则都分别输入0\n");
		scanf("%d%d%d", &para, &lnum, &i);
		
		if (para == 0 && lnum == 0 && i == 0) break;

		while (info->para != para || info->lnum != lnum)  //验证输入段号和行号是否有误
		{
			if (info->Rlink == NULL)
			{
				printf("\n输入的位置有误!请重新输入!\n");
				break;
			}
			else info = info->Rlink;
		}

		if (info->para == para && info->lnum == lnum)
		{
			findstr(str, takestr, info, i * 2 - 2);
			if (strcmp(takestr, str) == 0)  //字符串匹对成功,输入的位置信息完全正确,进行删除字符串操作
			{
				deletestr(head, info, i, str);
				printf("\n删除成功!\n\n");
			}
			else printf("\n输入的位置有误!请重新输入!\n");
		}

		info = head;
	}
	
	printf("\n返回主菜单...\n");
}


//在所给位置添加字符串
void addstr(Novel* info, char str[], int lnum, int i)
{
	int str_len, j, k;
	int last = MAXSIZE, fins;   //last方便跨结点传输数据,fins用于后面记录完成插入后跳出循环
	Novel* q = NULL;  //工具人指针:增加结点后,可调整增加结点后面的结点的数据
	Novel* next = NULL;  //用于申请新结点

	str_len = strlen(str);
	printf("\n准备在第%d段,第%d行,第%d个位置添加内容...\n", info->para, info->lnum, i);
	k = 0;  //用于后面添加字符串内容
	last = MAXSIZE - 1;  //初始化数据
	fins = 0;
	i = 2 * i - 2;

	while (info->para == info->Rlink->para) info = info->Rlink;  //找到想要添加内容的那一段的最后一个节点

	for (j = 0; j < MAXSIZE; j++)  //找到该段最后一个结点中的最后一个有效元素位置
		if (info->data[j] == '\0') break;

	if (j + str_len > MAXSIZE)  //需要申请新结点
	{
		next = (Novel*)malloc(sizeof(Novel));  //申请结点,连接上新结点
		info->Rlink->Llink = next;
		next->Rlink = info->Rlink;
		info->Rlink = next;
		next->Llink = info;
		next->para = info->para;
		next->lnum = info->lnum + 1;
		next->number = info->number + 1;

		info = next;  //info指向新结点,调整新结点后面的结点数据
		q = next->Rlink;
		while (q != NULL)  //后面的结点号都加一
		{
			q->number++;
			q = q->Rlink;
		}

		//遍历找出要添加内容的位置
		while (fins == 0)
		{
			last = j;
			for (j = last + str_len - MAXSIZE; j >= 0; last--)  //跨结点传输数据,补全后面结点
			{
				info->data[j] = info->Llink->data[last];
				if (last == i && info->Llink->lnum == lnum)  //若遍历到了要插入字符串的位置
				{
					for (; i < MAXSIZE; i++)
					{
						info->Llink->data[i] = str[k];
						k++;
					}

					if (k == str_len)  //若已经添加完,不需要继续进行循环下去
					{
						fins = 1;
						break;
					}
					else  //否则到第二结点继续添加内容
					{
						for (i = 0; i < str_len - MAXSIZE + last; i++) info->data[i] = str[k];
						fins = 1;
						break;
					}
				}

				j--;
			}
			if (fins == 1) break;  //若已经添加完内容,则直接退出,不用参加下面循环

			info = info->Llink;
			for (j = MAXSIZE - 1; last >= 0; last--)  //一个结点内的内容后移str_len位,为前面增加内容腾出空位
			{
				info->data[j] = info->data[last];
				if (last == i && info->lnum == lnum)
				{
					for (; i < j; i++)
					{
						info->data[i] = str[k];
						k++;
					}

					fins = 1;
					break;
				}

				j--;
			}

			j = MAXSIZE - 1;  //形成回流循环
		}
	}
	else
	{
		while (fins == 0)
		{
			for (j += str_len; j >= str_len; j--)   //一个结点内的内容后移str_len位,为前面增加内容腾出空位
			{
				info->data[j] = info->data[j - str_len];

				if (j - str_len == i && info->lnum == lnum)   //若遍历到了要插入字符串的位置
				{
					for (; i < j; i++)
					{
						info->data[i] = str[k];
						k++;
					}

					fins = 1;
					break;
				}
			}
			if (fins == 1) break;  //若已经添加完内容,则直接退出,不用参加下面循环

			for (; j >= 0; j--)   //跨结点传输数据,补全后面结点
			{
				info->data[j] = info->Llink->data[last];
				if (last == i && info->Llink->lnum == lnum)   //若遍历到了要插入字符串的位置
				{
					fins = 1;
					for (; i < MAXSIZE; i++)
					{
						info->Llink->data[i] = str[k];
						k++;
					}

					if (k == str_len) break;  //若前面那个结点就已经添加完了就跳出
					else
					{
						for (i = 0; i < j; i++)
						{
							info->data[i] = str[k];
							k++;
						}
					}

					break;
				}

				last--;
			}

			info = info->Llink;  //形成回流循环
			j = last;
			last = MAXSIZE - 1;
		}
	}
}


//添加小说内容
void AddNovel(Novel* head, Novel* info)
{
	int para, lnum, i;  //记录想要添加的文字内容位置,分别为段号、行号和位置
	char str[20] = { '\0' };  //保存输入的字符串,最多10个字

	while (1)
	{
		printf("\n请输入你想要添加内容的位置:(分别输入段号和行号和第几个字)\n");
		printf("tips:若想要退出添加则都分别输入0\n");
		scanf("%d%d%d", &para, &lnum, &i);

		if (para == 0 && lnum == 0 && i == 0) break;

		printf("\n请输入你想要添加的内容:(最多10个字)\n");
		scanf("%s", &str);

		while (info->para != para || info->lnum != lnum)  //验证输入段号和行号是否有误
		{
			if (info->Rlink == NULL)
			{
				printf("\n输入的位置有误!请重新输入!\n");
				info = head;
				break;
			}
			else info = info->Rlink;
		}

		if (info->para == para && info->lnum == lnum)  //如果输入的段号和行号无误
		{
			addstr(info, str, lnum, i);
			printf("\n添加成功!\n\n");
		}

		info = head;
	}

	printf("\n返回主菜单...\n");
}


//修改链表(相当于删除后增加)
void ChangeNovel(Novel* head, Novel* info)
{
	char strdelete[20] = { '\0' };  //暂存要删除的字符串
	char stradd[20] = { '\0' };     //暂存要添加的字符串
	char takestr[20] = { '\0' };    //暂存从链表取出的字符串
	int para, lnum, i;

	while (1)
	{
		printf("\n请输入你想要修改的内容所在位置:(分别输入段号和行号和第几个字)\n");
		printf("tips:若想要退出添加则都分别输入0\n");
		scanf("%d%d%d", &para, &lnum, &i);

		if (para == 0 && lnum == 0 && i == 0) break;

		printf("\n请输入你想要修改掉的内容:(最多10个字)\n");
		scanf("%s", &strdelete);

		printf("\n请输入你想要修改成的内容:(最多10个字)\n");
		scanf("%s", &stradd);

		while (info->para != para || info->lnum != lnum)   //找到输入的位置结点
		{
			if (info->Rlink == NULL)
			{
				printf("\n输入的位置有误!请重新输入!\n");
				info = head;
				break;
			}
			else info = info->Rlink;
		}

		findstr(strdelete, takestr, info, i * 2 - 2);
		if (strcmp(takestr, strdelete) == 0)  //字符串匹对成功,输入的位置信息完全正确,进行修改字符串操作
		{
			deletestr(head, info, i, strdelete);  //删除字符串
			addstr(info, stradd, lnum, i);  //添加字符串
			printf("\n已成功修改!\n");
		}
		else printf("\n输入的位置信息与要修改的文字内容不匹配!请重新输入!\n");
	}

	info = head;  
	printf("\n返回主菜单...\n");
}


//输出小说
void PrintfNovel(Novel* head, Novel* info)
{
	int i, j, choice = 0, page = 1, prt = 1;  //page记录当前输出第几页,prt = 1则打印小说,否则不打印
	int number = 1;   //记录info要从第几个结点开始打印

	printf("小说输出如下:(输入-1返回主菜单)\n");
	while (choice != -1)
	{
		if (prt == 1)
		{
			printf("\n+---------------------------------------------------------------------------------------------------+\n\n");
			for (i = 0; i < PAGE; i++)   //打印出一页
			{
				if (info != NULL)   //若结点不为空,则读出         
				{
					for (j = 0; j < MAXSIZE; j++)  //打印出一行
					{
						if (info->data[j] == '\0') break;  //打印中途读到‘\0’直接跳出开始打印下一段
						else printf("%c", info->data[j]);
					}
					printf("\n");
					if (info->Rlink != NULL && i < PAGE - 1) info = info->Rlink;
					else break;  //还未打印到一页的最后一行且下个结点不为空则继续打印,否则跳出打印循环
				}
			}
			printf("\n********               0:上一页      ------  第%d页  ------      1:下一页               ********\n\n", page);
			printf("+---------------------------------------------------------------------------------------------------+\n\n");
		}

		scanf("%d", &choice);
		switch (choice)
		{
		case 0:
			if (info->number <= 5)  //若当前在第一页,则没有上一页
			{
				printf("当前页没有上一页!请重新输入:\n");
				prt = 0;
			}
			else
			{
				number = info->number - 4 - info->number % 5;  //计算出要跳转到上一页的第一行的结点号
				while (info->number != number) info = info->Llink;
				page--;
				prt = 1;
			}
			break;
		case 1:
			if (info->Rlink == NULL)
			{
				printf("当前页没有下一页!请重新输入:\n");
				prt = 0;
			}
			else
			{
				info = info->Rlink;
				page++;
				prt = 1;
			}
			break;
		case -1:break;
		default:
			printf("输入错误,请重新输入!");
			prt = 0;
			break;
		}
	}

	info = head;  //指针指回头指针方便后续操作
	printf("\n返回主菜单...\n\n");
}


//保存小说
void ConserveNovel(Novel *head, Novel *info)
{
	FILE* fp;
	int i;

	if ((fp = fopen("Novel.txt", "w")) == NULL) 
	{
		printf("\n文件打不开!n");
		exit(0);
	}

	printf("\n正在存入文件!\n");
	while (info != NULL)
	{
		i = 0;

		while (1)  //保存一段的内容
		{
			while (i != MAXSIZE && info->data[i] != '\0')  //保存一个结点的内容
			{
				putc(info->data[i], fp);
				i++;
			}

			if (info->Rlink != NULL)  //当前未为最后一个结点
			{
				if (i == MAXSIZE && info->para == info->Rlink->para)   //本段未结束
				{
					i = 0;
					info = info->Rlink;
				}

				if (info->data[i] == '\0' || (i == MAXSIZE && info->para != info->Rlink->para))  //检测到本段结束,则跳出
				{
					putc('\n', fp);
					break;
				}
			}
			else   //最后一个结点
			{
				putc('\n', fp);
				break;
			}
		}

		info = info->Rlink;
	}

	printf("\n保存文件完毕!\n\n");
	info = head;
	fclose(fp);
}


//释放链表
void Empty(Novel *head)
{
	Novel* p = head->Rlink;

	while (1)
	{
		free(head);
		if (p != NULL)
		{
			head = p;
			p = head->Rlink;
		}
		else break;
	}

	printf("\n已释放所有内存!\n\n");
}


//主函数
int main()
{
	Novel* head; //双向链表头指针
	int choice = -1;  //用于主菜单中的功能选择
	char str[20] = { '\0' };  //保存输入的字符串,最多10个字
	Novel* next = NULL, * info = NULL;  //next用于申请新结点,info指向当前使用的结点

	head = (Novel*)malloc(sizeof(Novel)); //申请头结点
	info = head;
	head->number = 1; //第一个节点
	head->para = 1;   //第一段
	head->lnum = 1;   //第一行
	head->Llink = NULL;
	head->Rlink = NULL;

	HeadWord();  //小说编辑器大标题

	while (choice != 0)
	{
		printf("+---------------------------------------------------------------------------------------------------+\n");
		printf("+------------------------------------------主菜单---------------------------------------------------+\n");
		printf("+---------------------------------------------------------------------------------------------------+\n");
		printf("+----------------------------------------1.读入文件-------------------------------------------------+\n");
		printf("+----------------------------------------2.搜索文字位置---------------------------------------------+\n");
		printf("+----------------------------------------3.删除小说内容---------------------------------------------+\n");
		printf("+----------------------------------------4.添加小说内容---------------------------------------------+\n");
		printf("+----------------------------------------5.修改小说内容---------------------------------------------+\n");
		printf("+----------------------------------------6.输出小说-------------------------------------------------+\n");
		printf("+----------------------------------------7.保存小说-------------------------------------------------+\n");
		printf("+----------------------------------------0.结束程序-------------------------------------------------+\n");
		printf("+---------------------------------------------------------------------------------------------------+\n\n");

		scanf("%d", &choice);
		switch (choice)
		{
		case 0:break;
		case 1:ReadNovel(head, next, info); break;
		case 2:
			printf("请输入你想查找的文字:(最多10个字)\n");
			scanf("%s", &str);  //输入要查的字符串
			SearchNovel(head, info, str);
			break;
		case 3:
			DeleteNovel(head, info); 
			printf("返回成功!\n\n");
			break;
		case 4:
			AddNovel(head, info);
			printf("返回成功!\n\n");
			break;
		case 5:
			ChangeNovel(head, info);
			printf("返回成功!\n\n");
			break;
		case 6:
			PrintfNovel(head, info); 
			printf("返回成功!\n\n");
			break;
		case 7:ConserveNovel(head, info); break;
		default:printf("输入错误,请重新输入!\n"); break;
		}
	}

	Empty(head);  //结束程序,释放全部结点
	printf("退出程序成功,谢谢使用!\n");

	return 0;
}

文件内容:我的只是我随便从网上粘贴下来的一段话,只要是全中文的就好。文件只需要跟代码文件在同一个文件夹内即可,不想的话就去复制它的全路径吧

 

如果读入打印出来是乱码的话,那就把你们的文件编码改为“ANSI”,别问,问就是这些坑我已经都帮你们踩过了

第一步:另存为

第二步:更改编码,最后点保存去原文件位置覆盖原文件就好

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值