[C语言]随机方向贪吃蛇,KMP算法实现

C语言编程练习题

题目一:随机方向贪吃蛇

需求说明

1. 菜单功能(核心新增)

  • 程序启动后,首先显示游戏菜单,菜单包含3个选项:① 开始游戏 ② 查看历史最大长度 ③ 退出程序;
  • 菜单操作:通过键盘输入对应选项(如输入1选择开始游戏、输入2查看历史最大长度、输入3退出),确认后执行对应操作;
  • 游戏死亡后,先显示死亡提示(如“蛇已死亡,游戏结束!”),提示“按任意键返回菜单”,按下任意键后回到主菜单,可重新选择选项;
  • 历史最大长度:记录所有游戏局中蛇身达到的最长长度,初始值为蛇的初始长度,每次游戏结束后,若当前蛇长大于历史最大长度,则更新历史最大长度;查看时直接显示“历史最大长度:XXX”。

2. 游戏机制

  • 选择“开始游戏”后,蛇在控制台界面中自动移动,无需玩家手动控制方向;
  • 移动规则:每移动3步后,从“上、下、左、右”中随机选择一个新方向,不能选择与当前移动方向相反的方向(例如当前向右,新方向不能向左);未到3步时,保持当前方向匀速移动;
  • 无关卡设计,游戏持续进行,仅当触发“蛇头撞自身身体”时结束,撞墙壁不结束;游戏结束后自动记录当前蛇长,更新历史最大长度(若需)。

3. 碰撞与穿墙规则

  • 蛇头碰到控制台边界(上下左右边缘)时,触发穿墙效果:从对应边界的对侧穿出(例如蛇头从顶部边界穿出,直接出现在底部对应x坐标位置;从左侧边界穿出,直接出现在右侧对应y坐标位置);
  • 蛇头碰到自身身体的任意一节,游戏结束,无其他结束条件;结束后显示死亡提示,按任意键返回主菜单。

4. 食物规则

  • 初始时在控制台随机位置生成一个食物(用▲表示);
  • 蛇头碰到食物后,蛇身长度+1,并在新的随机位置生成下一个食物(食物不能生成在蛇身身上);
  • 记录蛇身长度,用于更新历史最大长度。

5. 界面要求

  • 菜单界面:简洁清晰,显示选项及操作提示(如“请输入选项[1-3]:”);
  • 游戏界面:用字符绘制游戏区域,蛇头用◆表示,蛇身用●表示,食物用▲表示,墙用■表示,空白区域用空格;
  • 界面大小固定(建议20行×40列,可自行调整,需在代码中明确);
  • 游戏结束后,显示“蛇撞到自身,游戏结束!”,随后提示“按任意键返回菜单”,按下任意键后回到主菜单。

6. 输入输出

  • 菜单操作:输入1/2/3选择对应功能,输入错误时提示“输入错误,请重新输入”并重新显示菜单;
  • 游戏过程:无需玩家输入任何指令,自动运行,仅在死亡后需要按下任意键返回菜单;
  • 查看历史最大长度:输入2后,显示“历史最大长度:XXX”,提示“按任意键返回菜单”,按下任意键回到主菜单;
  • 退出程序:输入3后,程序正常退出。

7. 技术要求

  • 随机数生成需使用rand()srand()函数,确保方向随机且符合规则;
  • 菜单的循环显示、游戏的循环运行,需通过循环语句实现,确保逻辑连贯;
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>
#include <io.h>
#include <process.h>
#include <time.h>
#include <stdlib.h>
#include <conio.h>

#define WIDTH 40
#define HEIGHT 20
#define INIT_LEN 3

int max_len = INIT_LEN;
int snake_x[1000];
int snake_y[1000];
int len;
int dir;
int step_cnt;
int food_x, food_y;
int game_over;

void menu() {
	system("cls");
	printf("===== 随机方向贪吃蛇 =====\n");
	printf("  1. 开始游戏\n");
	printf("  2. 查看历史最大长度\n");
	printf("  3. 退出程序\n");
	printf("==========================\n");
	printf("请输入选项[1-3]:");
}

