C语言数组+内存编址+推箱子小游戏

上一篇文章:C语言代码示范与讲解+C语言编程规范及基础语法+编程实战

写在前面

  • Talk is cheap. Show me the code.——Linux 的创始人 Linus Torvalds
  • Everybody in this country should learn how to program a computer, because it teaches you to think.——苹果创始人Steve Jobs
  • Stay hungry. Stay foolish.——《Whole Earth Catalog》

数组

一维数组定义、引用

  • 数组的定义:类型名 数组名 [数组长度]
  • 代码案例
    请看代码注释
#include <stdio.h>
int main()
{
	int i;
	double average, sum;
	int a[10];		//数组的定义:类型名 数组名 [数组长度]
	//数组长度可以为整型常数也可以为字符型常数,但不能为变量
	printf("Enter 10 integers:\n");
	sum = 0;
	for (i = 0; i < 10; i++)//利用for循环引用数组下标
	{
		scanf_s("%d",&a[i]);//数组的引用
		sum = sum + a[i];//将所有输入的成绩累加存入sum中
	}
	average = sum / 10;//计算平均成绩
	printf("average=%.2f\n",average);//输出平均成绩,并保留两位小数
	printf(">average:");
	for (i = 0; i < 10; i++)//输出大于平均成绩的分数
		if (a[i] > average)
			printf("%d ",a[i]);
	putchar('\n');
	return 0;
}

注意

  • 数组长度可以为整型常数也可以为字符型常数,但不能为变量 数组长度也可以是表达式,只要表达式的结果是常数就可。
    例如:
#define N 5;
double b[N*5];//数组长度为25
  • 在定义数组之后,系统根据数组中元素的类型及个数在内存中分配了一段连续的存储单元用于存放数组中的各个元素,并对这些单元进行连续编号,下标,以区分不同的单元。每个单元所需要的字节数由数组定义时给定的类型来确定。假设定义了short int型数组,short型占两个字节,如果第一个内存地址为4010,那么第二个为4012,第三个4014往后依次类推。数组的引用要指定下标,数组名 [下标],下标可以是整型表达式,它的合理取值范围为0到数组长度减1在引用时不要让下标越界,一旦发生下标越界,就会把数据写到其他变量所占的存储单元中,甚至写入程序代码段,有可能造成不可预料的运行结果
  • 一维数组的初始化:类型名 数组名 [数组长度] = {初值表};初值表依次放着数组元素的初值。例如:int a [10] ={1,2,3,4,5,6,7,8,9,10};
  • 标准C语言规定,只有静态存储的数组才能初始化,但一般C语言编译系统都允许对动态存储的数组赋初值。对静态数组初始化例如:
    static int b [5] = {1,2,3,4,5};初始化静态数组b,如果没有初始化,系统会自动给所有的数组元素赋值0
  • 如果动态数组没有初始化,会出现什么情况呢?
    在这里插入图片描述
  • 数组的初始化也可以只初始化一部分元素,例如:static int b [5] = {1,2,3};
  • 数组初始化时,如果对全部元素都赋了初值,就可以省略数组长度,例如:int a [] = {1,2,3,4,5};
  • 为了提高程序的可读性,建议不要省略数组长度
  • 可以在一个类型声明中声明多个数组
    例如:
double salary[8],tax[8];
  • 也可以变量和数组进行混合声明
    例如:
int n, score[30];
  • 使用一维数组输出斐波那契数列的前十项
#include <stdio.h>
int main()
{
	int i;
	int fib[10] = {1,1};//数组初始化,生成斐波那契数列的前两项
	//计算斐波那契数列剩余的8项
	//数组的应用离不开循环。将数组的下标作为循环变量,通过循环,就可以对数组的所有元素逐个进行处理
	for (i = 2; i < 10; i++)
		fib[i] = fib[i - 1] + fib[i - 2];
	//输出斐波那契数列
	for (i = 0; i < 0; i++)
		printf("%6d",fib[i]);
	if ((i + 1) % 5 == 0)//如果输出了5个,换行
		putchar('\n');
	return 0;
}
  • 反序输出
#include <stdio.h>
main()	//不写函数类型默认为整型,不写参数,默认空参数void,可以写,也可以省略,返回值也可以没有
{
	int n,a[10];
	printf("input 10 data:\n");
	for(n=0;n<10;n++)
		scanf("%d",&a[n]);
	putchar('\n');
	for(n=9;n>=0;n--)
		printf("%d",a[n]);
}

二进制转换为十进制补充

在本专栏上一篇博客中最后讲了利用数组写的一个二进制转十进制的代码,现在再改动一下拿出来。加了一个do-while循环,当输入了非法字符时,打印“输入错误!请重新输入!”直到输入正确,进行转换。int型无法储存31位这么大的数字,所以只能用数组来存储。

#include <stdio.h>
#include <math.h>
int main()
{
	int er[31];
	int i, j, count, sum, k, m, flag, a;
	char digit;
	printf("请输入一个二进制数(该二进制数最大为1111111111111111111111111111111):\n");
	//count=0;不能在外面赋值
	do
	{
		//for (a = 0; a <= 30; a++)
		//{
		//	er[a] = 0;	//这个for循环可要可不要,不影响结果,目的是将上一次输入的结果清零,但是因为有了count统计输入位数,所以省去也可以
		//}

		flag = count = 0;
		for (i = 0; i <= 31; i++)
		{
			digit = getchar();
			if (digit == '1')
			{
				er[i] = 1;
				count++;
			}
			else if (digit == '0')
			{
				er[i] = 0;
				count++;
			}
			else if (digit == '\n')
				break;
			else
			{
				printf("输入错误!请重新输入!\n");
				flag = 1;
			}
		}
	} while (flag == 1);
	sum = m = 0;
	for (k = count - 1; k >= 0; k--)
	{
		sum = sum + er[k] * pow(2, m);
		m++;
	}
	printf("%d", sum);

	return 0;
}
  • 找出数组的最小值和它所对应的下标
