贪吃蛇实现

server/2025/2/3 14:33:14/

1.资料来源

https://learn.microsoft.com/zh-cn/windows/console/getstdhandle

2.前言

简介

贪吃蛇是久负盛名的游戏,和俄罗斯方块、扫雷等游戏位列于经典游戏的行列。
《贪食蛇》中玩家控制一条不断移动的蛇,在屏幕上吃掉出现的食物。每吃掉一个食物,蛇的身体就会变长。游戏的目标是尽可能长时间地生存下去,同时避免蛇头撞到自己的身体或屏幕边缘。游戏最初是像素风格,后来发展出了3D版本和多人对战模式。玩家需要灵活操作,利用策略在有限的空间内避免碰撞,挑战高分。

实现基本功能

  1. 贪吃蛇地图绘制
  2. 蛇吃食物的功能(上下左右方向键控制蛇的动作)
  3. 蛇撞墙死亡
  4. 蛇撞自身死亡
  5. 计算得分
  6. 蛇身加速、减速
  7. 暂停游戏

技术要点

C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win2API等。

WIN32API

Win32 API是Windows操作系统的核心编程接口,用于与操作系统内核直接交互。

Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一种函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数的服务的对象是应用程序(Application),所以称之为Application Programming Interface,简称API函数, WIN32API也就是Microsoft Windows 32位平台的应用程序编辑接口。

控制台程序(Console)

平常运行起来的黑框其实就是控制台程序
win + R输入cmd打开控制台窗口
可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列

mode con cols=100 lines=30

也可以通过命令命令设置控制台窗口的名字

title 贪吃蛇

这些能在控制台窗口执行的命令,也可以调用C语言函数system来执行,例如:

#include <stdlib.h>
//system函数可以执行系统命令
int main()
{//设置控制台相关属性system("mode con cols=100 lines=30");system("title 贪吃蛇");//暂停//getchar();  system("pause");return 0;
}

3.相关win32API函数

控制台屏幕上的坐标 COORSD

COORD 是Windows API上定义的一个结构体,表示一个字符在控制台屏幕缓冲区上的坐标,坐标系(0, 0)的原点位于缓冲区的顶部左侧单元格。

typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;

示例

#include <windows.h>
int main()
{COORD pos1 = { 0, 0 };COORD pos2 = { 10, 20 };system("pause");return 0;
}

GetStdHandle 获取句柄

GetStdHandle是一个Windows API函数,它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中获取一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。

HANDLE WINAPI GetStdHandle(_In_ DWORD nStdHandle
);

在这里插入图片描述

HANDLE GetStdHandle(DWORD nStdHandle);
typedef void* HANDLE

CONSOLE_CURSOR_INFO 结构

包含有关控制台游标的信息。

typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;BOOL  bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

在这里插入图片描述


GetConsoleCursorInfo 检索游标信息

检索有关指定控制台屏幕缓冲区的游标大小和可见性的信息。

BOOL WINAPI GetConsoleCursorInfo(_In_  HANDLE               hConsoleOutput,  //句柄_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo  //指针
);PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关
主机游标(光标)的信息。

SetConsoleCursorInfo 设置游标信息

为指定的控制台屏幕缓冲区设置光标的大小和可见性。

BOOL WINAPI SetConsoleCursorInfo(_In_       HANDLE              hConsoleOutput,_In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

示例1

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info = { 0 };//获取和houtput句柄相关的控制台上的光标信息,存储在cursor_info中GetConsoleCursorInfo(houtput, &cursor_info);printf("%d\n", cursor_info.dwSize);system("pause");   //暂停//修改光标的占比cursor_info.dwSize = 50;//设置和houtput句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, &cursor_info);system("pause");return 0;
}

SetConsoleCursorPosition 设置控制台光标位置

设置指定控制台屏幕缓冲区中的光标位置。将要设置的坐标信息存储在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

BOOL WINAPI SetConsoleCursorPosition(_In_ HANDLE hConsoleOutput,_In_ COORD  dwCursorPosition
);

在这里插入图片描述
示例:

void set_pos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x, y };SetConsoleCursorPosition(houtput, pos);
}
int main()
{//直接定位光标位置set_pos(10, 20);system("pause");return 0;
}

GetAsyncKeyState 获取按键情况

确定调用函数时键是向上还是向下,以及上次调用 GetAsyncKeyState 后是否按下了该键。

SHORT GetAsyncKeyState([in] int vKey
);

将键盘上每个键的虚拟键值传给函数,函数通过返回值来分辨键的状态。

GetAsyncKeyState的返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键按过,否则为0。

参考:虚拟键代码 https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes


分装宏函数

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1) ? 1 : 0)
//结果是1表示按过,结果是0表示没按过

