对于游戏开发者来说,在开发过程中,加入 UI 的支持是不可或缺的一环,不过想要自己动手敲代码实现 UI 实属一件难事,后来 ImGUI 诞生为开发者们带来直接拿来用般的便利,而这是否意味着 ImGUI 即将去掉传统的 GUI 设计?

作者 | nil

译者 | 苏本如,责编 | 屠敏

出品 | CSDN(ID:CSDNnews)

一个即兴的、未经深思熟虑的想法是:像Dear ImGUI这样的东西有可能是主流UI库的未来吗?

对于那些不知道什么是即时模式图形用户界面或ImGUI的人,可以看看Casey Muratori在2005年制作的一个还算有名的视频:https://youtu.be/Z1qyvQsjK5Y(需科学上网)。

大多数使用ImGUI风格的程序员发现,使用ImGUI来创建用户界面比使用传统的保留模式图形用户界面(GUI)要容易得多。而且性能会得到显著地提高。

典型的保留模式,面向对象的GUI框架是一个系统。在该系统中,你基本上创建了一个GUI框架控件(widget)的“场景图”(窗口、网格、滑块、按钮、复选框等等)。你将你的数据复制到这些控件中,等待事件(event)或回调(callback)在控件被编辑时接收到通知。然后查询控件的新值并将其复制回你的数据中。

这种模式几乎应用于所有的GUI系统中。Windows、WFP、HTML DOM、Apple UIKit、Qt,你能叫出名字的99%的GUI框架都属于保留模式的,面向对象的,“场景图”式的GUI。

这种模式的GUI存在的问题是:

  • 必须编写大量代码来管理GUI对象的创建和销毁。

    设想你有一个滚动列表,你经常需要创建100多个或1000多个GUI控件(就像HTML,创建一个TR,然后是TD,然后是每个TD的内容,等等)。如果数据真的很大,你最终不得不创建一些控件的虚拟窗口,要么在用户滚动时创建新的窗口并且删除旧窗口,要么将旧窗口从后面拉出来,然后将其添加到前面。其结果是:你需要写的代码太多了。

  • 创建和销毁对象导致UI反应迟缓。

    由于GUI对象的创建和销毁速度很慢(通常它们是非常大的对象),因此通常需要编写大量的代码来帮助寻找和设计解决方案,以最小化需要创建和销毁的对象数量。

    想想React如何使用虚拟DOM来识别差异,然后将这些差异应用到实际的GUI控件和DOM树/场景图中。

  • 你必须编制数据传入/传出控件。

    这就需要先将数据复制到控件中,然后对事件做出响应,并将控件中的新数据读回。需要编写更多的代码。

与此相反,ImGUI中没有对象,也几乎没有状态。大多数ImGUI的简单做法是像下面这样调用函数:

// draw a buttonif (ImGUI::Button("Click Me")) {  IWasClickedSoDoSomething();}// draw sliderImGUI::SliderFloat("Speed:" &someInstance.speed, 0.0f, 100.0f);
if (ImGUI::Button("Click Me")) {IWasClickedSoDoSomething();
}
// draw slider
ImGUI::SliderFloat("Speed:" &someInstance.speed, 0.0f, 100.0f);

这里的Button和Slider做了两件事:

  1. 它们将绘制控件所需的位置和纹理坐标添加到一个向量(数组)中。如果控件被裁剪出屏幕或在当前窗口/裁剪矩形之外,则坐标不被添加。

  2. 它们检查鼠标指针的位置、键盘状态等,以操作该控件。如果数据发生变化,它们会立即返回。

所以,这样做有如下优点:

  • 丝毫不需要分配内存,也即需要的内存为零!

  • 速度很快。即使使用非常复杂的UI并且只有单线程的情况下,大多数(如果不是全部)ImGUI在60fps(帧)的速度下运行没有任何问题。

  • 不需要对必须管理的对象进行创建和销毁操作。

  • 没有状态,因为没有对象来存储状态。

  • 基本不需要编制数据。

  • 没有需要注册或响应的事件或回调。

