目录

  • 摘要
  • 系列文章
  • 正式开始
    • 准备礼物
    • 怎么送
    • 送礼物
    • seval_to_Gift
    • 最终效果
    • 在 Android 平台测试
  • 划重点

摘要

承接上文 Cocos Creator JSB [Lv.1] (2)
在上文中,我们已经跟小姐姐进行了基本的互动。可是光说不练怎么行,本文中我们将要给小姐姐送礼物~

系列文章

  • Cocos Creator JSB [Lv.1] (1)
  • Cocos Creator JSB [Lv.1] (2)
  • Cocos Creator JSB [Lv.1] (3)

正式开始

准备礼物

我们在C++层面创建一个礼物的类。
myjsb目录下创建Gift.hGift.cpp
Gift.h

#ifndef __CC_GIFT_H__
#define __CC_GIFT_H__#include <string>
#include "base/ccMacros.h"namespace cocos2d { namespace myjsb {       // 命名空间,cocos2d::myjsbclass CC_DLL Gift      // 普通的带有构造函数的类。{public: Gift();     // 无参构造函数。Gift(const std::string& name);    // 带1个参数的构造函数。virtual ~Gift();  // 析构函数。// 对应成员变量的 set 和 get 方法。inline const std::string& getName() const { return _name; };inline void setName(std::string& name) { _name = name; };protected:       std::string _name;   // 礼物的名称,会在构造函数中赋初始值。};}}  // namespace cocos2d::myjsb#endif    // __CC_GIFT_H__

Gift.cpp

#include "myjsb/Gift.h"namespace cocos2d { namespace myjsb {       // 命名空间,cocos2d::myjsb// 无参构造函数。Gift::Gift(): _name(""){// nothing.}// 带1个参数的构造函数。Gift::Gift(const std::string& name): _name(""){_name = name;  // 初始化礼物名称。}// 析构函数。Gift::~Gift(){// nothing.}}}  //  namespace cocos2d::myjsb

将这两个文件加入libcocos2d中,从而让它们编译进libcocos2d库中,

jsb_cocos2dx_myjsb_auto.hpp

...
extern se::Object* __jsb_cocos2d_myjsb_Gift_proto;
extern se::Class* __jsb_cocos2d_myjsb_Gift_class;bool js_register_cocos2d_myjsb_Gift(se::Object* obj);
bool register_all_myjsb(se::Object* obj);
// 析构函数的桥梁,构造函数的桥梁不用在这里声明。
SE_DECLARE_FINALIZE_FUNC(js_cocos2d_myjsb_Gift_finalize);
// 声明作为桥梁的函数。
// 桥梁函数的作用:
// 1、将js层面传递过来的参数从se::Value类型转换为C++的类型。
// 2、调用对应的(在js_register_xxx中绑定的)C++类的成员函数,并传递转换后的的参数。
// 3、得到返回值,并将其转换为se::Value类型。
// 4、存入s.rval中,s.rval中的值就是返回给js层面的返回值。
SE_DECLARE_FUNC(js_cocos2d_myjsb_Gift_getName);
SE_DECLARE_FUNC(js_cocos2d_myjsb_Gift_setName);
...

jsb_cocos2dx_myjsb_auto.cpp