#include <stdio.h>
int main()
{
	int i, index, n;
	int a[10];
	printf("Enter n:");
	scanf_s("%i",&n);
	printf("Enter %d integers:",n);
	for (i = 0; i < n; i++)
		scanf_s("%d",&a[i]);//Visual Studio编译器中的scanf()函数为scanf_s()
	index = 0;
	for (i = 1; i < n; i++)
		if (a[i] < a[index])
			index = i;
	printf("min is %i\tsub is %d\n",a[index],index);
	{		//复合语句
		int temp;	//定义复合语句中的局部变量,有效范围只在复合语句范围内
		temp = a[index];
		a[index] = a[0];
		a[0] = temp;	//将最小值与数组第一个数进行交换并输出数组
		for (i = 0; i < n; i++)
			printf("%d ",a[i]);
	}

	return 0;
}

选择法排序和冒泡法排序

输入n个数,将它们从小到大排序(选择法排序)

#include <stdio.h>
int main()
{
	int a[10];
	int n, i, k, index, temp;
	printf("Enter n:");
	scanf_s("%d",&n);
	printf("Enter %d integers:",n);
	for (i = 0; i < n; i++)
		scanf_s("%d",&a[i]);
	for (k = 0; k < n - 1; k++)//只需要循环n-1次就可以了
	{
		index = k;//
		for (i = k + 1; i < n; i++)//从第k项往后找出最小的数
			if (a[i] < a[index])
				index = i;
		temp = a[index];//将最小的数赋给中间量
		a[index] = a[k];//通过中间变量交换数值
		a[k] = temp;//通过中间变量交换数值
	}
	printf("After sorted:");//排序后输出
	for (i = 0; i < n; i++)
		printf("%d ",a[i]);
	putchar('\n');
	return 0;
}
  • 输入n个数,将它们从小到大排序(冒泡法排序)
#include <stdio.h>
main()
{
	int a[10]={12,4,56,79,90,5,32,11,54,2};
	int i, j, temp;
	for(i=0;i<9;i++)
		for(j=0;j<9-i;j++)
		{
			if(a[j]>a[j+1])
			{
				temp=a[j];//交换二者的值
				a[j]=a[j+1];//交换二者的值
				a[j+1]=temp;//交换二者的值
			}
		}
	for(i=0;i<10;i++)
		printf("%d   ",a[i]);
}

执行过程:
外层循环i=0时,
a[0]与a[1]比较,结果是12大于4,所以交换二者的值,此时的顺序为4,12,56,79,90,5,32,11,54,2
然后a[1]与a[2]比较,结果是12小于56,所以不交换值,此时的顺序为4,12,56,79,90,5,32,11,54,2
然后a[2]与a[3]比较,结果是56小于79,所以不交换值,此时的顺序为4,12,56,79,90,5,32,11,54,2
然后a[3]与a[4]比较,结果是79小于90,所以不交换值,此时的顺序为4,12,56,79,90,5,32,11,54,2
然后a[4]与a[5]比较,结果是90大于5,所以交换值,此时的顺序为4,12,56,79,5,90,32,11,54,2
然后a[5]与a[6]比较,结果是90大于32,所以交换值,此时的顺序为4,12,56,79,5,32,90,11,54,2
依次类推… …
最后i=0时的内层循环结束后,会选出最大的数90,并被推到了最后,此时顺序为4,12,56,79,5,32,11,54,2,90
然后外层循环i++,i变为了1,再次执行内层循环,由于已经将最大数90推到了最后,所以这次执行内层循环时,只需要从0开始,执行到9-i,总共执行了9次,把前面9项中的最大值推到90的前面
依次类推… …
最后当外层循环也执行完毕后,就可以实现从小到大的排序了,即
2,4,5,11,12,32,54,56,79,90

  • 在数组中查找一个数(一)
#include <stdio.h>
int main(void)
{
	int i, flag, x;
	int a[5];	//定义数组,数组长度为5
	printf("Enter 5 integers:\n");
	for (i = 0; i < 5; i++)
		scanf_s("%d",&a[i]);	//输入5个数
	printf("Enter x:\n");
	scanf_s("%d",&x);		//输入x的值,在五个数中查找x
	for (i = 0; i < 5; i++)
		if (a[i] == x)
		{
			printf("Index is %d\n", i);//输出这个数的下标
			flag = 1;
			break;
		}
	if (flag == 0)
		printf("Not found\n");//判断flag是否等于零,等于零说明没找到

	return 0;
}
  • 在数组中查找一个数(二、顺序查找法)
#include <stdio.h>
main()
//不写函数类型默认为整型,不写参数,默认空参数void,可以写,也可以省略,返回值也可以没有
{
	int x;
	int i;
	int a[10] = {3,23,45,67,34,22,89,78,73,68};
	printf("input x:\n");
	scanf("%d",&x);
	for (i = 0; i < 10; i++)
		if (x == a[i])
			break;
	if (i >= 10)//如果i大于等于10,说明没有找到
		printf("not found\n");
	else
		printf("The location of this number is %d\n",i);
}
  • 在数组中查找一个数(三、二分查找法)
    顺序查找法虽然容易实现,但是效率不高二分查找法,又叫折半查找法,是一种效率较高的查找方法,适用于已排好序的数列基本思路,将所要查找的数与已知数列的中间的那个数进行比较大小,缩小一半的范围;然后在缩小的范围内再取中间数进行比较大小,再次缩小范围,直到找到为止。这样进行查找效率更加高
#include <stdio.h>
void main()
{
	int a[10]={-2,0,6,18,23,56,80,100,130,165};
	int x;
	int bot,mid,top;	//定义三个位置指示器
	printf("input x:\n");
	scanf("%d",&x);
	top=0;bot=9;mid=(top+bot)/2;	//为指针赋初值
	while (bot>top)
	{
		if(x==a[mid])
		{
			printf("the location is %d\n",mid);
			break;
		}
		else
		{
			if(x>a[mid])
			{
				top=mid+1;
				mid=(top+bot)/2;
			}
			else
			{
				bot=mid-1;
				mid=(bot+bot)/2;
			}
		}
	}
	if(bot<=top)
		printf("not found!");
}
  • 有序数组插入