下面两点可能是这样做的缺点:

  • 可能需要更多的CPU。

    我还不能确信这一点总是对的。保留模式GUI设计的初衷是为了尽量减少工作量。假设你有一个类似微软Excel的用户界面。它有75个工具栏按钮和显示300个单元格的电子表格。输入光标位于单元格E7中,并且在闪烁。如果回到Windows 3.0(及更早版本),CPU将绘制像素(GPU那时不存在)。GUI系统确定只需要重新绘制光标本身大小的一些小区域,并且只需要将这些像素直接重新绘制到屏幕内存中。同样,如果键入字母,系统只能确定单元格E7已被修改,只需重新绘制单元格E7。

    在1993-1994年的计算机上,这点很重要。因为计算机无法以每秒60帧的速度绘制整个屏幕。

    因此,对于传统的基于“场景图”的面向对象的保留模式GUI来说,这是最好的做法。

    需要注意的是,系统仍然需要检查图形用户界面的大部分地方来计算最小的影响区域是什么。这可能不如重新绘制每个像素的工作量大,但需要的工作量也不少。

    ImGUI则相反,任何时候你想更改任何内容,整个图形用户界面就要重新绘制。即使是光标。以我们进入Excel示例,所有75个工具栏控件和300个单元格都将因为一个闪烁的光标而重新绘制。

    这是ImGUI的最坏情况。大量的CPU被浪费了。

    再拿滚动电子表格作个对比。

    在基于场景图的保留模式的图形用户界面中,假设您按下page down键,很可能300个单元格控件会被删除,300个新的单元格控件会被创建,每个单元格的数据将被复制到每个单元格控件中。从所有这些来看,GUI系统将遍历所有300个单元并将它们绘制出来。

    相反,在ImGUI的情况下,不会删除任何旧控件,不会创建任何新控件,也不复制任何数据, 300个单元格要像先前一样绘制出来。在这种情况下,ImGUI为更新整个显示页面所需要的CPU工作量仅仅是保留模式GUI系统工作量的十分之一至百分之一。

    哪种情况更常见呢?对于一个文本编辑器来说,通常只有很小的变化,所以场景图式的GUI会获胜。但是对于Instagram或Facebook应用程序,人们几乎经常滚动页面,在这种情况下,ImGUI以压倒性优势获胜。

  • 可访问性问题

    使用保留模式GUI,所有控件的数据都已复制到GUI的场景图中。这意味着GUI系统本身可以查看这些数据并提供不同的接口(比如放大,说出它,变成盲文,等等)。

    而使用ImGUI的情况下,通常GUI不保留任何数据,所以它可能做不了保留模式GUI能够做的那些事情。

    这可能是一个值得研究的地方。可能存在一些方案可以使ImGUI能够比传统方法更好地处理可访问性问题。大多数ImGUI用于游戏开发,它针对的对象是同一团队中的游戏开发人员,而不是最终用户。也就是说,没有动力去推动对这些解决方案的探索。

下面两点感觉是缺点,但可能不是:

  • 样式

    我不太确定大多数保留模式GUI是如何支持换肤的。可能是使用最具样式风格的,且包含了所有1000多个CSS选项的HTML DOM。

    对于ImGUI来说,样式是由你来设计的。添加更多的样式选项,甚至是几乎所有的CSS或者至少是好的那部分CSS,可能是相对容易实现的,而且能保持好的性能。更好的地方在于:你可以很容易选择需要这些样式或者不需要这些样式。所以如果你的应用程序不需要这样的样式,为什么要浪费内存或CPU时间来处理它呢?为什么要像大多数保留模式GUI那样,不管你使用如否,都要将所有的样式数据嵌入到每个控件中呢?考虑一下HTML,如果每个元素都有100个样式设置(毫不夸张确实有100个设置),那是一件多么可怕的事。

  • 动画

    大多数ImGUI都是无状态的,所以所有的动画都取决于应用程序。虽然很容易想到使用存有少量动画状态数据的包装器(wrapper)可以很容易地将UI动画放回。但是事实上,包装器可以让你选择只在重要的地方支持动画,比如样式。大多数保留模式的GUI都保存有大量的数据、状态和每个控件的设置,无论你使用如否。