...
#include "myjsb/Gift.h"
...
se::Object* __jsb_cocos2d_myjsb_Gift_proto = nullptr;
se::Class* __jsb_cocos2d_myjsb_Gift_class = nullptr;// 桥梁函数,new jsb.Gift 实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_constructor(se::State& s) {const auto& args = s.args();  // 传递的参数。size_t argc = args.size();        // 参数的数量。CC_UNUSED bool ok = true;     // 参数转换是否成功的标志。do {if (argc == 0) {// 调用不带参数的构造函数。cocos2d::myjsb::Gift* cobj = new (std::nothrow) cocos2d::myjsb::Gift();// 将 C++ 类的实例保存在 se::State 中,之后可以通过 s.nativeThisObject() 获取。s.thisObject()->setPrivateData(cobj);se::NonRefNativePtrCreatedByCtorMap::emplace(cobj);return true;} else if (argc == 1) {std::string name;// 传递过来的昵称是 se::Value 的形式,将其转换为 std::string 。ok &= seval_to_std_string(args[0], &name);SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Gift_constructor : Error processing new value");// 调用带1个参数的构造函数。cocos2d::myjsb::Gift* cobj = new (std::nothrow) cocos2d::myjsb::Gift(name);// 将 C++ 类的实例保存在 se::State 中,之后可以通过 s.nativeThisObject() 获取。s.thisObject()->setPrivateData(cobj);se::NonRefNativePtrCreatedByCtorMap::emplace(cobj);return true;}} while(false);// 传递的参数不正确时,打印的提示信息。SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d or %d", (int)argc, 0, 1);return false;
}
SE_BIND_CTOR(js_cocos2d_myjsb_Gift_constructor, __jsb_cocos2d_myjsb_Gift_class, js_cocos2d_myjsb_Gift_finalize)// 桥梁函数,js对象被回收时,实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_finalize(se::State& s) {CCLOGINFO("jsbindings: finalizing JS object %p (cocos2d::myjsb::Gift)", s.nativeThisObject());auto iter = se::NonRefNativePtrCreatedByCtorMap::find(s.nativeThisObject());if (iter != se::NonRefNativePtrCreatedByCtorMap::end()){se::NonRefNativePtrCreatedByCtorMap::erase(iter);// s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。cocos2d::myjsb::Gift* cobj = (cocos2d::myjsb::Gift*)s.nativeThisObject();// 删除 C++ 类的实例,从而触发类的析构函数。delete cobj;}return true;
}
SE_BIND_FINALIZE_FUNC(js_cocos2d_myjsb_Gift_finalize)// 桥梁函数,let foo = gift.name 时,实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_getName(se::State& s) {// s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。cocos2d::myjsb::Gift* cobj = (cocos2d::myjsb::Gift*)s.nativeThisObject();SE_PRECONDITION2(cobj, false, "js_cocos2d_myjsb_Gift_getName : Invalid Native Object");const auto& args = s.args();   // 传递的参数。size_t argc = args.size();        // 参数的数量。CC_UNUSED bool ok = true;     // 参数转换是否成功的标志。if (argc == 0) {   // 需要0个参数。// 不需要参数,也就没有了参数的转换部分。std::string result = cobj->getName();    // 调用类的成员函数,并得到返回值。// 将返回值转换为 se::Value 类型,并赋值到s.rval,s.rval中的值就是返回给js层面的返回值。ok &= std_string_to_seval(result, &s.rval());SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Gift_getName : Error processing arguments");return true;}// 传递的参数不正确时,打印的提示信息。SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 0);return false;
}
SE_BIND_PROP_GET(js_cocos2d_myjsb_Gift_getName)// 桥梁函数,gift.name = "foo" 时,实际会调用到这里。
static bool js_cocos2d_myjsb_Gift_setName(se::State& s) {// s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。cocos2d::myjsb::Gift* cobj = (cocos2d::myjsb::Gift*)s.nativeThisObject();SE_PRECONDITION2(cobj, false, "js_cocos2d_myjsb_Gift_setName : Invalid Native Object");const auto& args = s.args();   // 传递的参数。size_t argc = args.size();        // 参数的数量。CC_UNUSED bool ok = true;     // 参数转换是否成功的标志。if (argc == 1) {   // 需要1个参数。std::string name;// 传递过来的昵称是 se::Value 的形式,将其转换为 std::string 。ok &= seval_to_std_string(args[0], &name);SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Gift_setName : Error processing new value");cobj->setName(name);   // 调用类的成员函数,并传递转换后的参数。// 因为此成员函数没有返回值,所以不用对 s.rval 赋值。return true;}// 传递的参数不正确时,打印的提示信息。SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);return true;
}
SE_BIND_PROP_SET(js_cocos2d_myjsb_Gift_setName)bool js_register_cocos2d_myjsb_Gift(se::Object* obj)
{auto cls = se::Class::create("Gift", obj, nullptr, _SE(js_cocos2d_myjsb_Gift_constructor));// 这里就是定义,jsb.Gift要暴露出一个name属性,// let foo = gift.name时,在JSB中要调用 js_cocos2d_myjsb_Gift_getName// gift.name= "foo" 时,在JSB中要调用 js_cocos2d_myjsb_Gift_setNamecls->defineProperty("name", _SE(js_cocos2d_myjsb_Gift_getName), _SE(js_cocos2d_myjsb_Gift_setName));// 这里定义js对象被垃圾回收时,需要调用js_cocos2d_myjsb_Gift_finalize// js_cocos2d_myjsb_Gift_finalize中做一些收尾工作,并delete掉C++类的实例,从而触发类的析构函数。// 构造函数的桥梁js_cocos2d_myjsb_Gift_constructor不需要在这里定义。cls->defineFinalizeFunction(_SE(js_cocos2d_myjsb_Gift_finalize));cls->install();// 将 se::Class::create 的 Gift 与 C++ 实际的 Gift 类对应起来。JSBClassType::registerClass<cocos2d::myjsb::Gift>(cls);__jsb_cocos2d_myjsb_Gift_proto = cls->getProto();__jsb_cocos2d_myjsb_Gift_class = cls;se::ScriptEngine::getInstance()->clearException();return true;
}bool register_all_myjsb(se::Object* obj)
{...// 以下函数被调用之后,jsb.Gift就可以使用了。js_register_cocos2d_myjsb_Gift(ns);...
}
...