示例:

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1) ? 1 : 0)
int main()
{//按哪个数字打印哪个数字while (1){if (KEY_PRESS(0x30))printf("0\n");else if (KEY_PRESS(0x31))printf("1\n");else if (KEY_PRESS(0x32))printf("2\n");else if (KEY_PRESS(0x33))printf("3\n");else if (KEY_PRESS(0x34))printf("4\n");else if (KEY_PRESS(0x35))printf("5\n");else if (KEY_PRESS(0x36))printf("6\n");else if (KEY_PRESS(0x37))printf("7\n");else if (KEY_PRESS(0x38))printf("8\n");else if (KEY_PRESS(0x39))printf("9\n");}return 0;
}

4.贪吃蛇游戏设计与分析

4.1地图

在游戏地图上,打印宽字符。普通的字符是占一个字节的,这类宽字符是占2个字节。
汉字本质上也是宽字符。

这里再简单的讲一下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。C语言最初假定地址都是单字节的,但是这些假定并不是在世界的任何地方都适用。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入了宽字符的类型 wchar_t 和宽字符的输入输出函数,加入了 <locale.h> 头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

4.1.1<locale.h>本地化

<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。
在标准库中,依赖地区的部分有以下几项:

  1. 数字量的格式
  2. 货币量的格式
  3. 字符集
  4. 日期和时间的表示形式

4.1.2类项

通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改。下面的一个宏,指定一个类项:
在这里插入图片描述


参考资料:https://learn.microsoft.com/zh-cn/cpp/text/locales-and-code-pages

https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-170

4.1.3 setlocale函数

char *setlocale(int category,const char *locale
);wchar_t *_wsetlocale(int category,const wchar_t *locale
);

setlocale函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。
setlocale的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有的类项。
C标准给第二个参数仅定义了2种可能取值: “C”(正常模式)和 “”(本地模式)。

在任意程序执行开始,都会隐藏式执行调用:

setlocale(LC_ALL, "C");

调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。

setlocale(LC_ALL, "");

setlocale的返回值是一个字符串指针,表示已经设置好的格式,如果调用失败,则返回空指针NULL。
setlocale()可以用来查询当前的地区,这时第二个参数设为NULL就可以了。


示例:

#include <locale.h>
int main()
{char* ret = setlocale(LC_ALL, NULL);printf("%s\n", ret);ret = setlocale(LC_ALL, "");printf("%s\n", ret);return 0;
}

参考资料:https://legacy.cplusplus.com/reference/clocale/setlocale/?kw=setlocale

/* setlocale example */
#include <stdio.h>      /* printf */
#include <time.h>       /* time_t, struct tm, time, localtime, strftime */
#include <locale.h>     /* struct lconv, setlocale, localeconv */int main ()
{time_t rawtime;struct tm * timeinfo;char buffer [80];struct lconv * lc;time ( &rawtime );timeinfo = localtime ( &rawtime );int twice=0;do {printf ("Locale is: %s\n", setlocale(LC_ALL,NULL) );strftime (buffer,80,"%c",timeinfo);printf ("Date is: %s\n",buffer);lc = localeconv ();printf ("Currency symbol is: %s\n-\n",lc->currency_symbol);setlocale (LC_ALL,"");} while (!twice++);return 0;
}

4.1.3 宽字符的打印

宽字符的字面量必须叫上前缀L,否则C语言会把字面量当作窄字符类型处理。前缀L在单引号前面,表示宽字符。宽字符的打印使用wprintf,对应wprintf()的占位符为 %lc;在双引号前面,表示宽字符串,对应wprintf()的占位符为 %ls

int main()
{//设置本地化setlocale(LC_ALL, "");//宽字符字面量前加 Lwchar_t ch1 = L'你';wchar_t ch2 = L'好';wchar_t ch3 = L'世';wchar_t ch4 = L'界';printf("%c%c\n", 'a', 'b');//格式串前面也要加 Lwprintf(L"%lc\n", ch1);wprintf(L"%lc\n", ch2);wprintf(L"%lc\n", ch3);wprintf(L"%lc\n", ch4);return 0;
}

代码

snake.h

#pragma once#include <stdio.h>
#include <locale.h>
#include <stdbool.h>
#include <windows.h>
#include <stdlib.h>
#include <time.h>#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BOOY L'●'
#define FOOD L'★'//类型的声明//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//蛇的状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL//正常退出
};//蛇身的节点类型
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
} SnakeNode, *pSnakeNode;//贪吃蛇
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头的指针pSnakeNode _pFood;//指向食物节点的指针enum DIRECTION _dir;//蛇的方向enum GAME_STATUS _status;//游戏的状态int _food_weight;//一个食物的分数int _score;//总成绩int _sleep_time;//休息时间,时间越短速度越快,时间越长速度越慢
} Snake, *pSnake;//函数声明//设置光标位置
void SetPos(short x, short y);
//游戏初始化
void GameStart(pSnake ps);
//欢迎界面的打印
void WelcomeToGame();
//绘制地图
void CreateMap();
//初始化蛇身
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//运行游戏
void GameRun(pSnake ps);
//蛇的移动-走一步
void SnakeMove(pSnake ps);
//判断下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);
//下一个位置是食物,吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);
//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps);
//检查是否撞墙
void KillByWall(pSnake ps);
//检查是否撞到自己
void KillBySelf(pSnake ps);
//结束运行-善后工作
void GameEnd(pSnake ps);

