每天更新代软件功能,每天有做不完的事情,所以,今天,我做了一个飞机大战的游戏来放松一下 。
一、文件框架
首先,我们先打开VS,我用的是 , 推荐这个版本,功能足够我们打代码了 , 而且BUG很少 。
然后,我们创建一个控制台项目,就可以做一个飞机大战的游戏了 。
我们打开了之后 , 我们就可以开始制作飞机大战的游戏了 。
首先,我们先创建几个文件,如下图所示:
然后,我们就可以开始敲代码了!?
二、函数
想要实现像市面上的那种飞机大战的效果 , 这个是C++几乎无法实现的 。(如果你能实现的话,博主希望你能发在评论区 , 被博主采纳的有红包哦~)
C++是一种多范式编程语言,它包含了过程化、面向对象、泛型等不同的编程范式 。它是一种高效的编程语言 , 可以编写性能很高的程序,同时也具有良好的可移植性 。C++的主要概念和用途如下:
类和对象:C++中的核心概念是类和对象,可以用类来描述具有相同属性和行为的一组对象 。它是面向对象编程的基础 。
继承和多态:C++支持继承 , 可以从已有类派生出新类,并且可以重写父类的方法实现多态性 。
泛型编程:C++支持模板,可以实现泛型编程和函数重载 。
异常处理机制:C++提供了一种处理程序运行时错误的机制 , 可以使用try、catch和throw关键字来实现异常处理 。
标准库:C++有一个丰富的标准库,包含了大量的函数和类,可以帮助程序员更容易地实现一些常见的任务,如输入输出、字符串处理、容器和算法等 。
C++的应用范围很广,包括系统编程、游戏开发、图形界面设计、数据库应用、网络编程、嵌入式开发等 。由于C++是一种高效的语言,适合编写对性能要求很高的应用程序 。同时,C++的可移植性也很好,可以在不同的平台上编译和运行 。
但是,C++更偏向于计算方面,如果要进行美观的图形化界面的设置,那建议你使用或者是来进行实现,因为它们能更方便 。
话不多说,我们来解决函数困难 。
游戏界面
game.h:
#pragma once#include
注释都在上边了,看不懂的可以私聊博主 。
然后,我们来做游戏的另一个函数,也就是敌机和战机?的外观和移动函数,这个函数是游戏的核心,没有了这个函数,那这个游戏就像一个图片,只能呈现出游戏的界面,而不能做出移动的效果,会让人第一个感觉就是一个字——丑 。
战机和敌机的外观及其移动方式
首先,我们先打开一个黑框(可以用CMD代替):
文章插图
我们需要让敌机从上往下移动,而战机是需要玩家自己上下左右进行控制的 。然后,我们先定义战机的外观:
// 这个函数体类的代码其实就是为了初始化战机的十个部分的位置 , 战机的组成如下所示://|5 //|9//*****12034//***678//第一排5个0的坐标依次对应了position[1]position[2]position[0]position[3]position[4]//第二排三个0的坐标依次对应了position[6]position[7]position[8]//两排0上面的两|的坐标从上往下依次对应了position[5]position[9]}void Game::drawPlane(){for (int i = 0; i < 9; i++){SetPos(position[i].X, position[i].Y);if (i != 5){cout << "*";}else if (i == 5){cout << "|";}}}
战机的样子在上面的代码块注释里面应该能看到,我们用和for循环语句进行战机的输入与输出 。
然后,我们来绘制敌机的样子:
// 接下来要根据敌机的左上角坐标和右下角坐标画出敌机,// 显然,敌机的外形如下所示://--//||//--void Game::drawEnemy(){for (int i = 0; i < 8; i++){drawFrame(enemy[i].position[0], enemy[i].position[1], '-', '|');}}
敌机的代码和样子都在上面了,我们用“-”和“|”来绘制敌机 。
其他的移动代码和子弹等角色的代码有“亿”点复杂,如果我在这里讲的话 , 人家CSDN要打电话给我了 。(因为如果写的话,这篇博文估计要5万多字)
现在供上角色的代码(game.cpp):
#include"game.h"#include#includeGame::Game(){// 调用类成员函数来进行初始化initPlane();initBullet();initEnemy();// 初始化四个int型数据成员,采用赋值的方式进行初始化// string类型的数据成员title没有进行初始化,因为:// string本身就是一个标准库类类型,它的类定义中设置了默认构造函数 , // 这些默认构造函数会将对象初始化为合理的默认状态,//string的默认构造函数会产生空字符串,相当于""。this->score = 0;rank = 25;rankf = 25;flag_rank = 0;}void Game::initPlane(){COORD centren;centren.X = 39;centren.Y = 22;position[0].X = position[5].X = position[7].X = position[9].X = centren.X;position[1].X = centren.X - 2;position[2].X = position[6].X = centren.X - 1;position[3].X = position[8].X = centren.X + 1;position[4].X = centren.X + 2;for (int i = 0; i <= 4; i++){position[i].Y = centren.Y;}for (int i = 6; i <= 8; i++){position[i].Y = centren.Y + 1;}position[5].Y = centren.Y - 1;position[9].Y = centren.Y - 2;// 这个函数体类的代码其实就是为了初始化战机的十个部分的位置,战机的组成如下所示://|5 //|9//*****12034//***678//第一排5个0的坐标依次对应了position[1]position[2]position[0]position[3]position[4]//第二排三个0的坐标依次对应了position[6]position[7]position[8]//两排0上面的两|的坐标从上往下依次对应了position[5]position[9]}void Game::drawPlane(){for (int i = 0; i < 9; i++){SetPos(position[i].X,position[i].Y);if (i != 5){cout << "*";}else if (i == 5){cout << "|";}}}// 这个成员函数通过将战机的每个坐标处输出" "来代替"0"和"|",// 来达到将战机消除的目的 。void Game::drawPlaneToNull(){for (int i = 0; i < 9; i++){SetPos(position[i].X, position[i].Y);cout << " ";}}// 该成员函数用来初始化子弹 , // 即将每个子弹的Y坐标初始化为30(bullet[i].Y = 30)来表示子弹处于失效状态void Game::initBullet(){for (int i = 0; i < 10; i++){bullet[i].Y = 30;} }// 该成员函数用来画出子弹// 首先检查每颗子弹的有效性,如果子弹有效 , 则定位到该子弹的坐标处,输出 "^",表示该子弹 , // 如果子弹是无效的,则不绘制void Game::drawBullet(){for (int i = 0; i < 10; i++){if (bullet[i].Y != 30){SetPos(bullet[i].X,bullet[i].Y);cout << "^";}}}//子弹失效void Game::drawBulletToNull(){for (int i = 0; i < 10; i++)if (bullet[i].Y != 30){SetPos(bullet[i].X, bullet[i].Y + 1);cout << " ";}}// 这个函数用来初始敌机的位置 , // 屏幕当中只能同时存在八架敌机 , // 且每架敌机用如下结构体Frame来表示 , 如下所示://typedef struct Frame//{//COORD position[2];//int flag;//}Frame;COORD random(COORD a, COORD b){int x = rand() % (a.X - b.X) + a.X;int y = rand() % (a.Y - b.Y) + a.Y;COORD c = { x,y };return c;}void Game::initEnemy(){COORD a = { 1, 1 };COORD b = { 45, 15 };for (int i = 0; i < 8; i++){enemy[i].position[0] = random(a, b);//random(a, b)是调用了一个重载的函数,它表示在坐标a、b之间的矩形框//内随机生成一个坐标值,并将该坐标值作为敌机的左上角的坐标 。// enemy[i].position[0]中是一个Frame结构体类型的变量,存放了敌机i的左上角的坐标 。enemy[i].position[1].X = enemy[i].position[0].X + 3;enemy[i].position[1].Y = enemy[i].position[0].Y + 2;// enemy[i].position[1]也中是一个Frame结构体类型的变量,存放了敌机i的右下角的坐标 。}}// 接下来要根据敌机的左上角坐标和右下角坐标画出敌机,// 显然,敌机的外形如下所示://--//||//--void Game::drawEnemy(){for (int i = 0; i < 8; i++){drawFrame(enemy[i].position[0], enemy[i].position[1], '-', '|');} }// 将敌机消除,通过输出空白的方式void Game::drawEnemyToNull(){for (int i = 0; i < 8; i++){drawFrame(enemy[i].position[0], enemy[i].position[1], ' ', ' ');}}//隐藏光标void HideCursor(){CONSOLE_CURSOR_INFO cursor_info = { 1,0 };//第二个值0表示隐藏光标SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);}void SetPos(int i, int j)//设置坐标点位(光标){HANDLE hout;COORD coord;coord.X = i;coord.Y = j;hout = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(hout, coord);}//左上角坐标、右下角坐标、用row填充行、用col填充列void drawFrame(COORD a, COORDb, char row, char col){drawRow(a.Y, a.X + 1, b.X - 1, row);drawRow(b.Y, a.X + 1, b.X - 1, row);drawCol(a.X, a.Y + 1, b.Y - 1, col);drawCol(b.X, a.Y + 1, b.Y - 1, col);}//把第y行,[x1, x2) 之间的坐标填充为 chvoid drawRow(int y, int x1, int x2, char ch){SetPos(x1, y);for (int i = 0; i <= (x2 - x1); i++){cout << ch;}}//把第x列,[y1, y2] 之间的坐标填充为 chvoid drawCol(int x, int y1, int y2, char ch){int y = y1;while (y != y2 + 1){SetPos(x, y);cout << ch;y++;}}//主菜单绘制int drawMenu(){SetPos(30,1);cout << "飞 机 大 战";drawRow(3, 0, 79, '-');drawRow(5, 0, 79, '-');SetPos(28, 4);cout << "w 和 s选择,k确定";int j = 11;SetPos(12, j);cout << ">>";SetPos(15, 11);cout << "1. 简单的敌人";SetPos(15, 13);cout << "2. 冷酷的敌人";drawRow(20, 0, 79, '-');SetPos(47, 11);cout << "简单的敌人:";SetPos(47, 13);cout << "简单敌人有着较慢的移动速度 。";SetPos(30, 21);cout << "copy right@风兮木萧";drawRow(22, 0, 79, '-');while (true){if (_kbhit()){char x = _getch();switch (x){case'w':{if (j == 13){SetPos(12, j);cout << "";j = 11;SetPos(12, j);cout << ">>";SetPos(51, 13);cout << "";SetPos(47, 11);cout << "简单的敌人:";SetPos(51, 13);cout << "简单敌人有着较慢的移动速度 , 比较容易对付";}break;}case's':{if (j == 11){SetPos(12, j);cout << " ";j = 13;SetPos(12, j);cout << ">>";SetPos(51, 13);cout << "";SetPos(47, 11);cout << "冷酷的敌人:";SetPos(51, 13);cout << "冷酷的敌人移动速度较快,难对付哟 。";}break;}case 'k':{if (j == 11)//源代码为8?return 1;elsereturn 2;}}}}return 0;}void drawFrame(int x1, int y1, int x2, int y2, char row, char col){COORD a = { x1, y1 };COORD b = { x2, y2 };drawFrame(a, b, row, col);}// 绘制游戏界面void drawPlaying(){drawFrame(0, 0, 48, 24, '=', '|');// draw map frame主界面drawFrame(49, 0, 79, 4, '-', '|');// draw output frame 状态界面drawFrame(49, 4, 79, 9, '-', '|');// draw score frame 分数界面drawFrame(49, 9, 79, 20, '-', '|');// draw operate frame 操作界面drawFrame(49, 20, 79, 24, '-', '|');// draw other message frame 提示界面SetPos(52, 6);cout << "得分:";SetPos(52, 7);cout << "称号:";SetPos(52, 11);cout << "操作方式:";SetPos(52, 13);cout << "a,s,d,w:控制战机移动 。";SetPos(52, 15);cout << "p:暂停游戏 。";SetPos(52, 17);cout << "e:退出游戏 。";SetPos(52, 22);cout << " 游戏虽好玩,不要贪多哦 ";}// 该成员函数用过响应战机的一个动作// a,s,w,d,来控制战机的移动void Game::planeMove(char x){if (x == 'a'){if (position[1].X != 1){for (int i = 0; i <= 9; i++){position[i].X -= 2;}}}// 如果玩家按下 'a' 键,说明玩家想让战机往左移动一个距离(2个单位),// 首先检测,战机的最左侧的位置坐标(即position[1].X)有没有达到左边界 , // 如果到达了边界 , 那就不做出移动;如果没有达到边界,则将战机10个部分的X值减小2 。if (x == 's'){if (position[7].Y != 23){for (int i = 0; i <= 9; i++){position[i].Y += 1;}}}// 如果玩家按下 's' 键,说明玩家想让战机往下移动一个距离(1个单位),// 首先检测,战机的最底部的位置坐标(即position[6].Y或者position[7].Y或者position[8].Y)有没有达到下边界,//如果到达了边界 , 那就不做出移动;如果没有达到边界,则将战机10个部分的Y值增加1 。if (x == 'd' && (position[4].X != 47)){for (int i = 0; i <= 9; i++){position[i].X += 2;} }// 如果玩家按下 'd' 键,说明玩家想让战机往右移动一个距离(2个单位),// 首先检测,战机的最右侧的位置坐标(即position[4].X)有没有达到右边界,//如果到达了边界 , 那就不做出移动;如果没有达到边界,则将战机10个部分的X值增加2 。if (x == 'w'&&(position[5].Y != 3)){for (int i = 0; i <= 9; i++){position[i].Y -= 1;}}// 如果玩家按下'w'键,说明玩家想让战机往上移动一个距离(1个单位),// 首先检测,战机的最顶部的位置坐标(即position[5].Y)有没有达到上边界 , //如果到达了边界,那就不做出移动;如果没有达到边界,则将战机10个部分的Y值减少1 。}// 该函数用来判断战机的某一部分是否与敌机有接触// 如果与敌机有接触在判断为坠毁booljudgeCoordInFrame(Frame frame, COORD spot){if ((spot.X >= frame.position[0].X) && (spot.X <= frame.position[1].X) && (spot.Y >= frame.position[0].Y) && (spot.Y <= frame.position[1].Y)){return true;}return false;}void drawFrame(Frame frame, char row, char col){COORD a = frame.position[0];COORD b = frame.position[1];drawFrame(a, b, row, col);}//游戏结束void Game::GameOver(){system("cls");COORD p1 = { 28,9 };COORD p2 = { 53,15 };drawFrame(p1, p2, '=', '|');SetPos(36, 12);string str = "Game Over!";for (int i = 0; i < str.size(); i++){Sleep(80);cout << str[i];}Sleep(1000);system("cls");drawFrame(p1, p2, '=', '|');SetPos(31, 11);cout << "击落敌机:" << score / 5 << " 架";SetPos(31, 12);cout << "得分:" << score;SetPos(31, 13);cout << "获得称号:" << title;SetPos(30, 18);Sleep(1000);cout << "继续? 是(y)| 否(n)";as://goto 语句标签 直接跳转至此char x = _getch();if (x == 'n'){exit(0);}else if (x == 'y'){system("cls");Game game;int a = drawMenu();// 绘制游戏开始界面主菜单if (a == 2)game.rank = 20;system("cls");drawPlaying();// 绘制游戏界面框架game.Playing();}else goto as;}// 该成员函数用来判断战机是否坠毁,// 依次判断每架敌机与战机的每个部分是否有接触,// 如果有接触,则表示战机坠毁void Game::judgePlane(){for (int i = 0; i < 8; i++){for (int j = 0; j < 9; j++)// 此处的实参position[j]是指战机的10个部分的COORD坐标,// 类中的成员函数可以访问数据成员变量 。// 此处也可以写成this-> position[j],因为// 成员函数具有一个附加的隐含形参 , 即指向该类对象的一个指针,// 这个隐含形参命名为this , 与调用成员函数的对象绑定在一起 。// 成员函数不能定义this形参,而是由编译器隐含地定义 。// 成员函数的函数体可以显式使用this指针,但不是必须这么做 。if (judgeCoordInFrame(enemy[i], position[j])){SetPos(62, 1);cout << "坠毁";drawFrame(enemy[i], '+', '+');// 将与战机相撞的敌机的形状绘制为://++//++//++Sleep(1000);GameOver();break;}}}// 该成员函数用来使得游戏暂停void Game::Pause(){SetPos(61, 2);cout << "";SetPos(61, 2);cout << "暂停中...";// 当出现"暂停中..."的提示以后 , 程序不停的接收按下的按键,// 当按下'p'键以后,说明要退出暂停状态,此时需要清除"暂停中..."的提示// 通过输出空白 ""来将其覆盖 , 达到效果char c = _getch();while (c != 'p'){c = _getch();}SetPos(61, 2);cout << "";}// 这个成员函数用来响应一次射击操作,// 也就是 , 当游戏中的时候 , 玩家按下"k"键,就执行该函数 。// 由于子弹是由COORD bullet[10]定义的,因此同一时刻 , 界面内只能有10颗子弹同时出现 。// 如果界面内不够10颗子弹,按下"k"键后战机应该发射出一颗子弹,// 于是,依次遍历10颗子弹,当遇到第一颗失效的子弹后 , // 立即将该子弹赋予新的坐标(战机的炮口,也就是(position[5].X,position[5].Y - 1)),// 让其激活 。然后退出for循环,函数执行完毕 。void Game::Shoot(){for (int i = 0; i < 10; i++){ if (bullet[i].Y == 30){bullet[i].X = position[5].X;bullet[i].Y = position[5].Y - 1;break;}}}void Game::drawThisBulletToNull(COORD c){SetPos(c.X,c.Y);cout << " ";}// 此成员函数用来响应一次子弹的运动// 每次子弹运动 , 屏幕子弹的坐标都会出现变化,即// 先判断子弹是否有效(即判断语句if (bullet[i].Y != 30)) , // 若子弹有效,将该子弹的Y坐标减少1,X坐标不变 , // 检测子弹坐标更改之后是否达到上边界,如果达到上边界,则将该子弹从屏幕上擦除,// 同时,将该子弹置为失效状态,即 bullet[i].Y = 30 。void Game::bulletMove(){for (int i = 0; i < 10; i++){if (bullet[i].Y != 30){bullet[i].Y -= 1;if (bullet[i].Y == 1){COORD pos = { bullet[i].X, bullet[i].Y + 1 };drawThisBulletToNull(pos);bullet[i].Y = 30;}}}}//击败的敌机清空void Game::drawThisEnemyToNull(Frame f){drawFrame(f, ' ', ' ');}// 该成员函数依次遍历每一架敌机,// 将每一架敌机依次与每一颗子弹进行检测 , // 判断敌机是否与子弹有接触,如果有接触,则表示击中敌机,// 此时将敌机和子弹擦除 , 然后在界面顶部的位置处随机生成一架敌机void Game::judgeEnemy(){for (int i = 0; i < 8; i++){for (int j = 0; j < 10; j++){if (judgeCoordInFrame(enemy[i], bullet[j])){score += 5;drawThisEnemyToNull(enemy[i]);COORD a = { 1, 1 };COORD b = { 45, 3 };enemy[i].position[0] = random(a, b);enemy[i].position[1].X = enemy[i].position[0].X + 3;enemy[i].position[1].Y = enemy[i].position[0].Y + 2;drawThisBulletToNull(bullet[j]);bullet[j].Y = 30;}}}}// 该成员函数用来响应一次敌机的移动// 界面上必须同时出现八架敌机,因此,// 如果有某架敌机运动到下边界处,则重置该敌机的坐标void Game::enemyMove(){for (int i = 0; i < 8; i++){for (int j = 0; j < 2; j++)enemy[i].position[j].Y++;// 我们将每架敌机的左上角和右下角坐标的Y值增加1 , // 表示该敌机向下走了一个距离// 检测向下走一个距离后的敌机的右下角坐标的Y值是否达到24,// 如果达到 , 代表敌机已经运动到下边界了 , // 此时需要随机重置该敌机的坐标if (enemy[i].position[1].Y==24){COORD a = { 1, 1 };COORD b = { 45, 3 };enemy[i].position[0] = random(a, b);enemy[i].position[1].X = enemy[i].position[0].X + 3;enemy[i].position[1].Y = enemy[i].position[0].Y + 2;}}}void Game::printScore(){if (score <= 120){flag_rank = 1;}else if (score > 120 && score <= 360){flag_rank = 2;}else if (score > 360 && score <= 480){flag_rank = 3;}else if (score > 480){flag_rank = 4;}SetPos(60, 6);cout << score;SetPos(60, 7);if (flag_rank == 1){title = "初级飞行员";}else if (flag_rank == 2){title = "中级飞行员";}else if (flag_rank == 3){title = "高级飞行员";}else if (flag_rank == 4){title = "王牌飞行员";}cout << title;}// 这个成员函数是游戏的主循环函数,// 定义了整个游戏过程 。void Game::Playing(){drawEnemy();drawPlane();int flag_bullet = 0;int flag_enemy = 0;while (true){Sleep(20);// 函数名:kbhit()(VC++6.0下为_kbhit())// 功能及返回值: 检查当前是否有键盘输入 , 若有则返回一个非0值,否则返回0// 用法:int kbhit(void);// 包含头文件: include // kbhit()在执行时,检测是否有按键按下,有按下返回非0值,没有按下则返回0,是非阻塞函数;// 不同于getch()的在执行时, 检测按下什么键, 如果不按键该函数不返回 , 也就不进行下一步操作,是阻塞函数 。if (_kbhit()){char x = _getch();// getch()是编程中所用的函数,这个函数是一个不回显函数,// 当用户按下某个字符时,函数自动读取,无需按回车// getch()并非标准C中的函数,不存在C语言中 。// 所在头文件是conio.h,而不是stdio.h 。// 用ch = getch(); 会等待你按下任意键之后,把该键字符所对应的ASCII码赋给ch, 再执行下面的语句 。if ('a' == x || 's' == x || 'd' == x || 'w' == x){drawPlaneToNull();// 将战机先擦除planeMove(x);// 根据所输入的操作符 , 对战机的坐标进行更改drawPlane();// 访问类中的数据成员——战机的坐标,在新的坐标处重新绘制战机judgePlane();// 判断战机是否有坠毁}//在某一循环当中 , 如果检测到有'p'键按下,// 首先在右侧游戏界面输出"暂停中...",然后陷入while()循环一直等待'p'键再次按下,// 如果'p'键没有按下,就一直处在while()循环内,因此不能执行后面的程序,起到暂停的效果 。else if ('p' == x){Pause();}// 如果是检测到'k'键按下 , 则运行Shoot()函数,else if ('k' == x){Shoot();}// 如果是检测到'k'键按下,则运行GameOver()函数,// GameOver()函数执行完毕后 , 执行break;语句跳出while循环(注意不是if (_kbhit())) 。// break语句用于结束最近的while、do while、for或switch语句,并将程序的执行权传递给紧接在// 被终止语句之后的语句 。else if ('e' == x){//CloseHandle(MFUN)GameOver();break;}}// 接下来处理子弹// 判断子弹状态的程序一直在运行if (flag_bullet == 0){bulletMove();// 更新界面上有效子弹的坐标drawBulletToNull();// 将处于旧坐标的子弹擦除drawBullet();// 绘制出新坐标上的子弹judgeEnemy();// 判断敌机是否被子弹击中}flag_bullet++;if (flag_bullet==1){flag_bullet = 0;}//接下来处理敌机if (flag_enemy==0){drawEnemyToNull();// 将所有的敌机都擦除enemyMove();//更新敌机的坐标drawEnemy();// 绘制出处于新坐标上的敌机judgePlane();// 判断敌机是否与战机接触}flag_enemy++;if (flag_enemy >= rank){flag_enemy = 0;}/* 输出得分 */printScore();}}
主函数总不需要我来详细讲解了吧,直接供上(main.cpp):
#define _CRT_SECURE_NO_WARNINGS#include"game.h"using namespace std;int main(){system("title 飞机大战");srand((unsigned int)time(NULL));//随机时间种子HideCursor();//隐藏光标Game game;int a = drawMenu();if (a == 2){game.rank = 20;}system("cls");drawPlaying();game.Playing();}
文章来之不易,希望各位读者能打赏一下博主 。
【用VSC++做一个飞机大战的游戏】升级版链接:飞机大战升级版
- 酥脆腐竹怎么做 脆腐竹的做法大全
- 清炒生菜怎么做才好吃 清炒生菜怎么做
- 臭屁醋怎么做 臭屁醋怎么做?
- 鸡蛋菠萝炒饭 菠萝蛋炒饭的做法
- 聊城高新区惠民消费券能在哪些商店用?
- 鱼饺子做法窍门视频 鱼饺子做法窍门
- 延长棚膜寿命有五招 什么大棚膜使用寿命长
- 萝卜田用什么除草剂 萝卜田适用土壤处理剂
- 自做电饭煲蛋糕怎么做 电饭煲自发粉蛋糕最简单
- 黑枸杞的作用与功效 黑枸杞不能和什么一起吃