这样我们的礼物就准备好了,可以在js脚本中按照如下方式使用,

let gift = new jsb.Gift()
gift.name = "爱马仕"let another_gift = new jsb.Gift("兰博基尼")

怎么送

送给小姐姐的礼物要精心包装,还要知道小姐姐喜不喜欢。
所以我们在送礼物的接口,传递一个礼物的object,以及一个回调函数,让小姐姐在回调函数中,告诉我们她喜不喜欢这个礼物。

送礼物

创建一个输入框,用于输入礼物的名字。
创建一个按钮,用于送礼物。
创建一个心形图片以及一个文本框,用于显示小姐姐对我们的心动值~

JSBController.js中实现功能。

...
let _heartValue = 0        // 心动值。cc.Class({...properties: {...giftEditBox: cc.EditBox,    // 输入框,用于输入礼物名称。heartValue: cc.Label,        // 文本框,用于显示心动值。}, onLoad () {...// 初始化心动值并显示。_heartValue = 0this.heartValue.string = _heartValue}, ...sendGift () {let gift_name = this.giftEditBox.string        // 获取输入的礼物名称。let gift = new jsb.Gift(gift_name)        // 创建一个礼物。// 小姐姐收礼物,传递一个礼物object,并且传递一个回调函数。// msg: 小姐姐收到礼物后对我们说的话。// val: 小姐姐收到礼物后,心动值的变化。_goddess.receiveGift(gift, (msg, val) => {// 把小姐姐说的话以及心动值的变化,格式化后,显示出来。this.txt.string = msg + '(' + val + ')'// 更新心动值。_heartValue += valthis.heartValue.string = _heartValue})},
});

将节点挂载到脚本对应变量上。

指定按钮点击后的回调函数。

接下来,要在C++层面实现相应的函数。
Goddess.h

...
#include <functional>
...
#include "myjsb/Gift.h"namespace cocos2d { namespace myjsb {      // 命名空间,cocos2d::myjsbclass CC_DLL Goddess {public: // 定义收礼物接口的回调函数。// msg: 小姐姐收到礼物后对我们说的话。// val: 小姐姐收到礼物后,心动值的变化。typedef std::function<void(const std::string& msg, const int val)> ResultCallback;...void receiveGift(Gift& gift, const ResultCallback& callback);   // 收礼物。protected: ...};}}  // namespace cocos2d::myjsb

Goddess.cpp

...
#include <cstdlib>
#include <ctime>namespace cocos2d { namespace myjsb {     // 命名空间,cocos2d::myjsb...// 简单增加了随机性,增加了一点点趣味性。void Goddess::receiveGift(Gift& gift, const ResultCallback& callback) {// 小姐姐有可能对我们说的话。std::string txts[] = {"What is this?", "Just so so.", "That's great!"};std::string s = gift.getName();     // 获取礼物名称。int k = 0;       // 用于 txts 的索引。int v = 0;      // 心动值变化。if (s.empty()) {   // 如果没写礼物名字。s += txts[0];v = -10;} else {    // 如果写了礼物名字。srand((int)time(0));  // 产生随机种子  把0换成NULL也行// k是整数且,1 <= k <= 2k = (rand() % ((sizeof(txts) / sizeof(txts[0])) - 1)) + 1;// v是整数且,1 <= v <= 9v = ((rand() % 10) + 1);if (1 == k) {  // 如果是 "Just so so." (txts[1])v *= -1;   // 心动值变化就是负数。}s = s + ", " + txts[k];      // 简单组织要说的话。}// 调用回调函数。if (callback) {callback(s, v);}}}}  //  namespace cocos2d::myjsb

jsb_cocos2dx_myjsb_auto.hpp