#include <stdio.h>
main()
{
	int a[7]={3,5,7,12,18,22};
	int i,x;
	printf("input x:\n");
	scanf("%d",&x);
	for(i=5;i>=0;i--)
	{
		if(a[i]>x)
			a[i+1]=a[i];
		else
			break;
	}
	a[i+1]=x;
	for(i=0;i<7;i++)
		printf("%d  ",a[i]);
}
  • 有序数组删除
#include <stdio.h>
main()
{
	int a[7]={3,5,7,10,12,18,22};
	int i,x;
	printf("input x:\n");
	scanf("%d",&x);
	for(i=0;i<7;i++)
	{
		if(a[i]==x)
			break;
	}
	for(;i<8-1;i++)//此时i省略,i的值应为上一次for循环结束后的i的值
		a[i]=a[i+1];
	printf("after delete:\n");
	for(i=0;i<6;i++)
		printf("%d  ",a[i]);
}

在这里插入图片描述
例如运行后输入的数值为10,对应的下标为3,所以第一个循环结束后的i的值为3,所以在第二次循环开始时,省略了i的值,就是上一次循环结束后的i的值

二维数组定义、引用

  • 定义
    类型名 数组名 [行长度][列长度]
  • 引用
    数组名 [行下标][列下标]
  • 注意:
    行和列的下标取值范围都是从0开始,到行和列的长度减1
  • 二维数组在内存中的存放形
    例如:a[3][2]
    存放形式为:
    在这里插入图片描述
  • 二维数组初始化
    • 可以分行赋值
      例如:int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
      也可以写成:
      int a[3][3]=
      {
      {1,2,3},
      {4,5,6},
      {7,8,9}
      };
      这种写法更清晰更明了
      也可以写成:
      int a[][3]=
      {
      {1,2,3},
      {4,5,6},
      {7,8,9}
      };
      如果每个元素都赋了初值,则行长度可以省略,但列长度不能省略,为了提高可读性,避免出错,不建议省略
    • 也可以按照顺序单行赋值(不建议):
      int a[3][3]={1,2,3,4,5,6,7,8,9};

      int a[][3]={1,2,3,4,5,6,7,8,9};
      静态二维数组不赋初值的元素系统会自动赋初值为0
      例如:
      在这里插入图片描述
      标准C语言动态二维数组不赋初值是不行的,但是大多数编译器会给不赋初值的二维数组元素自动赋初值
      例如:
      在这里插入图片描述
      动、静态二维数组都可以只赋一部分元素。
  • 找出矩阵中最大值所在的位置
#include <stdio.h>
int main()
{
	int col, i, j, row;
	int a[3][2];//定义一个二维数组
	printf("Enter 6 integers:\n");//输入提示
	for (i = 0; i < 3; i++)//利用两个for循环的嵌套输入6个数
		for (j = 0; j < 2; j++)
			scanf_s("%d",&a[i][j]);
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 2; j++)//利用两个for循环输出矩阵,外层循环控制矩阵的层数,内层循环控制输出一行的内容
			printf("%4d",a[i][j]);
		putchar('\n');//输出完一行后回车输出下一行
	}
	col = row = 0;
	for(i=0;i<3;i++)
		for (j = 0; j < 2; j++)
			if (a[i][j] > a[row][col])//利用两个for循环查找最大值的位置
			{
				row = i;
				col = j;
			}
	printf("max=a[%d][%d]=%d\n",row,col,a[row][col]);输出最大值
	return 0;
}

在这里插入图片描述

  • 按矩阵形式输出二维数组
#include <stdio.h>
int main()
{
	int i, j;
	int a[3][2];
	for (i = 0; i < 3; i++)
		for (j = 0; j < 2; j++)
			a[i][j] = i + j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 2; j++)
			printf("%4d", a[i][j]);
		putchar('\n');
	}
	return 0;
}

执行顺序为
在这里插入图片描述

  • 简单了解多维数组
    例如:
int a[2][3][5];

1.声明了一个三维数组,其中第一个下标的取值范围为0到1,第二个下标的取值范围为0到2,第三个下标的取值范围为0到4。数组中共包含了2乘3乘5=30个元素。
2.多维数组在内存中也是顺序存放。
3.三维数组可以理解为一个立体空间

  • 计算某个日期对应该年的第几天
#include <stdio.h>
int day_of_year(int year,int month,int day)
{
	int k,leap;
	int tab[2][13]=
	{
	{0,31,28,31,30,31,30,31,31,30,31,30,31},
	{0,31,29,31,30,31,30,31,31,30,31,30,31},
	};
	leap=(year%4==0&&year%100!=0||year%400==0);//判断是否为闰年,逻辑表达式结果为1时真,为0时假,把0或1赋给leap
	for(k=1;k<month;k++)
		day=day+tab[leap][k];
	return day;
}
int main()
{
	int year,month,day1,day2;
	printf("请按照顺序输入年月日:\n");
	scanf("%d%d%d",&year,&month,&day1);
	day2=day_of_year(year,month,day1);
	printf("该日期是该年的第%d天。\n",day2);
	return 0;
}

在这里插入图片描述

  • 按列输入和按行输入(简单了解)
#include <stdio.h>
void main()
{
	int a[2][3],b[2][3],i,j;
	for (j = 0; j < 3; j++)
		for (i = 0; i < 2; i++)
			scanf("%d",&a[i][j]);
	for (i = 0; i < 2; i++)
		for (j = 0; j < 3; j++)
			scanf("%d",&b[i][j]);

	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
			printf("%2d",a[i][j]);
		putchar('\n');
	}
	putchar('\n');
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
			printf("%2d", b[i][j]);
		putchar('\n');
	}
}

在这里插入图片描述
如果外层循环的循环变量为 j 的话,就是按列输入

矩阵转置

  • 首先介绍矩阵术语
    定义一个N行N列的二位数组
    例如:
    a[3][3]=
    {
    {1,2,3},
    {4,5,6},
    {7,8,9},
    };
    从左上角到右下角叫做矩阵的主对角线 把1、5、9连起来的一条线,下标的规律为i==j
    从右上角到左下角叫做矩阵的副对角线 把3、5、7连起来的一条线,下标的规律为i+j==N-1 (N为矩阵的长和宽)
    上三角:主对角线以上的部分 下标规律i<=j
    下三角:主对角线以下的部分 下标规律i>=j
    对于矩阵:
    1 2 3
    4 5 6
    7 8 9
    转置后:
    1 4 7
    2 5 8
    3 6 9
    代码实现:
