深入探索SDL游戏开发

news2024/11/20 13:34:07

在这里插入图片描述

前言

欢迎来到小K的SDL专栏第二小节,本节将为大家带来基本窗口构成、渲染器、基本图形绘制、贴图、事件处理等的详细讲解,看完后希望对你有收获

文章目录

      • 前言
      • 一、简单窗口
      • 二、渲染器
      • 三、基本图形绘制
        • 1、点
        • 2、线
        • 3、矩形
        • 4、圆和椭圆
      • 四、贴图
      • 五、事件处理
        • 1、事件处理流程
        • 2、效果

一、简单窗口

✨第一步,我们先包含SDL图形库的头文件

#include<SDL.h>

✨第二步,我们需要初始化SDL2库

int main(int argc, char* argv[])  //主函数必须这样写
{
	//初始化SDL库
	if (SDL_Init(SDL_INIT_VIDEO) != 0)
	{
		SDL_Log("Init failed!%s\n", SDL_GetError());
		return -1;
	}

注意主函数的形参,必须是一个整型,后跟上一个char*数组(参数分别代表命令行参数个数和命令行参数数组),不能是其他形式的main函!

在主函数中我们先调用SDL_init初始化函数,如果不先初始化 SDL,就不能调用任何 SDL 函数。暂时我们只需要SDL的视频子系统,所以我们先只将 SDL_INIT_VIDEO 标志传递给它。其它的SDL系统大家可以转到定义看一看,其中有一个是全部都包含了,还是很有意思的!!!

当发生错误时,SDL_Init 返回 负数。当出现错误时,我们可以将具体的错误原因打印到控制台。在SDL中有一个和printf函数功能相同的函数,即SDL_Log。然后用SDL_GetError获取错误字符串并打印出来。

第三步,我们就到了紧张刺激的创建窗口的截断了,开干!!!

//SDL_WINDOWPOS_UNDEFINED
SDL_Window* window = SDL_CreateWindow(u8"king word", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
if (!window)
{
	SDL_Log("create window failed!%s\n", SDL_GetError());
	return -1;
}

如果SDL成功初始化,我们将使用SDL_CreateWindow创建一个窗口。

  • 第一个参数设置窗口的标题;前面的u8是如果我们的窗口名字中有汉字,它会使汉字正常显示,反之会出现乱码
  • 接下来两个参数分别是窗口的x和y,即窗口在屏幕上的位置,我们不关心位置在那里,所以直接传SDL_WINDOWPOS_UNDEFINED即可,我这里使用的是SDL_WINDOWPOS_CENTERED,表示屏幕居中的意思;
  • 接下来的两个参数分别表示窗口的宽度和高度;
  • 最后一个参数表示窗口创建成功之后显示出来。
    如果有错误,SDL_CreateWindow 返回 NULL。我们将错误打印到控制台。

最后一步就是设置延迟和销毁窗口了

//暂停一会
SDL_Delay(2000);
//销毁窗口
SDL_DestroyWindow(window);
//退出并清理SDL库
SDL_Quit();

如果窗口被成功创建,则会显示到桌面。
为了防止它消失,我们将调用SDL_Delay。 SDL_Delay将等待给定的毫秒数。 一毫秒是千分之一秒。 这意味着上面的代码将使窗口等待2000 /1000秒或2秒。
需要注意的重要一点是,当SDL延迟时,它不能接受来自键盘或鼠标的输入。当您运行这个程序时,如果它没有响应,请不要惊慌。我们没有给它处理鼠标和键盘的代码 ;

退出之前需要调用SDL_DestroyWindow手动销毁窗口和调用SDL_Quit清理所有初始化的子系统。

!!!简单的窗口我们已经可以熟练的完成了,接下来我们就要更深入的了解SDL了,激动吧!激动就对了!!!

二、渲染器

在讲基本图形绘制之前,先给大家讲一个重要的点,那就是渲染器,什么是渲染器呐?打个比方,建模就是用摄像机拍照,渲染就是打印出照片。渲染器的话就是实现建模到出图的一个必经桥梁。所以我们只有窗口是不够的我们还需要渲染器,把我们绘制的东西打印出来,渲染到屏幕上

第一步,创建渲染器

//创建渲染器
SDL_Renderer* render = SDL_CreateRenderer(window, -1, 0);
if (!render)
{
	SDL_Log("create Renderer failed%s\n", SDL_GetError());
	goto done;
}

这里用SDL_CreateRendererAPI来创建渲染器

  • 第一个参数表示你要在那个窗口上显示渲染器
  • 第二个和第三个参数这里先不讲,一般填-1和0就可以了
  • 返回的SDL_Renderer*,需要接收一下,判断下渲染器是否创建成功

这里由于C语言的代码重用度不高,自己又懒得写,所以用了go语句,渲染器创建失败,直接跳到最后的销毁窗口

第二步,设置渲染器背景颜色

//设置渲染器颜色
SDL_SetRenderDrawColor(render, 255, 255, 255, SDL_ALPHA_OPAQUE);
  • 第一个参数就是你要设置颜色的渲染器
  • 后面三个参数就是RGB
  • 最后一个参数是RGBA中的A,设置不透明度的,一般默认设置255就好了,或者用它提供的宏SDL_ALPHA_OPAQUE,它的值也是255

第三步,清屏

//清屏----------->背景颜色为上边设置的渲染器颜色
SDL_RenderClear(render);

也就是让上面设置的颜色显示出来


然后在这之后你就可以放绘图代码了


最后一步就是让你上面的绘图全部显示出来,并把渲染器销毁

//渲染器出场----->让前面的渲染器操作显示出来
SDL_RenderPresent(render);	
//销毁渲染器
SDL_DestroyRenderer(render);

好了,渲染器的简单介绍就到这里了,下面我们来介绍基本的图形绘制

三、基本图形绘制

1、点

在这里插入图片描述

SDL_Point point[10];
for (int i = 0; i < 10; i++)
{
	point[i] = (SDL_Point){ i,i };
}
SDL_RenderDrawPoints(render, point, 10);

这里直接绘制了十个点,值得注意的是,数组中的赋值需要强转一下,这里不像C++中会提供隐士转换,因为SDL的库大部分都是C语言编写的

最后一个绘制函数中的参数介绍

  • 第一个参数就是你要绘制到哪个渲染器
  • 第二个参数就是数组
  • 第三个参数就是点的个数

同样,如果你要绘制一个点,那么就用SDL_RenderDrawPoint,把s去掉即可,参数中第二个填点就好了

2、线

在这里插入图片描述

//2、绘制线
SDL_RenderDrawLine(render, 0, 480, 640, 0);
SDL_Point ps[] = { {0,240},{320,0},{640,480} };
SDL_RenderDrawLines(render, ps, 3);

效果如上图,这里的API参数的意思也显而易见

  • 最后四个数字就是两个点的坐标
  • 然后加s的系列和点加s的系列差不多,第二个参数为数组名,第三个参数为点的个数,这个函数会把这几个连到一起,成为折线

3、矩形

在这里插入图片描述

//3、矩形
SDL_Rect rect = { 100,100,20,20 };
SDL_RenderDrawRect(render, &rect);
rect = (SDL_Rect){ 125,125,20,20 };
SDL_RenderFillRect(render, &rect);

✨画矩形就简单了,后面一个参数就是一个矩形类,这里介绍一下矩形类,例如SDL_Rect rect = { 100,100,20,20 };就是矩形左上角坐标为100,100,然后宽和高分别为20,20,带fill的是填充矩形,颜色要通过SDL_SetRenderDrawColor来设置

4、圆和椭圆

SDL库中并没有提供原生的画圆函数,这里我们自己写了,但是效率不高,大家看看就行,少用

//圆
void SDL_RenderDrawCircle(SDL_Renderer* pRender, float x, float y, float r)
{
	float angle = 0;
	for (angle = 0; angle < 360; angle += 0.1)
	{
		SDL_RenderDrawPoint(pRender, x + r * SDL_cos(angle), y + r * SDL_sin(angle));
	}
}
void SDL_RenderFillCircle(SDL_Renderer* pRender, float x, float y, float r)
{
	register float angle = 0;
	while (r > 0)
	{
		for (angle = 0; angle < 360; angle += 0.1)
		{
			SDL_RenderDrawPoint(pRender, x + r * SDL_cos(angle), y + r * SDL_sin(angle));
		}
		r -= 1;
	}
}

//椭圆
void SDL_RenderDrawEllipse(SDL_Renderer* pRender, SDL_Rect* rect)
{
	//半轴长
	int aHalf = rect->w / 2;
	int bHalf = rect->h / 2;

	int x, y;
	//求出圆上每个坐标点
	for (float angle = 0; angle < 360; angle += 0.1f)
	{
		x = (rect->x + aHalf) + aHalf * SDL_cos(angle);
		y = (rect->y + bHalf) + bHalf * SDL_sin(angle);
		SDL_RenderDrawPoint(pRender, x, y);
	}
}
void SDL_RenderFillEllipse(SDL_Renderer* pRender, SDL_Rect* rect)
{
	SDL_Rect r = *rect;
	//半轴长
	float aHalf;
	float bHalf;
	//椭圆上每点坐标
	float x, y;

	while (r.w >= 0 && r.h >= 0)
	{
		aHalf = r.w / 2.f;
		bHalf = r.h / 2.f;
		//求出圆上每个坐标点
		for (float angle = 0; angle < 360; angle += 0.1f)
		{
			x = (r.x + aHalf) + aHalf * SDL_cos(angle);
			y = (r.y + bHalf) + bHalf * SDL_sin(angle);
			SDL_RenderDrawPointF(pRender, x, y);
		}
		r.x++;
		r.y++;
		r.w -= 2;
		r.h -= 2;
	}
}

在这里插入图片描述
在这里插入图片描述

	//绘制圆,自定义函数----->40ms,比较慢,不建议使用
	SDL_RenderDrawCircle(render, 300, 300, 20);
	SDL_RenderFillCircle(render, 330, 330, 20);

	//绘制椭圆
	SDL_Rect r = { 200,200,40,50 };
	SDL_RenderDrawEllipse(render, &r);
	r = (SDL_Rect){ 250,200,40,50 };
	SDL_RenderFillEllipse(render, &r);

圆的参数就不说,就是一个原心,一个半径;许多小伙半看到椭圆的参数就蒙圈了,怎么还放一个矩形类进去了,其实椭圆就是矩形大的内切,所以就不难解释了

四、贴图

在这里插入图片描述
在这里插入图片描述

//加载图片
SDL_Surface* sfc = SDL_LoadBMP("./assets/images/1.bmp");
if (!sfc)
{
	SDL_Log("surface load failed!%s\n", SDL_GetError());
	goto done;
}
//将surface转换为texture------->效率更高,加载更快
SDL_Texture* tex = SDL_CreateTextureFromSurface(render, sfc);
if (!tex)
{
	SDL_Log("Texture load failed!%s\n", SDL_GetError());
	//用完释放图片,后面都用Texture
	SDL_FreeSurface(sfc);
	goto done;
}
//用完释放图片,后面都用Texture
SDL_FreeSurface(sfc);
//输出图片
SDL_Rect desRect = { 0,0 };
SDL_Rect srcRect = { 145,246,38,73 };
//查询Texture的宽度和高度
SDL_QueryTexture(tex, NULL, NULL, &desRect.w, &desRect.h);
SDL_RenderCopy(render, tex, NULL, &desRect);

第一步,加载图片

//加载图片
SDL_Surface* sfc = SDL_LoadBMP("./assets/images/1.bmp");
if (!sfc)
{
	SDL_Log("surface load failed!%s\n", SDL_GetError());
	goto done;
}

返回的是SDL_Surface*需要接收一下,看有没有加载成功,需要注意的是后面要填图片路径,但是提一下,填相对路径比较好,绝对路径是带盘符的,如果你发一个SDl的游戏给别人,填的相对路径就可以加载,发绝对路径,人家可能没下到和你同一个盘符,更不用说还有其他的文件前缀

第二步,转换图片

//将surface转换为texture------->效率更高,加载更快
SDL_Texture* tex = SDL_CreateTextureFromSurface(render, sfc);
if (!tex)
{
	SDL_Log("Texture load failed!%s\n", SDL_GetError());
	//用完释放图片,后面都用Texture
	SDL_FreeSurface(sfc);
	goto done;
}

为什么要转换,大家一看第一个APISDL_Surface就应该明白一点意思,表面,代表什么,加载深度不快,而SDL_Texture采用CPU加速,渲染,也就是常常说的硬件家速

最后一步,输出图片

//输出图片
SDL_Rect desRect = { 0,0 };
SDL_Rect srcRect = { 145,246,38,73 };
//查询Texture的宽度和高度
SDL_QueryTexture(tex, NULL, NULL, &desRect.w, &desRect.h);
SDL_RenderCopy(render, tex, NULL, &desRect);
  • SDL_QueryTexture是查询图片的宽度和高度,第二第三个参数不用管,填NULL就可以了,第三四个参数,把查询到的宽度和高度赋给矩形
  • SDL_RenderCopy让图片显示出来,第四个参数填的是图片的实际大小,那这张图片显示的时候就会在窗口全屏显示,进行了缩放
    • 第三个参数什么意思呐?就是图片的局部显示,放入的是一个矩形类,x、y、w、h,即要显示的区域的左上角坐标和区域的宽度、高度

五、事件处理

//事件处理,主循环
	SDL_bool isDown = SDL_FALSE;
	while (!isDown)
	{
		//处理事件
		SDL_Event ev = { 0 };
		//处理队列上的事件
		if (SDL_PollEvent(&ev))
		{
			//事件等于退出事件,结束主循环,退出程序
			if (ev.type == SDL_QUIT)   //枚举类型,注意不要写错了
			{
				isDown = SDL_TRUE;
				//goto done;
			}
			//键盘按下事件
			else if (ev.type == SDL_KEYDOWN)
			{
				//按下的什么键
				switch (ev.key.keysym.sym)
				{
				case SDLK_UP:
					SDL_Log("up\n");
					break;
				case SDLK_DOWN:
					SDL_Log("down\n");
					break;
				case SDLK_LEFT:
					SDL_Log("left\n");
					break;
				case SDLK_RIGHT:
					SDL_Log("right\n");
					break;
				default:
					SDL_Log("key:%d\n", ev.key.keysym.sym);
					break;
				}
			}
			//鼠标按下/弹起事件
			else if (ev.type == SDL_MOUSEBUTTONDOWN)
			{
				switch (ev.button.button)
				{
				case SDL_BUTTON_LEFT:
					SDL_Log("button left pos(%d,%d)\n", ev.button.x, ev.button.y);
					break;
				case SDL_BUTTON_MIDDLE:
					SDL_Log("button middle\n");
					break;
				case SDL_BUTTON_RIGHT:
					SDL_Log("button right pos(%d,%d)\n", ev.button.x, ev.button.y);
					break;
				case SDL_BUTTON_X1:
					SDL_Log("button X1 pos(%d,%d)\n", ev.button.x, ev.button.y);
					break;
				case SDL_BUTTON_X2:
					SDL_Log("button X2 pos(%d,%d)\n", ev.button.x, ev.button.y);
					break;
				}
			}
			else if (ev.type == SDL_MOUSEBUTTONUP)
			{

			}
			//鼠标移动事件
			else if (ev.type == SDL_MOUSEMOTION)
			{
				//SDL_Log("mouse motion pos(%d,%d)\n", ev.motion.x, ev.motion.y);
			}
			//鼠标滚轮
			else if (ev.type == SDL_MOUSEWHEEL)
			{
				SDL_Log("mouseWheel dir(%d,%d) pos(%d,%d)\n",
					ev.wheel.x, ev.wheel.y, ev.wheel.mouseX, ev.wheel.mouseY);
			}
		}
	}

1、事件处理流程

✨除了将图像放在屏幕上之外,游戏还要求您处理来自用户的输入。您可以使用 SDL事件处理系统来做到这一点。(把一下代码放到main函数中)

//主循环标志
bool isDone = false;

✨在输出表面和更新窗口表面之后,我们声明了一个退出标志,用于跟踪用户是否已退出。由于此时我们刚刚启动了应用程序,显然它被初始化为false。

✨我们还声明了一个SDL_Event联合。SDL事件是有意义的,比如 按键、 鼠标移动、 操纵杆 按钮按下等。在这个应用程序中,我们将找到退出事件来结束应用程序。

//应用程序主循环
 while( !isDone )
 {

✨在之前的教程中,我们让程序在关闭前等待几秒钟。 在这个应用程序中,我们让应用程序等到用户退出后才关闭。

✨当用户没有退出时,我们会有应用程序循环。 这种在应用程序处于活动状态时持续运行的循环称为主循环,有时也称为游戏循环。 它是任何游戏应用程序的核心。

//事件处理
static SDL_Event ev ={0};
//处理队列上的事件
while( SDL_PollEvent( &ev ) != 0 )
{
   //用户请求退出
    if( ev.type == SDL_QUIT )
  	{
         isDone = true;
    }
}

✨在主循环里面写了事件循环。 这样做的作用是继续处理事件队列,直到它为空。

✨当您按下一个键、移动鼠标或触摸触摸屏时,事件就会被放到事件队列中。
在这里插入图片描述

✨然后事件队列将按照事件发生的顺序存储它们,等待您处理它们。 当您想知道发生了什么事件以便处理它们时,您可以轮询事件队列,通过调用SDL_PollEvent获取最近的事件。 SDL_PollEvent所做的就是从事件队列中取出最近的事件,并将该事件中的数据放入我们传递给函数的SDL_Event中。

在这里插入图片描述

✨SDL_PollEvent 将不断从队列中取出事件,直到队列为空。当队列为空时,SDL_PollEvent 将返回 0。所以这段代码所做的就是不断轮询事件队列中的事件,直到它为空。如果来自事件队列的事件是 SDL_QUIT 事件(用户点击窗口右上角关闭按钮产生的事件),我们将退出标志设置为 true,以便我们可以退出应用程序。

//输出图片
SDL_BlitSurface( gXOut, NULL, gScreenSurface, NULL );
//更新窗口表面
SDL_UpdateWindowSurface( gWindow );
}

✨在我们处理完一帧的事件后,我们绘制到屏幕并更新它(如上一教程中所述)。如果退出标志设置为真,应用程序将在循环结束时退出。如果它仍然是假的,它将一直持续到用户 X 掉窗口。

2、效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/562396.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XR交互技术趋势:6DoF追踪、手势识别、眼动跟踪……

XR交互技术提供了用户与虚拟环境进行交互的方式和手段&#xff0c;而实时云渲染则提供了真三维、可交互、高沉浸的图形渲染和计算能力。结合这两者&#xff0c;用户可以通过XR设备获得更真实、更沉浸的虚拟体验&#xff0c;同时享受到优质的图形效果和流畅的交互响应。本篇文章…

关于开发中对端口(port)的几点理解

一、服务端的端口是固定的&#xff0c;客户端的端口是随机的 客户端端口是随机的&#xff0c;比如访问百度&#xff0c;系统为浏览器分配了个端口1024。过一会重开电脑&#xff0c;访问了新浪&#xff0c;可能还是用1024端口&#xff0c;我不关浏览器&#xff0c;还要再开一个浏…

CenterFusion数据处理函数__getitem__()解析

CenterFusion数据处理函数__getitem__解析 1. 图像数据处理1.1 通过利用nuScence_COCO实例化对象获取图像以及相关数据的信息1.2 获取图像数据增强的相关参数&#xff1a;中心点c&#xff0c;尺度scale&#xff0c;旋转rotia和翻转flip1.3 根据生成的参数生成仿射矩阵来对图像进…

spring boot 集成 swagger3

Swagger 3是一种开源的API描述工具&#xff0c;它可以帮助开发人员设计、构建、文档化和测试API。Swagger 3支持多种编程语言和框架&#xff0c;包括Java、Node.js、Python、Ruby等&#xff0c;并提供了许多集成工具和插件&#xff0c;例如Postman、Apigee等。 Swagger 3使用Op…

北京君正应用案例:双镜头双画面乔安枪球联动摄像头

你是否遇到过这种问题&#xff1f; 既要看店铺又要看柜台 既要看车又要看大门 雷龙发展提供原厂技术支持&#xff0c;并提供君正集成电路完整解决方案&#xff0c;大大降低你的开发难度及开发时间。 单镜头摄像头一台不够广 出现监控盲区&#xff0c;让小偷有可趁之机 只能装两…

sql语句---left join or right join

1068. 产品销售分析 I 销售表 Sales&#xff1a; -------------------- | Column Name | Type | -------------------- | sale_id | int | | product_id | int | | year | int | | quantity | int | | price | int | -------------------- (s…

JS文字转语音技术实现

前言 最近在做排队叫号系统&#xff0c;涉及到文字转语音播报&#xff0c;因此总结了几种前端文字转语音发声的方法。 一、Web Speech API h5新提供的一个原生语音识别技术的API&#xff0c;可以将文本转成语音并播放。 作为官方的api&#xff0c;实现的效果是比较符合理想的…

Windows平台下用例图中包含(include)、扩展(extend)和泛化(generalization)介绍

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows平台下用例图中包含(include)、扩展(extend)和泛化(generalization&#xff09;介绍。 用例图是解决用户需求的图&#xff0c;画好用例图一定要理清用例之间的关系。用例之间有三种关系&…

LabVIEWCompactRIO 开发指南33 测试和调试LabVIEW FPGA代码

LabVIEWCompactRIO 开发指南33 测试和调试LabVIEW FPGA代码 如前所述&#xff0c;应在仿真模式下开发LabVIEWFPGA VI&#xff0c;以快速迭代设计并避免冗长的编译时间。当需要测试和调试VI时&#xff0c;可以保持仿真模式或利用其他几个选项。应该根据功能验证与性能的要求以…

【LeetCode】 复制带随机指针的链表

Leetcode 138.复制带随机指针的链表 文章目录 题目描述解题思路运行代码 题目描述 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全…

C语言中这么骚的退出程序方式你知道几个?

前言 在本篇文章当中主要给大家介绍C语言当中一些不常用的特性&#xff0c;比如在main函数之前和之后设置我们想要执行的函数&#xff0c;以及各种花式退出程序的方式。 1、main函数是最先执行和最后执行的函数吗&#xff1f; 1&#xff09;C语言构造和析构函数 通常我们在…

Python数据分析案例27——PCA-K均值-轮廓系数客户聚类

本案例适合应用统计&#xff0c;数据科学&#xff0c;电商专业 K均值对客户进行分类的案例都做烂了......但我认为这个案例还是有一定的价值的&#xff0c;使用了pca&#xff0c;还有轮廓系数寻找最优的聚类个数。 下面来看看 代码准备 导入包 import numpy as np import pa…

网上学影视后期靠谱吗 影视后期剪辑需要学什么

影视后期如果有人手把手当面教的话&#xff0c;当然是最好的。但很多人都没有这么好的条件&#xff0c;实际上&#xff0c;网上也有很多教程可以学习利用。不过&#xff0c;小伙伴们可能会有疑问&#xff0c;网上学影视后期靠谱吗&#xff0c;影视后期剪辑需要学什么&#xff1…

从创意造型到高品质曲面的卓越体验|CATIA ICEM Design Experience

目录 IDX为设计师提供了强大直观的建模工具 IDX为曲面工程师提供高品质数字模型处理能力 IDX与其他工具配合形成完整的数字化解决方案 建模是设计工作的重要环节&#xff0c;合适的数字模型能够在各个环节对整个设计流程产生正面的推动作用。 设计的不同阶段对模型有各自的…

Azkaban从入门到精通以及案例实操系列

1、Azkaban概论 1.1、Azkaban简介 Azkaban 是一个开源的基于 Web 的工作流调度系统&#xff0c;由 LinkedIn 公司开发并维护。它可以帮助用户在大规模数据处理中来管理和调度作业&#xff0c;提供了简单易用、高效可靠的工作流设计和调度功能。 Azkaban 的主要特点包括&…

亿发ERP系统,全链条采购协同管理数智化平台,中小企业采购业务全流程管理

在数字时代&#xff0c;中小型企业在采购管理方面面临多项挑战。 集采管理难&#xff1a;由于资源和专业知识有限&#xff0c;中小企业通常难以建立集中采购职能&#xff0c;无法有效简化整个组织的采购活动。这一挑战包括定义采购政策、标准化程序和实施高效的采购系统。 信…

『赠书活动 | 第六期』《“Java四大名著“,你集齐了吗?》

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 『赠书活动 &#xff5c; 第六期』 本期书籍&#xff1a;《“Java四大名著”&#xff0c;你集齐了吗&#xff1f;》 赠书规则&#xff1a;评论区&#xff1a;点赞&…

供水管网监测系统,供水管网压力监测系统

水是城市赖以生存的血脉&#xff0c;直接关系到居民、企业和公共设施的正常生活和生产。然而&#xff0c;城市供水管网的高效运行面临着一系列挑战&#xff0c;如管道老化、泄漏和不均衡的供水压力等。 目前城市供水管网泄漏检测排查采用人工巡检、人工听漏的方式&#xff0c;巡…

Flutter3.10版本发布,编程语言的重大更新

Flutter是一款强大的跨端开发框架&#xff0c;可以帮助开发者构建高性能、美观、灵活的应用程序&#xff0c;从而实现跨平台开发和部署。小程序容器技术与跨端框架结合使用&#xff0c;为开发者提供一站式的小程序开发和发布服务&#xff0c;帮助他们更加轻松和高效地构建和部署…

CPU和GPU前端的应用

1、CPU&#xff08;英文Central Processing Unit 中央处理器&#xff09; CPU&#xff08;中央处理器&#xff09;是一种通用的处理器&#xff0c;其主要任务是执行计算机程序中的指令和序列。它能够处理复杂的逻辑判断、分支、跳转、内存访问等操作&#xff0c;因此在执行大多…