...
// 声明作为桥梁的函数。
// 桥梁函数的作用:
// 1、将js层面传递过来的参数从se::Value类型转换为C++的类型。
// 2、调用对应的(在js_register_xxx中绑定的)C++类的成员函数,并传递转换后的的参数。
// 3、得到返回值,并将其转换为se::Value类型。
// 4、存入s.rval中,s.rval中的值就是返回给js层面的返回值。
SE_DECLARE_FUNC(js_cocos2d_myjsb_Goddess_receiveGift);
...

jsb_cocos2dx_myjsb_auto.cpp

...
// 桥梁函数,jsb.Goddess.receiveGift() 时,实际会调用到这里。
static bool js_cocos2d_myjsb_Goddess_receiveGift(se::State &s) {// s.nativeThisObject() 可以获取到绑定的 C++ 类的实例。cocos2d::myjsb::Goddess *cobj = (cocos2d::myjsb::Goddess *) s.nativeThisObject();SE_PRECONDITION2(cobj, false, "js_cocos2d_myjsb_Goddess_receiveGift : Invalid Native Object");const auto& args = s.args(); // 传递的参数。size_t argc = args.size();        // 参数的数量。CC_UNUSED bool ok = true;     // 参数转换是否成功的标志。do {if (argc == 2) {   // 需要2个参数。cocos2d::myjsb::Gift gift;// 传递过来的礼物是 se::Value 的形式,将其转换为 Gift 类的实例 。// 由于 Gift 类是我们自创建的类,// cocos2dx 引擎中没有将 se::Value 转换为 Gift 类的实例的代码,需要我们在之后自己编写。ok &= seval_to_Gift(args[0], &gift);// C++ 无法直接调用 js 传递过来的回调函数,所以这里定义 C++ 层面的回调函数,// 参数的类型以及数量与 js 的回调函数保持一致。// Goddess::receiveGift中调用的回调函数,实际上就是这个callback。// 下面的工作实际上是让这个 callback 保存一个 lambda 表达式定义的函数。std::function<void(const std::string& msg, const int val)> callback;// args[1] 是 js 传递过来的 callback。if (args[1].isObject() && args[1].toObject()->isFunction()) {se::Value jsThis(s.thisObject());se::Value jsFunc(args[1]);// 如果目标类是一个单例则不能用 se::Object::attachObject 去关联// 必须使用 se::Object::root,无需关心 unroot,unroot 的操作会随着 lambda 的销毁触发 jsFunc 的析构,在 se::Object 的析构函数中进行 unroot 操作。// 如果使用 s.thisObject->attachObject(jsFunc.toObject);会导致对应的 func 和 target 永远无法被释放,引发内存泄露。jsFunc.toObject()->root(); // lambda 中所做的工作:// 1、将 Goddess::receiveGift 中调用回调函数所传递过来的 msg 和 val 转换为 se::Value 。// 2、组装转换后的参数为 args 。// 3、通过 js 的对象,调用 js 指定的回调函数,并传递参数 args 。auto lambda = [=](const std::string& msg,const int val) -> void {se::ScriptEngine::getInstance()->clearException();se::AutoHandleScope hs;CC_UNUSED bool ok = true;se::ValueArray args;   // 提供给 js 回调函数的参数。args.resize(2);       // 指定参数个数。// msg 是 std::string ,将其转换为 se::Value 。ok &= std_string_to_seval(msg, &args[0]);// val 是 int ,将其转换为 se::Value 。ok &= int32_to_seval(val, &args[1]);se::Value rval;        // js 回调函数的返回值。se::Object* thisObj = jsThis.isObject() ? jsThis.toObject() : nullptr;se::Object* funcObj = jsFunc.toObject();// 执行 js 回调函数。bool succeed = funcObj->call(args, thisObj, &rval);if (!succeed) {se::ScriptEngine::getInstance()->clearException();}};// Goddess::receiveGift 中执行的 callback 实际上是 lambda 中编写的代码。callback = lambda;} else {callback = nullptr;}SE_PRECONDITION2(ok, false, "js_cocos2d_myjsb_Goddess_receiveGift: Error processing arguments");cobj->receiveGift(gift, callback); // 调用类的成员函数,并传递转换后的参数。return true;}} while(false);// 传递的参数不正确时,打印的提示信息。SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);return false;
}
SE_BIND_FUNC(js_cocos2d_myjsb_Goddess_receiveGift);bool js_register_cocos2d_myjsb_Goddess(se::Object* obj) {...// 这里就是定义,jsb.Goddess要暴露出一个 receiveGift 函数,// jsb.Goddess.receiveGift() 时,在JSB中要调用 js_cocos2d_myjsb_Goddess_receiveGiftcls->defineFunction("receiveGift", _SE(js_cocos2d_myjsb_Goddess_receiveGift));...
}
...