#include <stdio.h>
void main()
{
	int a[3][3]=
	{
	{1,2,3},
	{4,5,6},
	{7,8,9},	//最后一行后面的逗号可加也可不加,无影响
	};
	int b[3][3];
	int i,j;
	for(i=0;i<3;i++)
		for(j=0;j<3;j++)
			b[i][j]=a[j][i];
	for(i=0;i<3;i++)
	{
		for(j=0;j<3;j++)
			printf("%3d",b[i][j]);
		putchar('\n');	//内层循环完后,即打印完一行后,加上一个回车换行,打印下一行
	}
}

以上是定义了另外一个数组来实现矩阵的转置,也可这样写:

#include <stdio.h>
int main()
{
	int i,j,n,temp;
	int a[6][6];
	printf("Enter n:");
	scanf("%d",&n);
	for(i=0;i<n;i++)
		for(j=0;j<n;j++)
			a[i][j]=i*n+j+1;//用此公式给数组赋初值
	for(i=0;i<n;i++)
		for(j=0;j<n;j++)
			if(i<=j)	//只遍历上三角即可,也可以写成i<j,因为主对角线上的值不用换
			{
				temp=a[i][j];
				a[i][j]=a[j][i];
				a[j][i]=temp;	//通过这三条语句将上三角的值与对应下三角的值进行互换
			}
	for(i=0;i<n;i++)//上下三角值互换后输出
	{
		for(j=0;j<n;j++)
			printf("%4d",a[i][j]);
		printf("\n");
	}
	return 0;
}

这个代码也可以写成:

#include <stdio.h>
int main()
{
	int i, j, n, temp;
	int a[6][6];
	printf("Enter n:");
	scanf_s("%d", &n);
	for (i = 0; i < n; i++)
		for (j = 0; j < n; j++)
			a[i][j] = i * n + j + 1;
	for (i = 0; i < n; i++)
		for (j = i; j < n; j++)	//也可以写成j=i+1;j<n;j++	因为j=i时是主对角线,而主对角线上的值不用换
		{
			temp = a[i][j];
			a[i][j] = a[j][i];
			a[j][i] = temp;
		}
	for (i = 0; i < n; i++)
	{
		for (j = 0; j < n; j++)
			printf("%4d", a[i][j]);
		putchar('\n');
	}
	return 0;
}

矩阵相加

矩阵相加过程比较简单,两个同阶矩阵相加只要对应位上的数字相加即可。
只要在双重循环中实现将a[i][j]+b[i][j]的结果赋给c[i][j]即可。
简单的例子:

#include <stdio.h>
void main()
{
	int a[2][3],b[2][3],c[2][3],i,j;
	printf("please input matrix a:\n");
	for(i=0;i<2;i++)
		for(j=0;j<3;j++)
			scanf("%d",&a[i][j]);
	for(i=0;i<2;i++)
		for(j=0;j<3;j++)
			scanf("%d",&b[i][j]);
	for(i=0;i<2;i++)
		for(j=0;j<3;j++)
			c[i][j]=a[i][j]+b[i][j];
	printf("matrix a is:\n");
	for(i=0;i<2;i++)
	{
		for(j=0;j<3;j++)
			printf("%4d",a[i][j]);
		putchar('\n');
	}
	printf("matrix b is:\n");
	for(i=0;i<2;i++)
	{
		for(j=0;j<3;j++)
			printf("%4d",b[i][j]);
		putchar('\n');
	}
	printf("A+B=:\n");
	for(i=0;i<2;i++)
	{
		for(j=0;j<3;j++)
			printf("%4d",c[i][j]);
		putchar('\n');
	}
}

在这里插入图片描述

矩阵相乘

矩阵也是可以相乘的。矩阵非常重要,在多个学科领域中都会用到。在这里简单了解一下即可。

#include <stdio.h>
void main() //DevC++可能运行不了该程序,需要将main前面的void去掉
{
	int a[3][3],b[3][3];
	int c[3][3]={{0},{0},{0}};//因为c中每个元素作为累加器出现,所以必须初值为0
	//这个语句等价于int c[3][3]={{0,0,0},{0,0,0},{0,0,0},};
	//也可以写成这样int c[3][3]={{0,0,0},{0,0,0},{0,0,0}};最后一个逗号可要可不要
	int i,j,k;
	printf("please input matrix a:\n");
	for(i=0;i<3;i++)
		for(j=0;j<3;j++)
			scanf("%d",&a[i][j]);
	printf("please input matrix b:\n");
	for(i=0;i<3;i++)
		for(j=0;j<3;j++)
			scanf("%d",&b[i][j]);
	for(i=0;i<3;i++)
		for(j=0;j<3;j++)
			for(k=0;k<3;k++)
				c[i][j]+=a[i][k]*b[k][j];
	printf("Matrix a is:\n");
	for(i=0;i<3;i++)
	{
		for(j=0;j<3;j++)
			printf("%4d",a[i][j]);
		putchar('\n');
	}
	printf("Matrix b is:\n");
	for(i=0;i<3;i++)
	{
		for(j=0;j<3;j++)
			printf("%4d",b[i][j]);
		putchar('\n');
	}
	printf("Matrix a*b is:\n");
	for(i=0;i<3;i++)
	{
		for(j=0;j<3;j++)
			printf("%4d",c[i][j]);
		putchar('\n');
	}
}

在这里插入图片描述

字符数组与字符串

字符串与字符数组在下一篇博客中写。下一篇文章
C语言字符数组与字符串+十六进制转十进制

内存编址