snake.c

#define _CRT_SECURE_NO_WARNINGS 1#include "snake.h"//设置光标位置
void SetPos(short x, short y)
{//获取标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x, y };SetConsoleCursorPosition(houtput, pos);
}//欢迎界面的打印
void WelcomeToGame()
{SetPos(38, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(40, 20);system("pause");system("cls");SetPos(30, 14);wprintf(L"用↑.↓.→.←来控制蛇的移动,按z加速,按x减速\n");SetPos(30, 15);wprintf(L"加速能够获得更高的分数\n");SetPos(40, 20);system("pause");system("cls");
}//绘制地图
void CreateMap()
{int i = 0;//上for (i = 0; i < 29; i++)wprintf(L"%lc", WALL);//左右for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);SetPos(56, i);wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (i = 0; i < 29; i++)wprintf(L"%lc", WALL);
}//初始化蛇身
void InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;//创建蛇身节点for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc()");exit(1);}cur->next = NULL;cur->x = POS_X + 2 * i;cur->y = POS_Y;//头插法插入链表if (ps->_pSnake == NULL)  //空链表ps->_pSnake = cur;else  //非空{cur->next = ps->_pSnake;ps->_pSnake = cur;}}//打印蛇身cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//设置贪吃蛇属性ps->_dir = RIGHT;//默认向右ps->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 200;//单位是毫秒ps->_status = OK;
}//创建食物
void CreateFood(pSnake ps)
{int x = 0;int y = 0;//生成x是2的倍数// x: 2-54// y: 1-25//x = 2 * (rand() % 27) + 2;
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y的坐标不能和设的身体坐标冲突pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->y)goto again;cur = cur->next;}//创建食物的节点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood():malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);wprintf(L"%lc", FOOD); ps->_pFood = pFood;
}//1.游戏初始化
void GameStart(pSnake ps)
{//0.先设置窗口信息,再光标隐藏system("mode con cols=100 lines=30");//设置窗口大小system("title 贪吃蛇");        HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄CONSOLE_CURSOR_INFO CursorInfo;//光标信息GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//隐藏控制台光标SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态//1.打印环境界面和功能介绍WelcomeToGame();//2.绘制地图CreateMap();//3.初始化蛇身InitSnake(ps);//4.创建食物CreateFood(ps);
}//打印帮助信息
void printHelpInfo()
{SetPos(64, 14);wprintf(L"%ls", L"不能穿墙,不能咬到自己");SetPos(64, 15);wprintf(L"%ls", L"用↑.↓.→.←来控制蛇的移动");SetPos(64, 16);wprintf(L"%ls", L"按z加速,按x减速");SetPos(64, 17);wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
//检测虚拟键值
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1) ? 1 : 0)
//暂停
void Pause()
{while (1){Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}}
}
//判断下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{//是食物返回1,不是食物返回0//return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)return 1;elsereturn 0;
}
//下一个位置是食物,吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{//在CreateFood()里创建了pFood和SnakeMove()里创建了pNextNode两个食物节点,需要连接一个,释放一个//头插法ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放下一个位置的节点free(pn);pn = NULL;//打印pSnakeNode cur = ps->_pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}ps->_score += ps->_food_weight;//重新创建食物CreateFood(ps);
}
//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;pSnakeNode cur = ps->_pSnake;while (cur->next->next != NULL){SetPos(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//最后一个节点要打印空白字符(两个空格)覆盖原来的蛇身节点BOOYSetPos(cur->next->x, cur->next->y);printf("  ");//释放最后一个节点free(cur->next);cur->next = NULL;
}
//检查是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 || ps->_pSnake->y == 26){ps->_status = KILL_BY_WALL;}
}
//检查是否撞到自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while (cur){if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y){ps->_status = KILL_BY_SELF;break;}cur = cur->next;}
}
//蛇的移动-走一步
void SnakeMove(pSnake ps)
{//创建一个节点,表示蛇即将到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}switch (ps->_dir){case UP:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;break;case DOWN:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;break;case LEFT:pNextNode->x = ps->_pSnake->x - 2;pNextNode->y = ps->_pSnake->y;break;case RIGHT:pNextNode->x = ps->_pSnake->x + 2;pNextNode->y = ps->_pSnake->y;break;}//检查下一个坐标处是否是食物if (NextIsFood(pNextNode, ps)){EatFood(pNextNode, ps);}else{NoFood(pNextNode, ps);}//检查是否撞墙KillByWall(ps);//检查是否撞到自己KillBySelf(ps);
}//2.运行游戏
void GameRun(pSnake ps)
{//打印帮助信息printHelpInfo();do{//打印总分数和食物的分值SetPos(64, 10);printf("总分数:%d\n", ps->_score);SetPos(64, 11);printf("当前食物的分数:%2d\n", ps->_food_weight);if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)ps->_dir = UP;else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)ps->_dir = DOWN;else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)ps->_dir = LEFT;else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)ps->_dir = RIGHT;else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){//正常退出游戏ps->_status = END_NORMAL;}else if (KEY_PRESS(0x5A)){//加速if (ps->_sleep_time > 80)//分四档{ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(0x58)){//减速if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}//蛇的移动-走一步SnakeMove(ps);Sleep(ps->_sleep_time);} while (ps->_status == OK);
}//3.结束运行-善后工作
void GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:printf("正常退出游戏\n");break;case KILL_BY_WALL:printf("撞到墙上,游戏结束\n");break;case KILL_BY_SELF:printf("撞到自己,游戏结束\n");break;}//释放蛇身链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1#include "snake.h"//游戏的测试逻辑
void test()
{int ch = 0;do{system("cls");//清屏//创建贪吃蛇Snake snake = { 0 };GameStart(&snake);//运行游戏GameRun(&snake);//结束运行-善后工作GameEnd(&snake);SetPos(20, 15);printf("再来一局吗?(Y/N)");ch = getchar();while (getchar() != '\n');//清理\n} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置适配本地环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test();return 0;
}

http://www.ppmy.cn/server/164635.html

相关文章

基于SpringBoot的软件产品展示销售系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

蓝桥云课下载(jdk11、eclipse、idea)

目录 下载jdk11下载eclipse下载idea 下载jdk11 下载地址&#xff1a; &#xff08;自用&#xff09; https://www.lanqiao.cn/courses/44495/learning/?id3144371&compatibilityfalse 安装步骤&#xff1a; 双击 -> -> 下一步 -> 配置环境变量&#xff08;略…

27. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表服务

报表是每个记账应用所具备的功能&#xff0c;要实现报表功能就需要把账本的核心功能&#xff08;记账&#xff09;完成&#xff0c;因此报表服务作为本专栏第一部分单体应用开发中最后一个要实现的功能&#xff0c;这一篇文章很简单&#xff0c;我们一起来实现一个简单的报表服…

Hive修复分区

Hive修复分区 简介 Hive的MSCK REPAIR TABLE命令用于修复&#xff08;即添加丢失的&#xff09;表分区。通常用于那些已在HDFS中存在&#xff0c;但尚未在Hive元数据中注册的分区。 当你在HDFS文件系统中手动添加或删除分区目录&#xff0c;Hive并不会自动识别这些更改。为同步…

实现一个安全且高效的图片上传接口:使用ASP.NET Core和SHA256哈希

实现一个安全且高效的图片上传接口&#xff1a;使用ASP.NET Core和SHA256哈希 在现代Web应用程序中&#xff0c;图片上传功能是常见的需求之一。无论是用户头像、产品图片还是文档附件&#xff0c;确保文件上传的安全性和效率至关重要。本文将详细介绍如何使用ASP.NET Core构建…

【回溯+剪枝】找出所有子集的异或总和再求和 全排列Ⅱ

文章目录 1863. 找出所有子集的异或总和再求和解题思路&#xff1a;子集问题解法&#xff08;回溯 剪枝&#xff09;47. 全排列 II解题思路&#xff1a;排序 回溯 剪枝 1863. 找出所有子集的异或总和再求和 1863. 找出所有子集的异或总和再求和 一个数组的 异或总和 定义为…

CSS(快速入门)

欢迎大家来到我的博客~欢迎大家对我的博客提出指导&#xff0c;有错误的地方会改进的哦~点击这里了解更多内容 目录 一、什么是CSS?二、基本语法规范三、CSS选择器3.1 标签选择器3.2 id选择器3.3 class选择器3.4 通配符选择器3.5 复合选择器 四、常用CSS样式4.1 color4.2 font…

『 C 』 `##` 在 C 语言宏定义中的作用解析

文章目录 ## 运算符的基本概念可变参数宏与 ## 的应用可变参数宏简介## 处理可变参数的两种情况可变参数列表为空可变参数列表不为空 示例代码验证 在 C 和 C 编程里&#xff0c;宏定义是个很有用的工具。今天咱们就来聊聊 ## 这个预处理器连接运算符在宏定义中的作用&#xff…