js_cocos2d_myjsb_Goddess_receiveGift中的逻辑稍微有些复杂,简单总结一下:

  1. js层面调用jsb.Goddess.receiveGift(gift, jscallback)
  2. 传递过来的giftse::Value,需要将其转换成Goddess类的实例。
  3. 传递过来的jscallback不能在C++层面直接调用。
  4. 创建一个c++callback,在调用Goddess::receiveGift函数时,传递这个c++callback
  5. c++callback被调用,拿到msgval,将其均转换为se::Value的形式。
  6. 组装转换后的参数为args
  7. 调用jscallback并传递args

seval_to_Gift

上边有提到,Gift类是我们自创建的类,cocos2dx引擎中没有将se::Value转换为Gift类的实例的代码,所以我们需要自己来编写。
cocos/scripting/js-bindings/manual/jsb_conversions.hpp

...
#include "myjsb/Gift.h"
...
bool seval_to_Gift(const se::Value& v, cocos2d::myjsb::Gift* pt);

cocos/scripting/js-bindings/manual/jsb_conversions.cpp

...
bool seval_to_Gift(const se::Value& v, cocos2d::myjsb::Gift* pt)
{assert(pt != nullptr);SE_PRECONDITION2(v.isObject(), false, "Convert parameter to Gift failed!");se::Object* obj = v.toObject();       // 获取 js 对象。se::Value name;bool ok = obj->getProperty("name", &name);     // 获取 js 对象中的 name 属性。SE_PRECONDITION3(ok && name.isString(), false, *pt = cocos2d::myjsb::Gift::Null);std::string n = name.toString();   // 转换为 std::string 。pt->setName(n);      // 通过 Gift 类的实例的成员方法,设置成员变量的值。return true;
}

上面用到了一个Gift类的默认值Null,需要我们在Gift类中定义。
Gift.h

...
namespace cocos2d { namespace myjsb {class CC_DLL Gift{public: /** A predefined Value that has not value. */static const Gift Null;...protected:       ...};}}  // namespace cocos2d::myjsb
...

Gift.cpp

...
namespace cocos2d { namespace myjsb {const Gift Gift::Null;...
}}  //  namespace cocos2d::myjsb

最终效果

至此,送礼物的功能就实现完成了。赶快构建工程,编译,运行起来,看看最终效果。

哇啊!!!心动值总是负的!讨小姐姐的欢心,好难 o(╥﹏╥)o

在 Android 平台测试

如果你想在Android平台测试,实现的代码都是一样的,但是新增的这几个文件

  • cocos/myjsb/Goddess.h
  • cocos/myjsb/Goddess.cpp
  • cocos/myjsb/Gift.h
  • cocos/myjsb/Gift.cpp
  • cocos/scripting/js-bindings/auto/jsb_cocos2dx_myjsb_auto.hpp
  • cocos/scripting/js-bindings/auto/jsb_cocos2dx_myjsb_auto.cpp

加入libcocos2d的方式不太一样。
Android平台,需要修改发布路径/jsb-default/frameworks/cocos2d-x/cocos/Android.mk

LOCAL_SRC_FILES := \
...
myjsb/Goddess.cpp \
myjsb/Gift.cpp \
scripting/js-bindings/auto/jsb_cocos2dx_myjsb_auto.cpp \
...LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH) \...$(LOCAL_PATH)/myjsb \...

划重点

  • 绑定构造函数和析构函数的写法。
  • js_xxx_constructor不用在jsb_xxx_auto.hpp中声明。
  • 绑定成员函数(有参数,参数传递js object,参数传递回调函数,无返回值)的写法。
  • se::Value转换为自创建类的实例的方式。
  • 在 Android 平台测试,需要注意的点。