计算机中的内存是由一个个的存储单元构成的,为了管理这些存储单元,对每个存储单元进行编号,这就是内存编址。普通的变量是有内存地址的,数组也是有内存地址的。我们要访问一些数据时,都是通过地址进行访问的。内存编址是按照字节进行的,每个字节对应一个地址编号。当程序运行时,系统中会有一个内存分配表,每遇到一次变量声明语句或函数调用语句(函数的形参),系统会根据变量的大小在内存中寻找合适的空间分配,并在内存分配表中增加一行记录,记载变量与内存地址的对应关系。
在这里插入图片描述
定义了一个int型的数组a,数组的长度为10。int型的数据占4个字节。数组名a就是这个数组的首地址,即数组第一个元素a[0]的地址。用%d以十进制的形式输出这个数组的首地址。a+1就是a[1]的地址,a+2就是a[2]的地址。6487536就是地址编号,由于int型占4个字节,所以下一个元素的地址为6487540,加了4,后面依次往后类推。
在这里插入图片描述
如果把数组的类型改为double型的,那么每个地址依次加8,因为double型的数据占8个字节。

推箱子小游戏教程

编写环境是DevC++,目前我已经测试了DevC++和visual studio两个编译器,代码稍微有差别。在DevC++上面运行正常,在VS中肯定能运行正常。但是在VS中能够运行正常的代码,在DevC++中不能运行正常。这个主要是因为VS编译器中可以省略stdlib.h头文件造成的。当我们调用system()函数的时候就会用到stdlib.h头文件中的函数声明。下面做详细解读,保证计算机小白都能够看懂。
—————————————————————————————
我们按照编写的顺序进行逐步编写并调试。

  • 基本操作先写好
#include <stdio.h>//编译预处理
int main()//主函数类型int型,无参数void省略
{
	return 0;//函数返回值0
}
  • 定义一个全局变量的二维数组。全局变量在本文所在专栏的上一篇文章中已经写过了全局变量可以保持各函数之间的联系,全局变量在任何子函数中都可以使用,全局变量定义在所有的函数之外
    这个二维数组也是有讲究的,里面的数字代表不同的含义。数字在矩阵中的位置也是有讲究的,根据你自己设计的地图来安排不同的数字的位置。我这里1代表围墙,0代表空地,3代表目的地,4代表箱子,5代表小人。
    这个数字也可以自己随便改,只要能够参与运算,复合逻辑即可。
#include <stdio.h>//编译预处理
int map[13][13] =
{
	{1,1,1,1,1,1,1,1,1,1,1,1,1},//里面的数字分别代表不同的地图元素
	{1,0,3,0,0,0,0,0,3,1,0,0,1},//数字1代表围墙,打印时用实心方块代表围墙
	{1,0,0,0,0,4,0,0,0,1,0,0,1},//数字零代表空地,用两个空格代表空地
	{1,1,1,1,1,1,1,0,0,1,0,0,1},//数字3代表目的地,用实心五角星代表目的地
	{1,0,1,0,5,0,0,0,0,0,0,0,1},//数字4代表箱子
	{1,0,4,0,0,0,0,0,0,1,1,1,1},//数字5代表人
	{1,0,0,0,0,0,0,1,0,1,0,0,1},
	{1,0,0,0,0,1,3,1,0,1,0,0,1},
	{1,0,0,0,0,0,4,0,0,1,0,0,1},
	{1,1,0,4,1,1,1,1,1,1,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,3,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1},
};
int main()//主函数类型int型,无参数void省略
{
	return 0;//函数返回值0
}
  • 然后用子函数来打印这个二维数组
    我给子函数命名为DrawMap,并且将它在主函数前面声明一下。把DrawMap()子函数写在主函数下面,并在主函数里面调用,调试运行一下看看结果有没有问题。
#include <stdio.h>//编译预处理
int map[13][13] =
{
	{1,1,1,1,1,1,1,1,1,1,1,1,1},//里面的数字分别代表不同的地图元素
	{1,0,3,0,0,0,0,0,3,1,0,0,1},//数字1代表围墙,打印时用实心方块代表围墙
	{1,0,0,0,0,4,0,0,0,1,0,0,1},//数字零代表空地,用两个空格代表空地
	{1,1,1,1,1,1,1,0,0,1,0,0,1},//数字3代表目的地,用实心五角星代表目的地
	{1,0,1,0,5,0,0,0,0,0,0,0,1},//数字4代表箱子
	{1,0,4,0,0,0,0,0,0,1,1,1,1},//数字5代表人
	{1,0,0,0,0,0,0,1,0,1,0,0,1},
	{1,0,0,0,0,1,3,1,0,1,0,0,1},
	{1,0,0,0,0,0,4,0,0,1,0,0,1},
	{1,1,0,4,1,1,1,1,1,1,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,3,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1},
};
void DrawMap();//函数类型为void空型,因为只是打印,没有返回值。参数也是void
//有关子函数上一篇博客中已经提到过
int main()//主函数类型int型,无参数void省略
{
	DrawMap();//在主函数里面调用一下子函数,打印看看结果有没有错误
	return 0;//函数返回值0
}
void DrawMap()
{
	for (int i = 0; i < 13; i++)
	{
		for (int j = 0; j < 13; j++)//两个for循环遍历循环二维数组
		{
			switch (map[i][j])	//对二维数组中的数进行switch
			{
			case 0:	//当数组中的数为0时,打印两个空格
				printf("  ");
				break;
			case 1:	//当数组中的数为1时,打印实心方块
				printf("■");
				break;
			case 3:	//当数组中的数为3时,打印空心五角星作为目的地
				printf("☆");
				break;
			case 4:	//当数组中的数为4时,打印空心方块作为箱子
				printf("□");
				break;
			case 5:	//当数组中的数为5时,打印小人
				printf("♀");
				break;
			case 7:	//当数组中的数为7时,即4+3,箱子在目的地上,打印实心五角星
				printf("★");
				break;
			case 8:	//当数组中的数为8时,即人在目的地上,打印小人
				printf("♀");
				break;
			}
		}
		putchar('\n');//打印一行输出一个回车换行,里面的for循环打印的是行,打完一行要回车,否则达不到预想的效果 
	}
}

在这里插入图片描述
调试后结果能打印出来,地图没有问题。那就继续编写下一步。

  • 接着我们来改变一下地图的颜色,使界面更加美观,在主函数中调用子函数的前面加上system(“color 25”); 注意不要忘了加上stdlib.h头文件,因为system()函数的声明是在该头文件中的。如果是VS编译器就可以省去stdlib.h头文件,system()函数依然可以用。
