• Day18:C++飞机大战


     0.基本效果界面展示:

    一、思路与细节

    1.仍然采用单例设计模式,高度抽象出几个类如point、graph、element(敌机、主机(role包含数据成员包含子弹)均包含其作为一个数据成员,进行共通的操作)。

    2.子弹十分密集,按一下空格会发出很多子弹:写一个timer定时器
    (注:当声明和实现放在同一个文件去实现的时候,可以以.hpp结尾)
    MyTimer::Timer(100,0) #100ms产生一颗

    3.注:虽然作了前向声明,但是在.cpp文件中,找不到其相应的成员函数,
    那一定是没有在.cpp中包含其相关的头文件。

    4.我承认:debug最好的定位方式,注释掉疑似代码,看相关功能的变化。

    二、具体实现:

    公共头文件:common.h

    1. #pragma once
    2. #include<graphics.h>
    3. #include<iostream>
    4. #include<thread> /*c++多线程*/
    5. #include<ctime>
    6. #include<string>
    7. #include<map>
    8. #include<list>
    9. #include<vector>
    10. #include<mmsystem.h>
    11. #pragma commment(lib,"winmm.lib")
    12. using namespace std;

    计时器:timer.h

    1. #pragma once
    2. #include"common.h"
    3. class MyTimer
    4. {
    5. public:
    6. static bool Timer(int duration, int id);
    7. protected:
    8. };

    time.cpp

    1. #include "timer.h"
    2. bool MyTimer::Timer(int duration, int id)
    3. {
    4. static int startTime[10];/*自动初始化为0*/
    5. int endTime = clock();
    6. if (endTime - startTime[id] >= duration)
    7. {
    8. startTime[id] = endTime;
    9. return true;
    10. }
    11. return false;
    12. }

    资源存放类: res.h

            在上个推箱子的项目中已经讲过单例设计的基本操作了。在此大致提几个点,都是相通的,大部分的数据成员和成员函数都是static,直接通过类名调用。

            ①构造函数是私有

            ②给一个公有接口,返回资源类的指针,进行对其内部的相关函数以及成员的操作

            ③数据成员用的map进行映射相关的资源!方便后面对资源的索引与调用。

    1. #pragma once
    2. #include"common.h"
    3. /*仍然采用单例设计模式*/
    4. class Res
    5. {
    6. public:
    7. static int WIDTH(string name); /*获取资源的宽度和高度*/
    8. static int HEIGHT(string name);
    9. static Res* GetInstance();
    10. static void DrawIMG(int x, int y,string name);
    11. static void DrawRole(int x, int y, string name, int preIndex);
    12. /*注;角色有不同的,用preIndex来索引*/
    13. //static DWORD WINAPI PlayMusic(LPVOID lparame); /*播放音乐的线程处理函数*/
    14. public:
    15. static map<string,IMAGE*> img; /*图片*/
    16. static map<string, string> music; /*音乐*/
    17. public:
    18. ~Res();
    19. private:
    20. Res();
    21. };

    res.cpp

    (静态数据成员的初始化,

            此外,此处还涉及一个细节,如何去掉一个物体图片周围的阴影部分:大致的实现原理是:准备原图一份,需要切除部分一张图片,/*NOTSRCERASE 先对遮罩进行处理 , 使得遮罩白色变黑 ,黑色区域变透明*//*SRCINVERT 在对小鸟原图处理*/->按位合&,实现背景部分的透明化操作)

    参考博客:c愤怒小鸟

    1. #include "res.h"
    2. /*step1:类外对静态数据成员进行初始化*/
    3. map<string, IMAGE*>Res::img; /*图片*/
    4. map<string, string>Res::music; /*音乐*/
    5. /*获取图片的宽度和高度*/
    6. int Res::WIDTH(string name)
    7. {
    8. return GetInstance()->img[name]->getwidth(); //img是一个map容器,通过name去映射出IMAGE*类型的数组,
    9. } //IMAGE自带获取图片的宽度和高度的相关功能
    10. int Res::HEIGHT(string name)
    11. {
    12. return GetInstance()->img[name]->getheight();
    13. }
    14. Res* Res::GetInstance()
    15. {
    16. static Res* res = new Res;
    17. return res;/*单例设计模式的固定写法*/
    18. }
    19. //只有一张图片的贴图:即背景图贴图
    20. void Res::DrawIMG(int x, int y, string name)
    21. {
    22. putimage(x, y, GetInstance()->img[name]);
    23. }
    24. void Res::DrawRole(int x, int y, string name, int preIndex)
    25. {
    26. //透明贴图(注意:一个固定的贴图方式->去掉背景!!!!)
    27. /*留有白色区域棱角就不好看了 ,此时就需要用函数去处理 , 是白色区域透明化,拿到小鸟的遮罩和原图*/
    28. putimage(x, y, GetInstance()->img[name] + preIndex, NOTSRCERASE);/*NOTSRCERASE 先对遮罩进行处理 , 使得遮罩白色变黑 ,黑色区域变透明*/
    29. putimage(x, y, GetInstance()->img[name] + preIndex+1, SRCINVERT);/*SRCINVERT 在对小鸟原图处理*/
    30. }
    31. Res::~Res()
    32. {
    33. delete img["背景"];
    34. delete[]img["角色"];
    35. delete[]img["敌机"];
    36. delete[]img["子弹"];
    37. }
    38. Res::Res()
    39. {
    40. /*路径处理*/
    41. string background = "..//res//background.jpg";
    42. string roleImg[4] = { "..//res//planeExplode_1.jpg","..//res//planeExplode_2.jpg",
    43. "..//res//planeNormal_1.jpg","..//res//planeNormal_2.jpg" }; //->注:只有下标0和2为有效的人物角色,下标1和3是0和2的遮罩,用于去阴影的。
    44. string ballImg[2] = {"..//res//bullet1.jpg","..//res//bullet2.jpg"};
    45. string enemy[4] = { "..//res//enemy_1.jpg","..//res//enemy_2.jpg",
    46. "..//res//enemyPlane1.jpg", "..//res//enemyPlane2.jpg", };
    47. img["背景"] = new IMAGE;
    48. img["角色"] = new IMAGE[4];
    49. img["子弹"] = new IMAGE[2];
    50. img["敌机"] = new IMAGE[4];
    51. loadimage(img["背景"], background.c_str());
    52. for (int i = 0; i < 4; i++)
    53. {
    54. loadimage(img["角色"] + i, roleImg[i].data());
    55. loadimage(img["敌机"] + i, enemy[i].data());
    56. }
    57. for (int i = 0; i < 2; i++)
    58. {
    59. loadimage(img["子弹"] + i, ballImg[i].c_str()); /*c_str()和data()等效,都是转化为c语言风格的string*/
    60. }
    61. }

    专门用于地图操作的类: graph.h

            负责①创建句柄,并②将地图绘制好

    1. #pragma once
    2. class Graph
    3. {
    4. public:
    5. Graph();
    6. ~Graph();
    7. void DrawMap(); /*对于地图的切换*/
    8. protected:
    9. };

     graph.cpp

    1. #include "graph.h"
    2. #include"res.h"
    3. Graph::Graph()
    4. {
    5. /*用图片的大小来初始化这个窗口*/
    6. initgraph(Res::GetInstance()->img["背景"]->getwidth(),
    7. Res::GetInstance()->img["背景"]->getheight());
    8. }
    9. Graph::~Graph()
    10. {
    11. closegraph();
    12. }
    13. void Graph::DrawMap()
    14. {
    15. Res::DrawIMG(0, 0, "背景"); /*第3个参数是用来map索引相关的资源的*/
    16. }

    抽象出敌机、玩家(主机)、子弹的共性类: element.h

            共同的特征方法:①x、y坐标②是否存活(bool状态)③血量④相关图片的宽度和高度

                    ⑤自身的绘制(直接调用Res类中的绘制函数即可。) ⑥自身的移动(直接调用point类的相关方法即可(见下))

            共同的数据成员:①坐标②名称③是否存活标记④血量

    1. #include"common.h"
    2. #include"res.h"
    3. #include"point.h"
    4. /*抽象出一个元素类,所有的敌机和角色都是由这个进行派生的*/
    5. class Element
    6. {
    7. public:
    8. virtual ~Element();
    9. Element();
    10. Element(int x, int y, string name, bool live, int hp = 0);
    11. int& GetX();
    12. int& GetY();
    13. bool& GetLive();
    14. int& GetHp();
    15. int GetWidth();
    16. int GetHeight();
    17. void DrawElement(int pre);
    18. void MoveElement(int speed,Point::Dir dir);
    19. protected:
    20. Point point; /*元素在窗口上的位置*/
    21. string name; /*元素的名字->用处之一:获取元素的高度和宽度*/
    22. bool live; /*是否存活的标记*/
    23. int hp; /*血量*/
    24. };

    element.cpp

    1. #include "element.h"
    2. #include"res.h"
    3. Element::~Element()
    4. {
    5. }
    6. Element::Element()
    7. {
    8. }
    9. Element::Element(int x, int y, string name, bool live, int hp):point(x,y),name(name)/*类的组合->初始化参数列表*/
    10. {
    11. this->live = live;
    12. this->hp = hp;
    13. }
    14. int& Element::GetX()
    15. {
    16. return point.getX();
    17. }
    18. int& Element::GetY()
    19. {
    20. return point.getY();
    21. }
    22. bool& Element::GetLive()
    23. {
    24. return live;
    25. }
    26. int& Element::GetHp()
    27. {
    28. return hp;
    29. }
    30. int Element::GetWidth()
    31. {
    32. return Res::GetInstance()->WIDTH(name);/*直接调用相应的接口,调用中间函数*/
    33. }
    34. int Element::GetHeight()
    35. {
    36. return Res::GetInstance()->HEIGHT(name);
    37. }
    38. void Element::DrawElement(int pre)
    39. {
    40. Res::GetInstance()->DrawRole(point.getX(), point.getY(), name, pre);
    41. }
    42. void Element::MoveElement(int speed, Point::Dir dir)
    43. {
    44. point.move(speed, dir);
    45. }

    内部物体的移动原理抽象出来的类: point.h

            关键:枚举类型->上下左右

            成员函数:①x、y坐标的接口②构造函数、拷贝构造函数③关键还是move()方法:如何实现物体的移动,要做好与Element类的对接。

    1. #pragma once
    2. #include"common.h"
    3. /*重点:所有物体的运转,都是坐标的改变,于是抽象出此坐标类*/
    4. class Point
    5. {
    6. public:
    7. enum Dir{left,right,down,up};
    8. Point(int x = 0, int y = 0);
    9. Point(const Point& point);
    10. int& getX();
    11. int& getY();
    12. /*移动点*/
    13. void move(int speed, Dir dir);
    14. protected:
    15. int x;
    16. int y;
    17. };

    point.cpp

    1. #include "point.h"
    2. Point::Point(int x, int y)
    3. {
    4. this->x = x;
    5. this->y = y;
    6. }
    7. Point::Point(const Point& point)
    8. {
    9. this->x = point.x;
    10. this->y = point.y;
    11. }
    12. int& Point::getX()
    13. {
    14. return x;
    15. }
    16. int& Point::getY()
    17. {
    18. return y;
    19. }
    20. void Point::move(int speed, Dir dir)
    21. {
    22. switch (dir)
    23. {
    24. case Point::left:
    25. this->x -= speed;
    26. break;
    27. case Point::right:
    28. this->x += speed;
    29. break;
    30. case Point::up:
    31. this->y -= speed;
    32. break;
    33. case Point::down:
    34. this->y += speed;
    35. break;
    36. }
    37. }

    主机的具体实现类: role.h

            数据成员:主角Element*类型的指针plane、和一个存放Element*类型的list

    容器(当做bullet来用!)

            方法:①构造函数(飞机的居中,hp和live状态的设置)

                    ②画飞机和子弹(遍历调用)只需要调用element中写好的方法即可。

                    ③数据成员的接口

                    ④子弹的移动(只会向前移动,遍历每一个子弹-=速度,改变y坐标即可)

                    ⑤plane的移动(异步函数的调用),GetAsyncKeyState()函数调用(具体说明看实现部分的注释!)

                    注:plane的移动中,核心的几点:(i)按上下左右键实现移动调用element类中写好的方法暂且不提,(ii)还有按下空格键,产生子弹(塞到链表中,new出来(注意产生的位置)),为了不让子弹过于密集,采用timer计时器!(iii)子弹的移动后,移动子弹+绘制子弹!!control中就直接调用此函数即可。

    1. #pragma once
    2. #include"common.h"
    3. class Element; /*因为只用到了该类的指针,所以直接前向声明即可!!!*/
    4. class Role
    5. {
    6. public:
    7. Role();
    8. ~Role();
    9. void DrawPlane(int preIndex);
    10. void MovePlane(int speed);
    11. void DrawBullet();
    12. void MoveBullet(int speed);
    13. Element*& GetPlane();
    14. list<Element*>& GetBullet();
    15. protected:
    16. Element* plane; /*角色*/
    17. list<Element*>bullet; /*子弹*/
    18. };

     role.cpp

    1. #include "role.h"
    2. #include"res.h"
    3. #include"element.h"
    4. #include"timer.h"
    5. Role::Role()
    6. {
    7. plane = new Element(Res::GetInstance()->WIDTH("背景")/2-Res::GetInstance()->WIDTH("角色")/2, //x
    8. Res::GetInstance()->HEIGHT("背景")-Res::GetInstance()->HEIGHT("角色"), //y
    9. "角色", //角色名
    10. true, //存活状态
    11. 100); /*产生一个居中的飞机*/ //hp
    12. }
    13. Role::~Role()
    14. {
    15. }
    16. void Role::DrawPlane(int preIndex)
    17. {
    18. plane->DrawElement(preIndex); /*元素类已经做好了*/
    19. }
    20. void Role::MovePlane(int speed)
    21. {
    22. /*GetAsyncKeyState是一个用来判断函数调用时指定虚拟键的状态,确定用户当前是否按下了键盘上的一个键的函数。如果按下,则返回值最高位为1。*/
    23. /*另一个函数GetKeyState与GetAsyncKeyState函数不同。GetAsyncKeyState在按下某键的同时调用,判断正在按下某键。
    24. GetKeyState则在按过某键之后再调用,它返回最近的键盘消息从线程的队列中移出时的键盘状态,判断刚按过了某键。
    25. GetAsyncKeyState 取异步键状态。*/
    26. if (GetAsyncKeyState(VK_UP) && plane->GetY() >= 0)
    27. {
    28. plane->MoveElement(speed, Point::up);
    29. }
    30. if (GetAsyncKeyState(VK_DOWN) && plane->GetY() <= Res::GetInstance()->HEIGHT("背景") - Res::GetInstance()->HEIGHT("角色"))
    31. {
    32. plane->MoveElement(speed, Point::down);
    33. }
    34. /*if (GetAsyncKeyState(VK_RIGHT)&& plane->GetX() <= Res::GetInstance()->WIDTH("背景") - Res::GetInstance()->WIDTH("角色"));
    35. {
    36. plane->MoveElement(speed, Point::right);
    37. }*/
    38. if (GetAsyncKeyState(VK_RIGHT) &&
    39. plane->GetX() <= Res::GetInstance()->WIDTH("背景") - Res::GetInstance()->WIDTH("角色"))
    40. {
    41. plane->MoveElement(speed, Point::right);
    42. }
    43. if (GetAsyncKeyState(VK_LEFT) && plane->GetX() >= 0)
    44. {
    45. plane->MoveElement(speed, Point::left);
    46. }
    47. /*子弹按空格产生*/
    48. if (GetAsyncKeyState(VK_SPACE)&&MyTimer::Timer(100,0)) /*100ms产生一颗*/
    49. {
    50. /*每产生一个子弹,就播放音乐*/
    51. //HANDLE threadID = CreateThread(NULL, 0, Res::PlayMusic, (int*)1, 0, 0);
    52. bullet.push_back(new Element(plane->GetX()+45,plane->GetY()-10,"子弹",1));
    53. //CloseHandle(threadID);
    54. }
    55. MoveBullet(1);
    56. DrawBullet();
    57. }
    58. /*子弹是存在容器中的,要把其都画出来*/
    59. void Role::DrawBullet()
    60. {
    61. for (auto v : bullet)
    62. {
    63. if (v->GetLive()) /*细节:首先是要存活*/
    64. {
    65. v->DrawElement(0);
    66. }
    67. }
    68. }
    69. void Role::MoveBullet(int speed)
    70. {
    71. for (auto v : bullet)
    72. {
    73. v->GetY() -= speed; /*子弹只会向前跑*/
    74. }
    75. }
    76. Element*& Role::GetPlane()
    77. {
    78. return plane;
    79. }
    80. list<Element*>& Role::GetBullet()
    81. {
    82. return bullet;
    83. }

    敌机的具体实现类: enemy.h

            数据成员:由于有多个敌机,本质上和子弹一样,创建一个list容器存放Element*(当做enemy来用)

            成员函数:①构造+析构

                            ②for逐一地遍历绘制

                            ③敌机的移动(调用element中封装好的move(方向固定向下))

                            ④敌机的创建(不在构造函数中写),设计到随机生成+从边界一点一点飞出来的效果,单独写一个create函数,随机生成rand()%宽度,起始创建位置在-1*敌机的高度处。

    1. #pragma once
    2. #include"common.h"
    3. class Element;
    4. class Enemy
    5. {
    6. public:
    7. Enemy();
    8. ~Enemy();
    9. void DrawEnemy(int preIndex);
    10. void MoveEnemy(int speed);
    11. Element* CreateEnemy();
    12. list<Element*>& GetEnemy();
    13. protected:
    14. list<Element*> enemyPlane; /*有多个敌机,用链表存储*/
    15. };

     enemy.cpp

    1. #include "enemy.h"
    2. #include"element.h"
    3. #include"res.h"
    4. Enemy::Enemy()
    5. {
    6. }
    7. Enemy::~Enemy()
    8. {
    9. }
    10. void Enemy::DrawEnemy(int preIndex)
    11. {
    12. for (auto v : enemyPlane)
    13. {
    14. if (v->GetLive())
    15. {
    16. v->DrawElement(preIndex);
    17. }
    18. }
    19. }
    20. void Enemy::MoveEnemy(int speed)
    21. {
    22. for (auto v : enemyPlane)
    23. {
    24. v->MoveElement(speed, Point::down);
    25. }
    26. }
    27. Element* Enemy::CreateEnemy()
    28. {
    29. return new Element(rand() % Res::GetInstance()->WIDTH("背景"),
    30. -1 * Res::GetInstance()->HEIGHT("敌机"), "敌机", 1, 3); /*3点血量,需要打三次才能消失*/
    31. }
    32. list<Element*>& Enemy::GetEnemy()
    33. {
    34. return enemyPlane;
    35. }

    函数流程的具体操作封装的类: control.h

            数据成员:①敌机                                的指针

                              ②主机(包含子弹)           的指针

                              ③地图                                的指针

            成员函数:①构造函数+析构函数

                              ②绘制:

                                    (i)地图,就直接调用graph类的绘制地图函数即可

                                    (ii)主机,调用绘制主机的同时,调用move函数(包含了检测键盘信息的功能,传参速度)

                                    (iii)敌机:A:每过1s即1000ms产生敌机,并放置到容器中

                                                        B:每过10ms,敌机进行移动

                                                        C:绘制敌机

                            ③打飞机的核心逻辑:

                                    (i)碰撞处理:碰到子弹,把子弹的live置为0(这个地方只改标记,删除放在第二步,思路清晰)

                                    (ii)通过标记去删除相应的数据(否则内存会越来越大)

                                                    (注意:迭代器删除的细节!)

    1. #pragma once
    2. #include"role.h"
    3. #include"graph.h"
    4. class Role;
    5. class pMap;
    6. class Enemy;
    7. class Control
    8. {
    9. public:
    10. Control(Graph* pMap = nullptr, Role* pRole = nullptr,Enemy*pEnemy=nullptr);
    11. ~Control();
    12. void DrawMap();
    13. void DrawRole();
    14. void DrawEnemy();
    15. void PlayEnemy();
    16. protected:
    17. /*所有的组成部分都可以在此封装好*/
    18. Role* pRole;
    19. Graph* pMap;
    20. Enemy* pEnemy;
    21. };

     control.cpp

    1. #include "control.h"
    2. #include"timer.h"
    3. #include"role.h"
    4. #include"graph.h"
    5. #include"enemy.h"
    6. #include"element.h"
    7. #include"res.h"
    8. Control::Control(Graph* pMap, Role* pRole,Enemy* pEnemy)
    9. {
    10. this->pMap = pMap;
    11. this->pRole = pRole;
    12. this->pEnemy = pEnemy;
    13. }
    14. Control::~Control()
    15. {
    16. }
    17. void Control::DrawMap()
    18. {
    19. pMap->DrawMap();
    20. }
    21. void Control::DrawRole()
    22. {
    23. pRole->DrawPlane(0);
    24. pRole->MovePlane(1);
    25. }
    26. void Control::DrawEnemy()
    27. {
    28. if (MyTimer::Timer(1000, 1))
    29. {
    30. pEnemy->GetEnemy().push_back(pEnemy->CreateEnemy());/*每过一秒就随机产生一个飞机*/
    31. }
    32. if (MyTimer::Timer(10, 2))
    33. {
    34. pEnemy->MoveEnemy(1);
    35. }
    36. pEnemy->DrawEnemy(0);
    37. }
    38. void Control::PlayEnemy()
    39. {
    40. /*1.碰撞处理:碰到子弹,把子弹的live置为0(这个地方只改标记,删除放在第二步,思路清晰)*/
    41. for (auto bullet : pRole->GetBullet()) /*角色里面自带子弹的数据成员*/
    42. {
    43. if (bullet->GetLive()==0)
    44. {
    45. continue;
    46. }
    47. if (bullet->GetY() < 0) /*子弹出了边界*/
    48. {
    49. bullet->GetLive() = false;
    50. }
    51. for (auto enemy : pEnemy->GetEnemy())
    52. {
    53. if (enemy->GetLive() && /*首先得活着!*/
    54. bullet->GetX() > enemy->GetX() &&
    55. bullet->GetX() < enemy->GetX() + Res::WIDTH("敌机") &&
    56. bullet->GetY() > enemy->GetY() &&
    57. bullet->GetY() < enemy->GetY() + Res::HEIGHT("敌机"))
    58. {
    59. enemy->GetHp() --;
    60. if (enemy->GetHp() <= 0)
    61. {
    62. enemy->GetLive() = false;
    63. }
    64. bullet->GetLive() = false; /*子弹碰到敌机也要消失*/
    65. }
    66. /*敌机出了窗口边界,也要消失*/
    67. if (enemy->GetY() >= Res::HEIGHT("背景"))
    68. {
    69. enemy->GetLive() = false;
    70. }
    71. }
    72. }
    73. /*2.通过标记去删除相应的数据(否则内存会越来越大)*/
    74. for (auto iterE = pEnemy->GetEnemy().begin(); iterE != pEnemy->GetEnemy().end();)
    75. { //细节:迭代的删除,一定要分开,不把++放在for中(否则会删不干净)
    76. if ((*iterE)->GetLive()==false)
    77. {
    78. iterE = pEnemy->GetEnemy().erase(iterE);
    79. }
    80. else
    81. {
    82. iterE++;
    83. }
    84. }
    85. for (auto iterB = pRole->GetBullet().begin(); iterB != pRole->GetBullet().end();)
    86. { //细节:迭代的删除,一定要分开,不把++放在for中(否则会删不干净)
    87. if ((*iterB)->GetLive() == false)
    88. {
    89. iterB = pRole->GetBullet().erase(iterB);
    90. }
    91. else
    92. {
    93. iterB++;
    94. }
    95. }
    96. }

    主函数:MainControl.cpp

            生成随机数种子->pControl初始化->双缓冲绘制图片(防闪屏)->

                    while(1)

                            {cleardevice()->绘制核心物件->打飞机核心逻辑检测};

    1. #include"control.h"
    2. #include"graph.h"
    3. #include"role.h"
    4. #include"enemy.h"
    5. int main()
    6. {
    7. srand((size_t)time(NULL));
    8. Graph* pMap = new Graph;
    9. Role* pRole = new Role;
    10. Enemy* pEnemy = new Enemy;
    11. Control*pControl=new Control(pMap,pRole,pEnemy);
    12. BeginBatchDraw();
    13. while (1)
    14. {
    15. cleardevice();
    16. pControl->DrawMap();
    17. pControl->DrawRole();
    18. pControl->DrawEnemy();
    19. pControl->PlayEnemy();
    20. FlushBatchDraw();
    21. }
    22. while (1);/*防止闪屏*/
    23. return 0;
    24. }

    三、资源:

    C++飞机大战项目代码和素材icon-default.png?t=M5H6https://download.csdn.net/download/zjjaibc/85802748​​​​​​​

  • 相关阅读:
    npm install卡住与node-npy的各种奇怪报错
    CASIO4850万能程序
    并查集模版以及两道例题
    Linux Ubuntu 22.04常用系统快捷键
    css取消移动端长按元素背景色
    Java Random.setseed()方法具有什么功能呢?
    蓝桥杯物联网竞赛_STM32L071_19_输出方波信号(PWM)
    行列均按段分组汇总
    细说React组件性能优化
    QMenu的基本使用:实现右键弹出菜单栏
  • 原文地址:https://blog.csdn.net/zjjaibc/article/details/125492166