Cocos Creator JSB [Lv.1] (3)相关推荐

  1. Cocos Creator JSB [Lv.1] (1)

    目录 摘要 系列文章 JSB能做什么 环境 资源 准备工作 正式开始 对工程做一些修改 如何互动 小姐姐现身 与小姐姐互动的桥梁 找到小姐姐 划重点 摘要 本系列文章主要实操JSB的手动绑定,在官方教 ...

  2. Cocos Creator JSB [Lv.2] TODO

    目录 摘要 环境 资源 并未正式开始 思路 问题 参考 摘要 本文本来是想介绍JSB自动绑定,但在为自动绑定工具搭建运行环境时,发现对运行环境的要求有些苛刻.python,yaml,cheetah都需 ...

  3. Cocos Creator JSB [Lv.3]

    目录 摘要 资源 正式开始 为什么要使用`JSB` `JSB`能做什么 进行`JSB`绑定需要做什么 构建工程 创建类 绑定(搭建桥梁) 实现自定义的类型转换(非必须) 注册 将新增的文件加入`lib ...

  4. java中JSB_深入解析Cocos Creator JSB绑定原理以及应用实践

    背景 一直以来,ABCmouse 项目中的整体 JS/Native 通信调用结构都是基于 callStaticMethod evalString 的方式.通过 callStaticMethod 方法我 ...

  5. Cocos Creator jsb手动绑定C++

    使用的是cocos creator 2.4.3版本:直接贴代码,结构如下: 在CocosDashboard\resources.editors\Creator\2.4.3\resources\coco ...

  6. Cocos Creator JSB绑定

    至于绑定所需要的环境,自己百度,文章最后提供部分环境依赖 D:\CocosCreator 为Cocos Creator安装目录 在D:\CocosCreator\resources\cocos2d-x ...

  7. Cocos Creator JSB绑定 MAC系统环境

    编译环境 python2.7(mac自带) 1.安装PyYAML mac: sudo easy_install pyyaml win: pip install pyyaml 2.安装Cheetah 下 ...

  8. Cocos Creator 2D摄像机 [Lv.1] 小视图

    目录 摘要 环境 资源 准备工作 正式开始 对工程做一些修改 如何互动 上摄像机 缩放(看清黑头和脂肪粒) 动起来 辅助的边框 得有边界 用分组解决个bug 划重点 摘要 本系列文章主要实操2D摄像机 ...

  9. json list格式_来来来,一文让你读懂Cocos Creator如何读写JSON文件

    前言 在游戏开发过程中,读取配置文件是必不可少的,而使用JSON做配置文件又比较常见,本文重点给大家讲述如何在Cocos Creator开发中读取和解析JSON数据文件以及如何写JSON文件. 一.J ...

最新文章

  1. 机器学习-线性回归LinearRegression
  2. python3 xrange *的作用
  3. 从hello server开始,到hello client结束
  4. RabbitMQ—重复消费、数据丢失和消息顺序性
  5. chattterbot配合UbuntuCorpusTrainer使用
  6. php把数字倒着展示,jQuery+PHP实现动态数字展示特效
  7. 什么是CPU密集型、IO密集型?
  8. 【RK3399Pro学习笔记】十四、ROS中tf坐标系广播与监听的编程实现
  9. python安装jupyterlab_超级详细 centos7 安装 jupyter lab
  10. mysql.createPool(db),Node.js中JavaScript操作MySQL的常用方法整理
  11. Jenkins持续集成学习-搭建jenkins问题汇总
  12. CentOS 7 配置免密码证书登录
  13. linux使用anaconda安装python包
  14. Cursor 详解及使用
  15. MCE公司:DDR1 和 DDR2 双靶点抑制剂的设计合成及其抗炎作用研究
  16. 从mysql学起_MySQL学习从这里出发!
  17. 让进度条颜色渐变的shader
  18. 谨赠20篇技术热文营造一个不一样的节日气氛!
  19. android7 显示到pc,安卓手机上的画面怎么投屏到Win7电脑上?超详细投屏方法看这里!...
  20. strcat、strcpy、strcmp三种函数用法

热门文章

  1. HIT-ICS2021大作业-1190200924-田轩
  2. Linux权限控制的基本原理
  3. 721 建模模拟 饲料混合加工问题 (20 五一 C)
  4. NBA球队实力榜:雄鹿升至榜首 勇士紧随其后
  5. 灵活型惠普T5740瘦客户机
  6. 美丽说蘑菇街首页效果(UITableView和UIScrollerView联动)
  7. html5餐厅模拟经营游戏《新的开始》源码
  8. 静态小米官网主页html5设计,web案例-css制作小米官网产品展示
  9. linux shell多进程通信,linux shell控制并发进程数实践 · BG2BKK Site
  10. 知识点记录-abase是什么