#include <stdio.h>
#include <stdlib.h>
int map[13][13] =
{
	{1,1,1,1,1,1,1,1,1,1,1,1,1},//里面的数字分别代表不同的地图元素
	{1,0,3,0,0,0,0,0,3,1,0,0,1},//数字1代表围墙,打印时用实心方块代表围墙
	{1,0,0,0,0,4,0,0,0,1,0,0,1},//数字零代表空地,用两个空格代表空地
	{1,1,1,1,1,1,1,0,0,1,0,0,1},//数字3代表目的地,用实心五角星代表目的地
	{1,0,1,0,5,0,0,0,0,0,0,0,1},//数字4代表箱子
	{1,0,4,0,0,0,0,0,0,1,1,1,1},//数字5代表人
	{1,0,0,0,0,0,0,1,0,1,0,0,1},
	{1,0,0,0,0,1,3,1,0,1,0,0,1},
	{1,0,0,0,0,0,4,0,0,1,0,0,1},
	{1,1,0,4,1,1,1,1,1,1,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,3,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1},
};
void DrawMap();//函数类型为void空型,因为只是打印,没有返回值。参数也是void
//有关子函数上一篇博客中已经提到过
int main()//主函数类型int型,无参数void省略
{
	system("color 25");
	DrawMap();//在主函数里面调用一下子函数,打印看看结果有没有错误
	return 0;//函数返回值0
}
void DrawMap()
{
	for (int i = 0; i < 13; i++)
	{
		for (int j = 0; j < 13; j++)//两个for循环遍历循环二维数组
		{
			switch (map[i][j])	//对二维数组中的数进行switch
			{
			case 0:	//当数组中的数为0时,打印两个空格
				printf("  ");
				break;
			case 1:	//当数组中的数为1时,打印实心方块
				printf("■");
				break;
			case 3:	//当数组中的数为3时,打印空心五角星作为目的地
				printf("☆");
				break;
			case 4:	//当数组中的数为4时,打印空心方块作为箱子
				printf("□");
				break;
			case 5:	//当数组中的数为5时,打印小人
				printf("♀");
				break;
			case 7:	//当数组中的数为7时,即4+3,箱子在目的地上,打印实心五角星
				printf("★");
				break;
			case 8:	//当数组中的数为8时,即人在目的地上,打印小人
				printf("♀");
				break;
			}
		}
		putchar('\n');//打印一行输出一个回车换行,里面的for循环打印的是行,打完一行要回车,否则达不到预想的效果 
	}
}

在这里插入图片描述
运行调试后,颜色正确。没毛病
system(“color 25”); 2代表的是绿色,5代表的是深紫色。color后面跟两个十六进制的数字,第一个数字的颜色是背景色,第二个数字是前景色(即字体颜色)
0代表黑色
1代表深蓝色
2代表绿色
3代表湖蓝色
4代表深红色
5代表深紫色
6代表黄色
7代表白色
8代表灰色
9代表淡蓝色
a代表淡绿色
b代表浅蓝色
c代表浅红色
d代表淡紫色
e代表淡黄色
f代表亮白色
可以根据自己的喜欢来配置颜色

  • 然后来写玩游戏的子函数
    还是要在主函数前面声明一下,我给这个子函数命名为PlayGame,代码过多,请看详细注释。
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>//不要忘记加入这个头文件,下面会用到其中的函数声明

int map[13][13] =
{
	{1,1,1,1,1,1,1,1,1,1,1,1,1},
	{1,0,3,0,0,0,0,0,3,1,0,0,1},
	{1,0,0,0,0,4,0,0,0,1,0,0,1},
	{1,1,1,1,1,1,1,0,0,1,0,0,1},
	{1,0,1,0,5,0,0,0,0,0,0,0,1},
	{1,0,4,0,0,0,0,0,0,1,1,1,1},
	{1,0,0,0,0,0,0,1,0,1,0,0,1},
	{1,0,0,0,0,1,3,1,0,1,0,0,1},
	{1,0,0,0,0,0,4,0,0,1,0,0,1},
	{1,1,0,4,1,1,1,1,1,1,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,3,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1},
};

void DrawMap();
void PlayGame();