对这点我真的很好奇。我知道大多数GUI框架作者都怀疑ImGUI是一个好的模式。而且据我所知,没有人真的尝试过它。如前所述,大多数的ImGUI用于游戏开发。而要想找到合适的模式来完全复制像苹果的UIKit这样的精致奢华的东西,需要各方的共同努力。这能做到并保持好的性能吗?把所有功能/特性添加回去会让它失去性能优势吗?ImGUI的基本设计是否意味着它最终将保持性能和易用性?如果没有场景图式的GUI,我们是否会发现某些功能/特性不可能真正地实现?

我还要补充一点,在某种程度上,React在使用上类似于ImGUI。React有JSX,但它只是函数调用的简化。最大的区别在于:

  • 不需要渲染器,因为每个组件都会立即渲染。。

  • 不需要隐藏的虚拟DOM。

  • 不需要设置状态,因为它是无状态的。

  • 不需要附加事件或处理像componentWillMount (组件将要加载), componentDidMount(组件加载), componentWillUnmount(组件将卸载)等事件,因为没有组件,只有函数,也没有控件(DOM元素、本地对象等)。

如果我们把上面的代码翻译成假想的ImReact语言,它看上去可能像下面这样:

const Button = (props) => {  return ImGUI:Button(props.caption);};const SliderFloat = (props) => {  return ImGUI:SliderFloat(props.caption, props.value, props.min, props.max);};const Form = (props) => {  if (<Button caption="Click Me">) {    DoSomething();  }  <SliderFloat min="0" max="100" value="&props.speed" caption="Speed:" />};return ImGUI:Button(props.caption);
};const SliderFloat = (props) => {return ImGUI:SliderFloat(props.caption, props.value, props.min, props.max);
};const Form = (props) => {if (<Button caption="Click Me">) {DoSomething();}<SliderFloat min="0" max="100" value="&props.speed" caption="Speed:" />
};

只要看一下React的代码,你会发现把它转换成真实的代码是非常简单的。

我不是非常确定对speed参数的更新是如何工作的,但我猜是将C++(ImGUI)和JavaScript(React)混合一起的作用。典型的ImGUI要么具有一种Javascript所不具有的模式,它能够传递进来一个指向原始值的指针,要么返回新值(代码如下):

newValue = ImGUI::SliderFloat(caption, currentValue, min, max);

如果你想使用和你编写的Dear IMGUI C++示例相同的用法,那么代码如下:

someInstance.speed = ImGUI::SliderFloat("Speed:", someInstance.speed, 0.0f, 100.0f);0.0f, 100.0f);

所以,如果我们假设了API的样式,那么代码可能是这样的:

const Button = (props) => {  return ImGUI:Button(props.caption);};const SliderFloat = (props) => {  return ImGUI:SliderFloat(props.caption, props.value, props.min, props.max);};const Form = (props) => {  if (<Button caption="Click Me">) {    DoSomething();  }  props.speed = (<SliderFloat min="0" max="100" value="{props.speed}" caption="Speed:" />);};return ImGUI:Button(props.caption);
};const SliderFloat = (props) => {return ImGUI:SliderFloat(props.caption, props.value, props.min, props.max);
};const Form = (props) => {if (<Button caption="Click Me">) {DoSomething();}props.speed = (<SliderFloat min="0" max="100" value="{props.speed}" caption="Speed:" />);
};

注意到上面的组件没有返回虚拟DOM节点,因为不需要。我们唯一真正要做的是JSX,它只是为了表明,如果你愿意,你可以使用React样式模式。

注意:在这个例子中,不要陷入直接的状态操作中。如何更新状态不应由UI库决定。不管你使用哪个GUI系统,你都可以自由地管理状态。这个例子显示了ImGUI样式是多么地简单。