void append(char* dest, int* pos, const char* src) {
	int i = 0;
	while (src[i] != '\0') {
		dest[*pos] = src[i];
		(*pos)++;
		i++;
	}
}

void gotoxy(int x, int y) {
	COORD pos = { x,y };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

void rand_dir() {
	int new_dir;
	do {
		new_dir = rand() % 4 + 1;
	} while ((dir == 1 && new_dir == 2) ||
			 (dir == 2 && new_dir == 1) ||
			 (dir == 3 && new_dir == 4) ||
			 (dir == 4 && new_dir == 3));
	dir = new_dir;
}

void create_food() {
	int flag;
	do {
		flag = 1;
		food_x = rand() % (WIDTH - 2) + 1;
		food_y = rand() % (HEIGHT - 2) + 1;
		for (int i = 0; i < len; i++) {
			if (snake_x[i] == food_x && snake_y[i] == food_y) {
				flag = 0;
				break;
			}
		}
	} while (!flag);
}

int hit_self() {
	for (int i = 1; i < len; i++) {
		if (snake_x[0] == snake_x[i] && snake_y[0] == snake_y[i]) {
			return 1;
		}
	}
	return 0;
}

void cross_wall() {
	if (snake_x[0] <= 0)
		snake_x[0] = WIDTH - 2;
	if (snake_x[0] >= WIDTH - 1)
		snake_x[0] = 1;
	if (snake_y[0] <= 0)
		snake_y[0] = HEIGHT - 2;
	if (snake_y[0] >= HEIGHT - 1)
		snake_y[0] = 1;
}

void move_snake() {
	step_cnt++;
	if (step_cnt >= 3) {
		rand_dir();
		step_cnt = 0;
	}
	
	for (int i = len - 1; i > 0; i--) {
		snake_x[i] = snake_x[i - 1];
		snake_y[i] = snake_y[i - 1];
	}
	
	switch (dir) {
		case 1: snake_y[0]--; break;
		case 2: snake_y[0]++; break;
		case 3: snake_x[0]--; break;
		case 4: snake_x[0]++; break;
	}
	
	cross_wall();
	
	if (hit_self()) {
		game_over = 1;
		if (len > max_len)
			max_len = len;
		return;
	}
	
	if (snake_x[0] == food_x && snake_y[0] == food_y) {
		len++;
		create_food();
	}
}

void game() {
	len = INIT_LEN;
	for (int i = 0; i < len; i++) {
		snake_x[i] = WIDTH / 2 + i;
		snake_y[i] = HEIGHT / 2;
	}
	dir = 3;
	step_cnt = 0;
	game_over = 0;
	create_food();
	
	while (!game_over) {
		gotoxy(0, 0);
		char screen[4096];
		int pos = 0;
		
		for (int j = 0; j < HEIGHT; j++) {
			for (int i = 0; i < WIDTH; i++) {
				if (j == 0 || j == HEIGHT - 1 || i == 0 || i == WIDTH - 1) {
					append(screen, &pos, "■");
				} else if (i == snake_x[0] && j == snake_y[0]) {
					append(screen, &pos, "◆");
				} else if (i == food_x && j == food_y) {
					append(screen, &pos, "▲");
				} else {
					int is_snake = 0;
					for (int z = 1; z < len; z++) {
						if (i == snake_x[z] && j == snake_y[z]) {
							append(screen, &pos, "●");
							is_snake = 1;
							break;
						}
					}
					if (!is_snake)
						append(screen, &pos, " ");
				}
			}
			append(screen, &pos, "\n");
		}
		char len_str[30];
		sprintf(len_str, " 当前长度:%d", len);
		append(screen, &pos, len_str);
		screen[pos] = '\0';
		printf("%s", screen);
		Sleep(200);
		move_snake();
	}
	
	gotoxy(10, 9);
	printf("蛇撞到自身,游戏结束!");
	gotoxy(10, 11);
	printf("按任意键返回菜单");
	getch();
}

void ssize() {
	system("cls");
	printf("==========================\n");
	printf("    历史最大长度:%d\n", max_len);
	printf("==========================\n");
	printf("按任意键返回菜单...\n");
	getch();
}

void menu_loop() {
	while (1) {
		menu();
		int choice;
		scanf("%d", &choice);
		switch (choice) {
			case 1: game(); break;
			case 2: ssize(); break;
		case 3:
			system("cls");
			printf("程序正常退出\n");
			exit(0);
		default:
			system("cls");
			printf("输入错误,请重新输入\n");
			Sleep(1000);
			break;
		}
	}
}

int main() {
	srand((unsigned)time(NULL));
	menu_loop();
	return 0;
}


计算机的随机原理 计算机没有真随机 都是假随机 为了不让随机数显的太规律 就引入了随机种子的概念 根据随机种子去产生随机数

C语言中的随机数生成原理

计算机无法生成真正的随机数,通常使用伪随机数生成器(PRNG)来模拟随机性。伪随机数序列由算法生成,看似随机但实际上是可预测的。

伪随机数生成器的工作原理

伪随机数生成器通过一个初始值(种子)和确定性算法生成一系列数字。如果种子相同,生成的序列完全相同。C语言中的rand()函数是典型的线性同余生成器(LCG),其公式为:

[ X_{n+1} = (a \times X_n + c) \mod m ]

其中:

  • (X_n) 是当前随机数
  • (a)、(c)、(m) 是预定义的常数
  • 结果通过取模运算限制在一定范围内

随机种子的作用

种子(srand()的参数)决定了伪随机序列的起点。如果不设置种子或使用固定种子(如srand(1)),每次程序运行时rand()会生成相同的序列。为了使序列看起来更随机,通常使用当前时间作为种子:

#include <stdlib.h>
#include <time.h>

srand(time(NULL));
int random_number = rand();

随机数的局限性

伪随机数存在以下问题:

  • 序列周期性重复,取决于算法实现
  • 可预测性,知道种子和算法即可重现序列
  • 不均匀分布,某些实现可能导致数值分布不均

改进随机性的方法

使用更复杂的算法或外部熵源可以提升随机性。例如:

  • 密码学安全的随机数生成器(如/dev/urandom
  • 混合多个熵源(时间、硬件噪声等)
// 从/dev/urandom读取随机字节(Linux)
unsigned int seed;
FILE* f = fopen("/dev/urandom", "rb");
fread(&seed, sizeof(seed), 1, f);
fclose(f);
srand(seed);

应用场景建议

  • 普通场景:rand()配合时间种子足够
  • 安全敏感场景:使用专用库(如OpenSSL的RAND_bytes()
  • 科学计算:选择高质量PRNG(如Mersenne Twister)

题目二:KMP算法实现与原理详解

背景介绍(必看,算法逻辑详解)

KMP算法全称为Knuth-Morris-Pratt算法,是一种高效的单模式串字符串匹配算法,核心解决了暴力匹配(BF算法)中“主串指针频繁回退、导致大量重复比对”的低效问题。与BF算法最坏时间复杂度O(n×m)(n为主串长度,m为子串长度)相比,KMP算法通过预处理子串生成辅助数组(next数组),实现主串指针永不回退,整体时间复杂度优化至O(n+m),适用于长文本查找关键字、日志检索、报文解析等场景。

核心核心逻辑:不重复比对主串中已比对过的字符,利用子串自身的“前缀与后缀重复特征”,让子串指针在不匹配时回退到最优位置,减少无效比对。

1. 核心概念(重中之重,理解后才能实现算法)

  • 前缀:对于子串pattern,不包含最后一个字符、以第一个字符开头的所有连续子串。
    示例:子串"ababc"(下标0-4),前缀包括:"a"(下标0)、"ab"(0-1)、"aba"(0-2)、"abab"(0-3),不包含整个子串
  • 后缀:对于子串pattern,不包含第一个字符、以最后一个字符结尾的所有连续子串。
    示例:子串"ababc",后缀包括:"c"(下标4)、"bc"(3-4)、"abc"(2-4)、"babc"(1-4),不包含整个子串
  • 最长公共前后缀长度(LPS):子串的某一段前缀和某一段后缀完全相同,且长度最长,这个长度就是最长公共前后缀长度。
    示例1:子串"abab"(0-3),前缀有"a""ab""aba",后缀有"b""ab""bab",最长且相同的是"ab",长度为2;
    示例2:子串"abc",前缀有"a""ab",后缀有"c""bc",无相同前后缀,长度为0;
    示例3:子串"aaaa",前缀"aaa"与后缀"aaa"相同,最长公共前后缀长度为3。

2. next数组(KMP算法核心,辅助子串指针回退)

2.1 next数组的定义

next数组是与子串长度相同的整型数组,数组的下标j对应子串的第j个字符(从0开始),next[j]的值表示:子串中前j+1个字符组成的子串(即pattern[0..j])的最长公共前后缀长度

2.2 next数组的手动计算示例(必看,掌握计算逻辑)

以子串pattern = "ababc"(长度5,下标0-4)为例,分步计算next数组:

  • j=0:子串为"a"(仅1个字符),无前缀、无后缀,最长公共前后缀长度为0 → next[0] = 0;
  • j=1:子串为"ab",前缀"a",后缀"b",无相同前后缀 → next[1] = 0;
  • j=2:子串为"aba",前缀"a""ab",后缀"a""ba",最长相同前后缀为"a",长度1 → next[2] = 1;
  • j=3:子串为"abab",前缀"a""ab""aba",后缀"b""ab""bab",最长相同前后缀为"ab",长度2 → next[3] = 2;
  • j=4:子串为"ababc",前缀"a""ab""aba""abab",后缀"c""bc""abc""babc",无相同前后缀 → next[4] = 0;

最终next数组为:[0, 0, 1, 2, 0]

2.3 next数组的核心作用

当主串str的第i个字符与子串pattern的第j个字符不匹配时,无需将主串指针i回退,也无需将子串指针j重置为0,而是让j回退到next[j-1]的位置,继续与主串的第i个字符比对。这样就跳过了所有无效的重复比对,大幅提升匹配效率。

示例:主串"ababxabcabx",子串"ababc",当i=4(主串字符"x")、j=4(子串字符"c")不匹配时,j回退到next[3] = 2,用子串第2个字符("a")继续与主串第4个字符("x")比对,无需回退i。

3. KMP匹配完整流程( step-by-step 详解)

假设主串为str(长度n),子串为pattern(长度m),next数组已预处理完成,匹配流程分为3个阶段,结合具体示例说明:

示例:主串str = "ababcabcabx"(n=11),子串pattern = "ababc"(m=5),next数组 = [0,0,1,2,0]

  1. 初始化:主串指针i = 0,子串指针j = 0;
  1. 匹配阶段:循环遍历主串(i < n):
                      情况1:str[i] == pattern[j] → 字符匹配,i和j同时向后移动1位(i++,j++);
    示例:i=0、j=0(a==a)→ i=1、j=1;i=1、j=1(b==b)→ i=2、j=2;以此类推,直到i=4、j=4(c==c),j=5(等于子串长度m=5),匹配成功。
  1. 情况2:str[i] != pattern[j] → 字符不匹配,分两种子情况:
                              子情况2.1:j > 0 → j回退到next[j-1]的位置,继续比对(i不回退);
    示例:若主串为"ababxabcabx",当i=4、j=4时,str[4] = 'x' != pattern[4] = 'c',j=next[3] = 2,继续用pattern[2]与str[4]比对。
  1. 子情况2.2:j == 0 → 子串第一个字符就不匹配,j保持0不变,i向后移动1位(i++);
    示例:若主串第一个字符为'd',与pattern[0]='a'不匹配,i++,继续比对主串下一个字符。
  1. 结束判断:
                     匹配成功:当j == m(子串指针遍历完整个子串),说明子串完全匹配,返回匹配的起始位置(i - j);
    示例:上述匹配成功时,i=5、j=5,起始位置 = 5-5 = 0,即子串从主串下标0开始匹配。
  1. 匹配失败:当i遍历完整个主串(i == n),j仍未达到m,说明主串中无匹配的子串,返回-1。

4. 实现要求

4.1 函数实现

  • 实现next数组生成函数:输入为子串(字符串)和子串长度,输出为next数组(整型数组);函数需严格遵循next数组的计算逻辑,确保每个下标的值正确。
  • 实现KMP匹配函数:输入为主串(字符串)、子串(字符串),输出为匹配的起始位置(整数);若找到匹配,返回起始下标(从0开始);若未找到,返回-1;匹配过程需严格遵循上述KMP匹配流程,主串指针不回退。

4.2 主函数测试

  • 定义主串和子串(可直接在代码中定义测试用例,也可允许用户输入主串和子串);
  • 调用next数组生成函数,生成子串对应的next数组(可选择打印next数组,用于验证正确性);
  • 调用KMP匹配函数,获取匹配结果,并输出:
                      匹配成功:打印“子串在主串的起始位置:x”(x为具体下标);
  • 匹配失败:打印“未找到匹配的子串”。

至少设计3组测试用例,覆盖“完全匹配”“部分匹配”“无匹配”三种场景,验证算法正确性。

4.3 补充要求

  • 算法逻辑需与上述描述完全一致,严禁使用暴力匹配逻辑替代KMP算法;
  • 代码需添加清晰注释,重点标注next数组计算、KMP匹配流程的关键步骤;
  • 子串长度不超过100,主串长度不超过200,确保代码可正常运行。
#include <stdio.h>
#include <string.h>

// ====================== 1. 生成 next 数组 ======================
// pattern:模式串(子串)
// next:存储最长公共前后缀长度的数组
// m:模式串长度
void getNext(char *pattern, int *next, int m) {
	// 初始化:len = 最长公共前后缀的当前长度,从 0 开始
	int len = 0;
	// 第一个字符没有前后缀,next[0] 固定为 0
	next[0] = 0;
	int i = 1;
	
	// 从第二个字符开始遍历模式串
	while (i < m) {
		if (pattern[i] == pattern[len]) {
			// 字符相等:最长公共前后缀长度 +1
			len++;
			next[i] = len;
			i++;
		} else {
			// 不相等
			if (len != 0) {
				// 回退到上一个可能的最长公共前后缀位置
				len = next[len - 1];
			} else {
				// 没有公共前后缀,赋值 0
				next[i] = 0;
				i++;
			}
		}
	}
}

// ====================== 2. KMP 匹配函数 ======================
// str:主串
// pattern:模式串(子串)
// 返回值:匹配成功 → 起始下标;匹配失败 → -1
int KMP(char *str, char *pattern) {
	int n = strlen(str);       // 主串长度
	int m = strlen(pattern);   // 子串长度
	
	// 子串为空,直接返回 0(题目不用考虑)
	if (m == 0) return 0;
	// 主串比子串短,不可能匹配
	if (n < m) return -1;
	
	// 定义 next 数组(题目要求子串长度 ≤100)
	int next[100];
	// 先生成 next 数组
	getNext(pattern, next, m);
	
	int i = 0;  // 主串指针(永不回退)
	int j = 0;  // 子串指针
	
	while (i < n) {
		if (str[i] == pattern[j]) {
			// 【情况1】字符匹配:两个指针同时后移
			i++;
			j++;
		}
		
		// 【匹配成功】子串遍历完了
		if (j == m) {
			// 返回匹配起始位置 = i - j
			return i - j;
		}
		// 【情况2】字符不匹配
		else if (i < n && str[i] != pattern[j]) {
			if (j != 0) {
				// j 回退到最优位置,i 不动
				j = next[j - 1];
			} else {
				// 子串第一个字符就不匹配,i 后移
				i++;
			}
		}
	}
	
	// 循环结束都没匹配成功
	return -1;
}

// ====================== 3. 测试函数(封装) ======================
void testKMP(char *str, char *pattern) {
	printf("主串:%s\n", str);
	printf("子串:%s\n", pattern);
	
	int pos = KMP(str, pattern);
	
	if (pos != -1) {
		printf("匹配成功!子串在主串的起始位置:%d\n", pos);
	} else {
		printf("未找到匹配的子串\n");
	}
	printf("----------------------------------------\n");
}

// ====================== 4. 主函数(3组测试用例) ======================
int main() {
	printf("========== KMP 算法测试 ==========\n\n");
	
	// 测试用例 1:完全匹配(开头匹配)
	char str1[] = "ababcabcabx";
	char pattern1[] = "ababc";
	testKMP(str1, pattern1);
	
	// 测试用例 2:部分匹配(中间匹配)
	char str2[] = "abcabababcxyz";
	char pattern2[] = "ababc";
	testKMP(str2, pattern2);
	
	// 测试用例 3:无匹配
	char str3[] = "abcdefg123456";
	char pattern3[] = "ababc";
	testKMP(str3, pattern3);
	
	return 0;
}

源码链接: https://pan.quark.cn/s/fa13cd6c6c8d Chrome浏览器作为一款备受青睐的网页浏览器,凭借其出色的稳定性和运行速度获得了广泛认可。 然而出于安全考量,Chrome系统默认不兼容ActiveX插件,因为ActiveX技术主要应用于Internet Explorer,它赋予网页内容与用户本地系统交互的能力,但同时也可能引发潜在的安全隐患。 不过在某些特定工作场景下,比如在企业内部网络环境或需要与老旧应用程序整合时,可能仍需在Chrome中启用ActiveX控件。 为此我们必须掌握在Chrome浏览器下加载和运用ActiveX的方法。 首先需要明确ActiveX的本质。 ActiveX是由微软设计的一种技术框架,旨在开发可在网页环境中运行的控件,这些控件能够完成多种功能,包括视频播放、应用程序组件运行或与硬件设备通信等。 ActiveX控件多以OCX(OLE控件)格式发布。 在Chrome浏览器中启用ActiveX需要采取额外措施,因为该浏览器本身并不支持此项技术。 以下是几种常见的解决方案: 1. **应用Chrome的兼容性设置**:部分Chrome版本提供了" --enable-internal-activex"命令行参数,可通过此参数使浏览器具备加载ActiveX控件的能力。 用户可在启动Chrome时,于快捷方式的目标路径后附加该参数来激活此功能。 例如:"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --enable-internal-activex。 2. **安装第三方插件**:市面上存在一些第三方插件,例如"IE Tab"或"ActiveX Con...
标题SpringBoot与微信小程序结合的健康饮食平台研究AI更换标题第1章引言介绍健康饮食平台的研究背景、意义、国内外研究现状、论文方法及创新点。1.1研究背景与意义阐述健康饮食平台在当前社会的重要性及其市场需求。1.2国内外研究现状分析国内外健康饮食平台的发展现状及趋势。1.3研究方法及创新点概述本文采用的研究方法和技术创新点。第2章相关理论总结健康饮食、SpringBoot及微信小程序的相关理论。2.1健康饮食理论介绍健康饮食的基本原则和营养学知识。2.2SpringBoot框架阐述SpringBoot框架的特点、优势及在项目中的应用。2.3微信小程序技术介绍微信小程序的开发技术、特点及其用户群体。第3章健康饮食平台设计详细介绍健康饮食平台的设计方案,包括前端和后端设计。3.1平台架构设计给出平台的整体架构、模块划分及交互流程。3.2数据库设计介绍数据库的设计思路、表结构及数据关系。3.3前后端交互设计阐述前后端数据交互的方式、接口设计及安全性考虑。第4章微信小程序实现介绍微信小程序的具体实现过程,包括页面设计、功能实现等。4.1页面设计与布局给出微信小程序的页面设计思路、布局及交互效果。4.2功能实现与测试详细介绍微信小程序各项功能的实现过程及测试方法。4.3用户体验优化阐述如何提升微信小程序的用户体验,包括界面优化、性能优化等。第5章平台测试与优化对健康饮食平台进行测试,并根据测试结果进行优化。5.1测试环境与数据介绍测试环境、测试数据及测试方法。5.2测试结果分析从功能、性能、用户体验等方面对测试结果进行详细分析。5.3平台优化策略根据测试结果提出平台优化策略,包括代码优化、功能改进等。第6章结论与展望总结本文的研究成果,并展望未来的研究方向。6.1研究结论概括本文的主要研究结论和平台实现效果。6.2展望指出本文研究的不足之处以及未来研究的方向和改进点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

猫佛佛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值