int main()
{
	system("color 25");
	PlayGame();//画地图的子函数放到玩游戏的子函数中调用,我们只需要在主函数中调用PlayGame即可
	return 0;
}
void DrawMap()	//因为二维数组是全局变量,所以画地图的子函数可以不在主函数中调用,只需要在PlayGame子函数中调用即可 
{
	for (int i = 0; i < 13; i++)
	{
		for (int j = 0; j < 13; j++)
		{
			switch (map[i][j])
			{
			case 0:
				printf("  ");
				break;
			case 1:
				printf("■");
				break;
			case 3:
				printf("☆");
				break;
			case 4:
				printf("□");
				break;
			case 5:
				printf("♀");
				break;
			case 7:
				printf("★");
				break;
			case 8:
				printf("♀");
				break;
			}
		}
		putchar('\n');
	}
}
void PlayGame()	//子函数,玩游戏
{
	char input;	//定义一个从键盘输入的字符
	int i, j;	//定义循环变量i和j
	int peoR, peoC;	//定义人的位置

	while (1)//条件判断永远为真,一直循环直到遇到break语句跳出循环
	{
		system("cls");//清屏,把上一次打印的结果全部清除,该函数在此编译器中需要stdlib.h头文件
		DrawMap();	//调用画地图子函数
		for (i = 0; i < 13; i++)	
		{
			for (j = 0; j < 13; j++)	//两个for循环遍历循环找出人的位置
			{
				if (map[i][j] == 5 || map[i][j] == 8)	//如果是5和8,那就是人的位置
				{
					peoR = i;	//把i赋给peoR
					peoC = j;	//把j赋给peoC
				}
			}
		}
		
		input = _getch();	//用_getch()函数从键盘输入字符,_getch()函数包含在conio.h头文件中,函数用途:从控制台读取一个字符,但不显示在屏幕上
		/*
		getch与getchar基本功能相同,差别是getch直接从键盘获取键值,不等待用户按回车,只要用户按一个键,
		getch就立刻返回, getch返回值是用户输入的字符对应的ASCII码,出错返回-1.输入的字符不会回显在屏幕上.getch函数常用于
		程序调试中,在调试时,在关键位置显示有关的结果以待查看,然后用getch函数暂停程序运行,当按任意键后程序继续运行.
		*/
		switch (input)	//对输入的字符进行switch
		{
		case'W':
		case'w':
		case 72:	//如果是大小写字母或者箭头上键,就往上走,箭头上键的键值为72
			if (map[peoR - 1][peoC] == 0 || map[peoR - 1][peoC] == 3)//如果人的前里面是空地或者是目的地可走
			{
				map[peoR - 1][peoC] += 5;//将人的上面的数加5,如果是0空地,加5后就变成了人;如果是目的地,加5后变成8,人在目的地上
				map[peoR][peoC] -= 5;//将人的位置减5,如果人在空地上,减5后变成0空地;如果人在目的地上,8减5后变成3目的地
			}
			else if (map[peoR - 1][peoC] == 4 || map[peoR - 1][peoC] == 7)//如果人的前面是4箱子或7箱子在目的地上
			{
				if (map[peoR - 2][peoC] == 0 || map[peoR - 2][peoC] == 3)//如果人前面的前面是0空地或3目的地,说明可以走
				{
					map[peoR - 2][peoC] += 4;//将人前面的前面加4
					map[peoR - 1][peoC] += 1;//将人前面加1
					map[peoR][peoC] -= 5;//将人原来的位置减5
				}
			}

			break;

		case'S':	//同往上走的情况,下面的以此类推
		case's':
		case 80:
			if (map[peoR + 1][peoC] == 0 || map[peoR + 1][peoC] == 3)
			{
				map[peoR + 1][peoC] += 5;
				map[peoR][peoC] -= 5;
			}
			else if (map[peoR + 1][peoC] == 4 || map[peoR + 1][peoC] == 7)
			{
				if (map[peoR + 2][peoC] == 0 || map[peoR + 2][peoC] == 3)
				{
					map[peoR + 2][peoC] += 4;
					map[peoR + 1][peoC] += 1;
					map[peoR][peoC] -= 5;
				}
			}
			break;

		case'A':
		case'a':
		case 75:
			if (map[peoR][peoC - 1] == 0 || map[peoR][peoC - 1] == 3)
			{
				map[peoR][peoC - 1] += 5;
				map[peoR][peoC] -= 5;
			}
			else if (map[peoR][peoC - 1] == 4 || map[peoR][peoC - 1] == 7)
			{
				if (map[peoR][peoC - 2] == 0 || map[peoR][peoC - 2] == 3)
				{
					map[peoR][peoC - 2] += 4;
					map[peoR][peoC - 1] += 1;
					map[peoR][peoC] -= 5;
				}
			}
			break;

		case'D':
		case'd':
		case 77:
			if (map[peoR][peoC + 1] == 0 || map[peoR][peoC + 1] == 3)
			{
				map[peoR][peoC + 1] += 5;
				map[peoR][peoC] -= 5;
			}
			else if (map[peoR][peoC + 1] == 4 || map[peoR][peoC + 1] == 7)
			{
				if (map[peoR][peoC + 2] == 0 || map[peoR][peoC + 2] == 3)
				{
					map[peoR][peoC + 2] += 4;
					map[peoR][peoC + 1] += 1;
					map[peoR][peoC] -= 5;
				}
			}
			break;
		}
	}
}
  • 最后判断是否完成游戏,即所有的箱子都已经在目的地上了,箱子我用4代替的,也就是地图上再也找不到一个数字4。那么每走完一步我都要查找一下数字4,用count来记录查找到数字4的次数。当count的值为0的时候,表明再也没有箱子不在目的地上了。
#include <stdio.h>
#include <conio.h>	//下面会用到该头文件中的_getch()函数的声明 
#include <stdlib.h> //下面会用到该头文中清屏函数和改变界面颜色函数的声明 

int map[13][13] =
{
	{1,1,1,1,1,1,1,1,1,1,1,1,1},//里面的数字分别代表不同的地图元素
	{1,0,3,0,0,0,0,0,3,1,0,0,1},//数字1代表围墙,打印时用实心方块代表围墙
	{1,0,0,0,0,4,0,0,0,1,0,0,1},//数字零代表空地,用两个空格代表空地
	{1,1,1,1,1,1,1,0,0,1,0,0,1},//数字3代表目的地,用实心五角星代表目的地
	{1,0,1,0,5,0,0,0,0,0,0,0,1},//数字4代表箱子
	{1,0,4,0,0,0,0,0,0,1,1,1,1},//数字5代表人
	{1,0,0,0,0,0,0,1,0,1,0,0,1},
	{1,0,0,0,0,1,3,1,0,1,0,0,1},
	{1,0,0,0,0,0,4,0,0,1,0,0,1},
	{1,1,0,4,1,1,1,1,1,1,0,0,1},
	{1,0,0,0,0,0,0,0,0,0,0,0,1},
	{1,0,0,0,0,0,3,0,0,0,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1,1},
};	//全局变量的二维数组定义并初始化

void DrawMap();	//定义一个子函数,用来画地图
void PlayGame();	//定义一个子函数,用来玩游戏

int main()	//主函数
{
	system("color 25");//第一个为前景色,第二个为背景色 
	PlayGame();//调用玩游戏函数 
	return 0;
}