state.value = ImGUI:SliderFloat(caption, value, min, max);value, min, max);

肯定比下面的保留模式GUI的实现要简单得多。

// at init timeconst slider = new SliderWidget(caption, state.value, min, max);slider.onChange = function(newValue) {  state.value = newValue;}// if state.value changed slider needs to show the new valuefunction updateSlider(newValue) {  state.value = newValue;}
const slider = new SliderWidget(caption, state.value, min, max);
slider.onChange = function(newValue) {state.value = newValue;
}// if state.value changed slider needs to show the new value
function updateSlider(newValue) {state.value = newValue;
}

更糟糕的是,现在你需要以某种方式调用updateSlider函数,或者在每一处state.value被更新的地方调用它,或者你编写一个复杂的系统,以便所有需要更新state.value的地方都调用一个跟踪所有控件及其状态的函数。

ImGUI库不需要如此复杂。它不需要处理控件。无论状态中的值是什么,每个帧都是控件的内容。这与React的承诺相同。但是,React使用的保留模式GUI库性能很差,它最终被这一点所拖累。

作为ImGUI可以实现复杂UI的一个例子,就是下面这个内容丰富的Unity编辑器界面。

这个例子表明,ImGUI在面向用户的应用程序中的使用也可以被优先考虑,而不仅仅只用在游戏应用中,即便Unity本身就是一个游戏制作软件。