void DrawMap()	//因为二维数组是全局变量,所以画地图的子函数可以不在主函数中调用,只需要在PlayGame子函数中调用即可 
{
	for (int i = 0; i < 13; i++)
	{
		for (int j = 0; j < 13; j++)//两个for循环遍历循环二维数组
		{
			switch (map[i][j])	//对二维数组中的数进行switch
			{
			case 0:	//当数组中的数为0时,打印两个空格
				printf("  ");
				break;
			case 1:	//当数组中的数为1时,打印实心方块
				printf("■");
				break;
			case 3:	//当数组中的数为3时,打印空心五角星作为目的地
				printf("☆");
				break;
			case 4:	//当数组中的数为4时,打印空心方块作为箱子
				printf("□");
				break;
			case 5:	//当数组中的数为5时,打印小人
				printf("♀");
				break;
			case 7:	//当数组中的数为7时,即4+3,箱子在目的地上,打印实心五角星
				printf("★");
				break;
			case 8:	//当数组中的数为8时,即人在目的地上,打印小人
				printf("♀");
				break;
			}
		}
		putchar('\n');//打印一行输出一个回车换行,里面的for循环打印的是行,打完一行要回车,否则达不到预想的效果 
	}

}
void PlayGame()	//子函数,玩游戏
{
	char input;	//定义一个从键盘输入的字符
	int i, j, count;	//定义循环变量i和j
	int peoR, peoC;	//定义人的位置

	while (1)//条件判断永远为真,一直循环直到遇到break语句跳出循环
	{
		system("cls");//清屏,把上一次打印的结果全部清除,该函数在此编译器中需要stdlib.h头文件
		DrawMap();	//调用画地图子函数
		for (i = 0; i < 13; i++)	
		{
			for (j = 0; j < 13; j++)	//两个for循环遍历循环找出人的位置
			{
				if (map[i][j] == 5 || map[i][j] == 8)	//如果是5和8,那就是人的位置
				{
					peoR = i;	//把i赋给peoR
					peoC = j;	//把j赋给peoC
				}
			}
		}
		
		input = _getch();	//用_getch()函数从键盘输入字符,_getch()函数包含在conio.h头文件中,函数用途:从控制台读取一个字符,但不显示在屏幕上
		/*
		getch与getchar基本功能相同,差别是getch直接从键盘获取键值,不等待用户按回车,只要用户按一个键,
		getch就立刻返回, getch返回值是用户输入的字符对应的ASCII码,出错返回-1.输入的字符不会回显在屏幕上.getch函数常用于
		程序调试中,在调试时,在关键位置显示有关的结果以待查看,然后用getch函数暂停程序运行,当按任意键后程序继续运行.
		*/
		switch (input)	//对输入的字符进行switch
		{
		case'W':
		case'w':
		case 72:	//如果是大小写字母或者箭头上键,就往上走,箭头上键的键值为72
			if (map[peoR - 1][peoC] == 0 || map[peoR - 1][peoC] == 3)//如果人的前里面是空地或者是目的地可走
			{
				map[peoR - 1][peoC] += 5;//将人的上面的数加5,如果是0空地,加5后就变成了人;如果是目的地,加5后变成8,人在目的地上
				map[peoR][peoC] -= 5;//将人的位置减5,如果人在空地上,减5后变成0空地;如果人在目的地上,8减5后变成3目的地
			}
			else if (map[peoR - 1][peoC] == 4 || map[peoR - 1][peoC] == 7)//如果人的前面是4箱子或7箱子在目的地上
			{
				if (map[peoR - 2][peoC] == 0 || map[peoR - 2][peoC] == 3)//如果人前面的前面是0空地或3目的地,说明可以走
				{
					map[peoR - 2][peoC] += 4;//将人前面的前面加4
					map[peoR - 1][peoC] += 1;//将人前面加1
					map[peoR][peoC] -= 5;//将人原来的位置减5
				}
			}

			break;

		case'S':	//同往上走的情况,下面的以此类推
		case's':
		case 80:
			if (map[peoR + 1][peoC] == 0 || map[peoR + 1][peoC] == 3)
			{
				map[peoR + 1][peoC] += 5;
				map[peoR][peoC] -= 5;
			}
			else if (map[peoR + 1][peoC] == 4 || map[peoR + 1][peoC] == 7)
			{
				if (map[peoR + 2][peoC] == 0 || map[peoR + 2][peoC] == 3)
				{
					map[peoR + 2][peoC] += 4;
					map[peoR + 1][peoC] += 1;
					map[peoR][peoC] -= 5;
				}
			}
			break;

		case'A':
		case'a':
		case 75:
			if (map[peoR][peoC - 1] == 0 || map[peoR][peoC - 1] == 3)
			{
				map[peoR][peoC - 1] += 5;
				map[peoR][peoC] -= 5;
			}
			else if (map[peoR][peoC - 1] == 4 || map[peoR][peoC - 1] == 7)
			{
				if (map[peoR][peoC - 2] == 0 || map[peoR][peoC - 2] == 3)
				{
					map[peoR][peoC - 2] += 4;
					map[peoR][peoC - 1] += 1;
					map[peoR][peoC] -= 5;
				}
			}
			break;

		case'D':
		case'd':
		case 77:
			if (map[peoR][peoC + 1] == 0 || map[peoR][peoC + 1] == 3)
			{
				map[peoR][peoC + 1] += 5;
				map[peoR][peoC] -= 5;
			}
			else if (map[peoR][peoC + 1] == 4 || map[peoR][peoC + 1] == 7)
			{
				if (map[peoR][peoC + 2] == 0 || map[peoR][peoC + 2] == 3)
				{
					map[peoR][peoC + 2] += 4;
					map[peoR][peoC + 1] += 1;
					map[peoR][peoC] -= 5;
				}
			}
			break;
		}
		count=0;	//给count赋值为零,在这个while循环中,每走一步我都要查找一次数字4,即箱子 
		for(i=0;i<13;i++)
		{
			for(j=0;j<13;j++)
			{
				if(map[i][j]==4)//利用两个for循环来查找箱子,如果有箱子就给count加一 
					count++;
			}
		}
		if(count==0)//每走完一步查找箱子,当我再也查找不到数字4的时候,表明所有的箱子已经在目的地上4+3=7 
			break;//所有的箱子都在目的地上时,用break跳出这个while(1)循环 
	}
	system("cls");//跳出后,将上一次打印的结果全部清除 
	printf("You win!");//输出你赢了 
}

下一篇文章

C语言字符数组与字符串+十六进制转十进制+字母和汉字的存储差别+bool型变量+反斜杠代码换行

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jackey_Song_Odd

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

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

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

打赏作者

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

抵扣说明:

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

余额充值