这里的Readme文件(https://github.com/ocornut/imgui#gallery)中也包含各式各样的使用ImGUI制作的UI截图。

这里是Dear ImGUI库中包含的示例的在线版本:https://greggman.github.io/doodles/glfw-imgui/out/glfw-imgui.html。

如果你想试着玩一下这个示例,请注意,它实际上不是为浏览器设计的,因此存在一些需要修复的问题。这些问题很容易解决,所以不要陷入吹毛求疵的小问题中。相反,你应该注意到它的UI非常复杂,但它能够以60帧的高速运行。使用主窗口中的“示例”菜单并打开更多窗口。展开主窗口中的示例,查看各种活动和复杂的控件。现在假想一下你试图使用HTML/DOM/React来制作同样复杂的UI。你会发现,不仅HTML/DOM版本会有很多卡顿,可能不可以60帧速度运行,而且实际实现的代码可能是多个维度代码的5到10倍。一个维度是使用HTML/DOM和/或React(vs. ImGUI)实现UI需要编写的代码量。另一个维度是要在屏幕上获得UI需要执行的代码量。我怀疑在HTML/DOM版本中执行的CPU指令量比ImGUI版本高出100倍。

比较一下ImGUI::Button函数和生成<button>元素。

对于<button>元素来说:

1. 需要创建HTMLButtonElement对象

这个对象具有下面所有这些需要设置为某种值的属性

autofocus: boolean  disabled: boolean  form: object  formAction: string  formEnctype: string  formMethod: string  formNoValidate: boolean  formTarget: string  name: string  type: string  value: string  willValidate: boolean  validity: object ValidityState validationMessage: string  labels: object NodeList title: string  lang: string  translate: boolean  dir: string  dataset: object DOMStringMap hidden: boolean  tabIndex: number  accessKey: string  draggable: boolean  spellcheck: boolean  autocapitalize: string  contentEditable: string  isContentEditable: boolean  inputMode: string  offsetParent: object  offsetTop: number  offsetLeft: number  offsetWidth: number  offsetHeight: number  style: object CSSStyleDeclaration namespaceURI: string  localName: string  tagName: string  id: string  classList: object DOMTokenList attributes: object NamedNodeMap scrollTop: number  scrollLeft: number  scrollWidth: number  scrollHeight: number  clientTop: number  clientLeft: number  clientWidth: number  clientHeight: number  attributeStyleMap: object StylePropertyMap previousElementSibling: object  nextElementSibling: object  children: object HTMLCollection firstElementChild: object  lastElementChild: object  childElementCount: number  nodeType: number  nodeName: string  baseURI: string  isConnected: boolean  ownerDocument: object HTMLDocument parentNode: object  parentElement: object  childNodes: object NodeList firstChild: object  lastChild: object  previousSibling: object  nextSibling: object  nodeValue: object  textContent: string disabled: boolean form: object formAction: string formEnctype: string formMethod: string formNoValidate: boolean formTarget: string name: string type: string value: string willValidate: boolean validity: object ValidityStatevalidationMessage: string labels: object NodeListtitle: string lang: string translate: boolean dir: string dataset: object DOMStringMaphidden: boolean tabIndex: number accessKey: string draggable: boolean spellcheck: boolean autocapitalize: string contentEditable: string isContentEditable: boolean inputMode: string offsetParent: object offsetTop: number offsetLeft: number offsetWidth: number offsetHeight: number style: object CSSStyleDeclarationnamespaceURI: string localName: string tagName: string id: string classList: object DOMTokenListattributes: object NamedNodeMapscrollTop: number scrollLeft: number scrollWidth: number scrollHeight: number clientTop: number clientLeft: number clientWidth: number clientHeight: number attributeStyleMap: object StylePropertyMappreviousElementSibling: object nextElementSibling: object children: object HTMLCollectionfirstElementChild: object lastElementChild: object childElementCount: number nodeType: number nodeName: string baseURI: string isConnected: boolean ownerDocument: object HTMLDocumentparentNode: object parentElement: object childNodes: object NodeListfirstChild: object lastChild: object previousSibling: object nextSibling: object nodeValue: object textContent: string 

2. 更多的对象需要创建出来。

从上面的属性列表我们可以看出,我们还需要创建下列对象:

NodeList            // an empty list of children of this buttonHTMLCollection      // another empty list of children of this buttonStylePropertyMap    //NameNodeMap         // the attributesDOMTokenList        // the CSS classes as a listCSSStyleDeclaration // an object used to deal with CSSDOMStringMap        // empty but used for dataset attributesValidityState       // ?? no idea
HTMLCollection      // another empty list of children of this button
StylePropertyMap    //
NameNodeMap         // the attributes
DOMTokenList        // the CSS classes as a list
CSSStyleDeclaration // an object used to deal with CSS
DOMStringMap        // empty but used for dataset attributes
ValidityState       // ?? no idea

到目前为止,这只是创建对象的时间。这里的许多属性需要设置默认值,用空字符串来填充,或者用其它需要创建的对象来填充,这些创建的对象也需要填充所有属性,并且可能还需要创建更深层次的对象。

好了,既然HTMLButtonElememt这个对象已经创建出来,它就被插入到DOM中。

在渲染时,浏览器将遍历DOM,我确信有一定数量的缓存,但它需要确定按钮在哪里。它可能会构建一些独立的内部场景图,与DOM本身分离,后者将执行1000多行代码来渲染特定的特性。

最终它将到达渲染按钮的地方。在这里,它必须再次检查100多个CSS属性。比如Text color,Font size,Font Family,Text Shadow,Transform,Animation, Border,Multiple Borders,Background color,Background Image,Background gradient,Is it transparent,Is it on its own stacking context,等等属性。毫不夸张有100多个属性。

假设没有使用任何特殊的特性的话,最终它会生成一些四顶点(quad vertices)来渲染字体字形。它可能会将这些字形渲染为纹理(texture)或纹理网格,以用于堆叠上下文(stacking context)。这是一种优化,因此理想情况下,如果不同的堆叠上下文的内容发生了更改,但此堆叠上下文中没有任何更改,则可以跳过为此上下文重新渲染纹理的过程,而只使用上次创建的纹理。

我确信还有100个与缓存位置相关的其他步骤被我遗漏了,比如将一些事情标记为已经计算过的,这样它们就不会被重新计算,如此等等。

对比一下ImGUI:Button,它只是一个函数,而不是一个对象。它的作用就是:

  1. 将按钮矩形剪裁到当前剪裁空间,如果剪裁完成,则退出

  2. 将按钮矩形的顶点插入到预先分配的顶点数组中

  3. 当第一个字形被按钮区域剪裁时,插入每个字形位置的顶点。

  4. 按下鼠标按钮,如果其位置在按钮矩形内,则返回true,否则为false。

这就是它全部做的事情。

请注意,这4个步骤也存在于支持HTML/DOM的浏览器中。只不过它们是100个步骤中的4个而已。

总的来说,ImGUI样式可能更快,也更容易使用。无论对于简单的情况或者复杂的情况下,它都易于使用。它的API也更易于使用,这很容易解释。它没有状态,没有对象。没有数据编制,没有事件或回调。即使当UI变得复杂时,它的速度还可以如此之快,所以不需要创建像React的虚拟DOM这样的大型框架。因为速度很快,所以几乎不需要花费精力寻找变通方法来解决像DOM这样的缓慢性问题。对ImGUI风格的UI实现投入更多的精力进行研究,可以促进生产力的巨大提高。

原文:https://games.greggman.com/game/imgui-future/

本文为 CSDN 翻译,转载请注明来源出处。

牛了,这几个案例让你迅速掌握AI技术!

https://edu.csdn.net/topic/ai30?utm_source=csdn_bw

【End】

作为码一代,想教码二代却无从下手:

听说少儿编程很火,可它有哪些好处呢?

孩子多大开始学习比较好呢?又该如何学习呢?

最新的编程教育政策又有哪些呢?

下面给大家介绍CSDN新成员:极客宝宝(ID:geek_baby)

戳他了解更多↓↓↓

 热 文 推 荐 

不得了!这个 AI 让企业家、技术人员、投资人同台“互怼”

史上第一代图形浏览器往事

5G NR 标准:下一代无线通信技术

如何使用 Firefox 阻止指纹识别的侵扰?

☞谷歌临时工达 12 万,外包程序员的出路在哪里?

☞直接拿来用!灵跃模组机器人硬核评测(编程篇)

☞IEEE 回应禁止华为系审稿人;WiFi联盟、蓝牙联盟已恢复华为成员资格;中国计算机学会:暂时中止与IEEE通信学会合作……

☞敲诈团伙将黑手伸向宅男, 你在家看不可描述的视频, 竟被骗走100万美元!

☞各方最新回应!如何看待IEEE官方声明“学术禁令”?

☞代码整洁之道-编写 Pythonic 代码

☞敲代码时,程序员戴耳机究竟在听什么?

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

@游戏开发者,ImGUI 能成为 GUI 的未来吗?相关推荐

  1. 为什么说Android才是游戏开发者的乐土?

    游戏向来是硬软件性能.多样输入法以及图形支持等技术发展的强劲推动力.以安卓平台为例--在可预见的未来,相比其他应用,游戏及其玩家更能促进市场发展并带动游戏设备升级.尽管安卓平台已相当成熟,但发展的步伐 ...

  2. 为什么游戏开发者不玩游戏_什么是游戏开发?

    为什么游戏开发者不玩游戏 Game Development is the art of creating games and describes the design, development and ...

  3. 你是“细”精你就赢了 游戏开发者怎样做好玩家细分

    玩家的偏好就像女朋友的脸一样,说变就变.开发者要老玩家.新玩家两手抓,怠慢哪边都不行.吸引玩家是一个老生常谈的话题,比如要打磨引人入胜的玩家体验.研究玩家生命周期,不断更新新功能等--你一定耳朵都听得 ...

  4. 2018 腾讯功能游戏开发者赛事火热开启

    9月6日,2018腾讯功能游戏开发者大赛正式开启,本次赛事是由腾讯功能游戏举办,旨在为国内优秀功能游戏作品提供发展平台,激活游戏研发从业者关注功能游戏领域.据了解,此次大赛为国内开发者提供与海外功能游 ...

  5. 一年突破3亿游戏安装量 小米游戏双发行模式助力游戏开发者

    2020年,全球市场迎来了颠覆式的新格局,挑战与机遇并存.一方面,疫情导致大量消费者居家隔离,线上业务迎来了爆发式增长;另一方面,文化冲突.数据隐私.地缘关系等因素对海外业务都增加了更多不确定性.对于 ...

  6. 游戏+AI,你不曾想象的未来

    AI,Artificial Intelligence,人工智能. 这词或许过去陌生,但现在几乎妇孺皆知.不管你是不是真的能拼对这个单词,是不是真的能理解其中的含义,都不重要. AlphaGo打败围棋世 ...

  7. 2020 Ohayoo游戏开发者沙龙成都站将于10月22日正式启动

    2020 Ohayoo游戏开发者沙龙成都站将于10月22日正式启动 10月22日起,Ohayoo将在成都.厦门.广州.北京.上海等多个城市举办"2020 Ohayoo游戏开发者沙龙" ...

  8. Unity3D和UE4游戏开发引擎哪个是游戏开发者最爱?

    UE4和Unity3D应该可以算的上是目前市场上最为热门的游戏引擎,也各自拥有为数众多的开发者.虽然一些大型的游戏公司仍然运用自家开发的引擎来制作游戏,但是在巨大的市场需求下,独立开发者与游戏工作室依 ...

  9. Android才是游戏开发者的乐土

    摘要:手机游戏开发之所以能在这产业急流中顶住风吹浪打,有两个原因:一是,庞大而复杂的用户群体:另一方面,现成的移动开发工具和服务比以往更为丰富.本文将细数游戏开发中那些实用的SDK.引擎.处理器和第三 ...

最新文章

  1. 普通人也能用AI拍出3D大片?这位清华博士后这么做
  2. Nature:Gordon组采用甘露糖苷选择性抑制尿路致病性大肠杆菌
  3. python编程语言是什么-编程语言分类及python所属类型
  4. java程序的运行结果依赖操作系统吗_java模拟试卷及答案及解析4
  5. 二、WPF datagrid 特定行变色
  6. HBase Shell基本操作
  7. ios 自动打包命令_【实践】iOS使用Jenkins实现自动化打包并上传蒲公英
  8. Object/DataSet Relational Mapping(对象/数据集关系映射)完整版本下载
  9. GenericUDAF使用流程记载(转载+自己整理)
  10. windows azure虚拟机创建——快速创建,库模板创建
  11. 字符串查找KMP算法(转)
  12. java加载配置文件
  13. 看到这一切,我忍俊不禁
  14. js检测是否安装java_js判断当前浏览器是否是源生app的webview
  15. DBA最缺的不是技术
  16. java 点云数据处理_点云数据处理学习笔记
  17. cad画直线长度与实际不符_cad画规定长度直线的方法步骤图
  18. [iOS]苹果开发证书 一个证书多人开发 注意项
  19. 高中数学基础-对数2.2.2对数函数图象及其性质(上)
  20. kernel编译练习2:给ubuntu22升级5.19内核

热门文章

  1. [论文阅读] Variational Adversarial Active Learning
  2. 记录——《C Primer Plus (第五版)》第十一章编程练习第二题
  3. [SQL实战]之查找当前薪水排名第二多的员工编号emp_no、薪水salary、last_name以及first_name,不准使用order by
  4. 2021年中国电子陶瓷市场趋势报告、技术动态创新及2027年市场预测
  5. 《Python游戏趣味编程》 第2章 弹跳的小球
  6. idea怎么设置选中文件时,自动在左侧弹出文件所在位置及文件
  7. dmb: 数据库监控及灾备系统(for mysql)_MySQL企业级数据库灾备(备份)系统-DMB v2.1发布...
  8. 聊聊代码质量 - 《学得会,抄得走的提升前端代码质量方法》前言
  9. 容器混合云发展引争议,专家亚马逊云科技中国峰会共探讨
  10. 联手三年,获取数千名客户,阿里云如何重构 Elastic 开放免费的技术?