FROM:http://www.2cto.com/kf/201303/197171.html

原文内容如下:


首先,我们要知道LUA是个什么东西,至于官方怎么说可以百度去查,但我想告诉你的是LUA就是一种可以在不必修改C++代码的情况下实现逻辑处理的手 段。稍微讲的再明白一点,就是你用指定语法写一些逻辑处理函数然后保存成文本格式,这个文件称为脚本文件,可以被游戏执行。经过若干年的发展,现在在 LUA中写逻辑,除了调用注册到LUA的静态C函数外,也已经可以方便的访问到C++工程中的类的成员函数。这是游戏开发史上最重要的技术之一。其改变了 很多设计方案,使游戏变的灵活强大而极具扩展性。

在Cocos2d-x中,有两个类来完成对于LUA脚本文件的处理。

1. CCLuaEngine:LUA脚本引擎

2. CCScriptEngineManager:脚本引擎管理器。

CCLuaEngine类的基类是一个接口类,叫做CCScriptEngineProtocol,它规定了所有LUA引擎的功能函数,它和 CCScriptEngineManager都存放在libcocos2d下的script_support目录中的 CCScriptSupport.h/cpp中。

首先我们来看一下CCScriptEngineProtocol:

class CC_DLL CCScriptEngineProtocol : public CCObject
{
public:  //取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用。  virtual lua_State* getLuaState(void) = 0;  //通过LUA脚本ID移除对应的CCObject  virtual void removeCCObjectByID(int nLuaID) = 0;  //通过函数索引值移除对应的LUA函数。  virtual void removeLuaHandler(int nHandler) = 0;  //将一个目录中的LUA文件加入到LUA资源容器中。  virtual void addSearchPath(const char* path) = 0;  //执行一段LUA代码  virtual int executeString(const char* codes) = 0;  //执行一个LUA脚本文件。  virtual int executeScriptFile(const char* filename) = 0;  //调用一个全局函数。  virtual int executeGlobalFunction(const char* functionName) = 0;  //通过句柄调用函数多种形态。
//通过句柄调用函数,参数二为参数数量。
virtual int executeFunctionByHandler(int nHandler, int numArgs = 0) = 0;
//通过句柄调用函数,参数二为整数数据。
virtual int executeFunctionWithIntegerData(int nHandler, int data) = 0;
//通过句柄调用函数,参数二为浮点数据。
virtual int executeFunctionWithFloatData(int nHandler, float data) = 0;
//通过句柄调用函数,参数二为布尔型数据。
virtual int executeFunctionWithBooleanData(int nHandler, bool data) = 0;
//通过句柄调用函数,参数二为CCObject指针数据和其类型名称。
virtual int executeFunctionWithCCObject(int nHandler, CCObject* pObject, const char* typeName) = 0;      //将一个整数数值压栈做为参数。
virtual int pushIntegerToLuaStack(int data) = 0;
//将一个浮点数值压栈做为参数。
virtual int pushFloatToLuaStack(int data) = 0;
//将一个布尔数值压栈做为参数。
virtual int pushBooleanToLuaStack(int data) = 0;
//将一个CCObject指针和类型名压栈做为参数。  virtual int pushCCObjectToLuaStack(CCObject* pObject, const char* typeName) = 0;  // 执行单点触屏事件
virtual int executeTouchEvent(int nHandler, int eventType, CCTouch *pTouch) = 0;
//执行多点触屏事件。  virtual int executeTouchesEvent(int nHandler, int eventType, CCSet *pTouches) = 0;  // 执行一个回调函数。  virtual int executeSchedule(int nHandler, float dt) = 0;
};

这个接口类的功能函数的具体实现,我们要参看CCLuaEngine类。

现在我们打开CCLuaEngine.h:

//加入lua的头文件,约定其中代码使用C风格
extern "C" {
#include "lua.h"
}
//相应的头文件。
#include "ccTypes.h"
#include "cocoa/CCObject.h"
#include "touch_dispatcher/CCTouch.h"
#include "cocoa/CCSet.h"
#include "base_nodes/CCNode.h"
#include "script_support/CCScriptSupport.h"  //使用Cocos2d命名空间
NS_CC_BEGIN  // 由CCScriptEngineProtocol派生的实际功能类。  class CCLuaEngine : public CCScriptEngineProtocol
{
public:  //析构  ~CCLuaEngine();  //取得LUA的全局指针,所有的LUA函数都需要使用这个指针来做为参数进行调用。  virtual lua_State* getLuaState(void) {  return m_state;  }  …此处省略若干字。  // 加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID  virtual void addLuaLoader(lua_CFunction func);  //取得当前单件实例指针  static CCLuaEngine* engine();  private:  //构造,单例,你懂的。  CCLuaEngine(void)  : m_state(NULL)  {  }  //初始化函数。
bool init(void);
//将一个句柄压栈  bool pushFunctionByHandler(int nHandler);  //唯一的LUA指针  lua_State* m_state;
};  NS_CC_END

分析其CPP实现:

[cpp]
//本类的头文件。
#include "CCLuaEngine.h"
//这里用到了tolua++库,tolua++库是一个专门处理LUA脚本的第三方库,可以很好的完成LUA访问C++类及成员函数的功能。如果没有tolua++,这块要处理起来可是麻烦死了。
#include "tolua++.h"  //加入lua库的相应头文件。
extern "C" {
#include "lualib.h"
#include "lauxlib.h"
#include "tolua_fix.h"
}  //加入Cocos2d-x所用的相应头文件。
#include "cocos2d.h"
#include "LuaCocos2d.h"
#include "cocoa/CCArray.h"
#include "CCScheduler.h"  //如果是ANDROID平台,加上对多线程加载LUA脚本的支持,使用相应的头文件。  #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "Cocos2dxLuaLoader.h"
#endif  //开始Cocos2d-x命名空间。
NS_CC_BEGIN  //析构。
CCLuaEngine::~CCLuaEngine()
{  //结束对LUA指针的使用,关闭LUA。  lua_close(m_state);
}  //初始始。
bool CCLuaEngine::init(void)
{  //开始对LUA的使用,创建一个LUA指针。
m_state = lua_open();
//打开相应的库。
luaL_openlibs(m_state);
//打开使用tolua封装的访问Cocos2d-x的库。  tolua_Cocos2d_open(m_state);
tolua_prepare_ccobject_table(m_state);
//如果是ANDROID平台,也加上对LUA进行多线程加载的库支持。
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)  addLuaLoader(loader_Android);
#endif  return true;
}  //取得单例指针。
CCLuaEngine* CCLuaEngine::engine()
{  CCLuaEngine* pEngine = new CCLuaEngine();  pEngine->init();  pEngine->autorelease();  return pEngine;
}  //通过LUA脚本ID移除对应的CCObject
void CCLuaEngine::removeCCObjectByID(int nLuaID)
{  tolua_remove_ccobject_by_refid(m_state, nLuaID);
}
//<span style="font-family: Arial, Helvetica, sans-serif;">通过函数索引值移除对应的LUA函数。</span>
void CCLuaEngine::removeLuaHandler(int nHandler)
{  tolua_remove_function_by_refid(m_state, nHandler);
}
//将一个目录中的LUA文件加入到LUA资源容器中。
void CCLuaEngine::addSearchPath(const char* path)
{  //取得全局表package
lua_getglobal(m_state, "package");
//取得其中的path字段,压入栈顶。
lua_getfield(m_state, -1, "path");
//取得当前的目录字符串。
const char* cur_path =  lua_tostring(m_state, -1);
//参数出栈,恢复堆栈。
lua_pop(m_state, 1);
//将新路径字符串加入到路径串列中,压入栈顶。
lua_pushfstring(m_state, "%s;%s/?.lua", cur_path, path);
//设置path字段值路径
lua_setfield(m_state, -2, "path");
//参数出栈,恢复堆栈。  lua_pop(m_state, 1);
}
//执行一段LUA代码
int CCLuaEngine::executeString(const char *codes)
{  //执行一段LUA代码。返回值存放到nRet中。
int nRet =    luaL_dostring(m_state, codes);
//进行下拉圾收集。  lua_gc(m_state, LUA_GCCOLLECT, 0);  //如果出错,打印日志。  if (nRet != 0)  {  CCLOG("[LUA ERROR] %s", lua_tostring(m_state, -1));  lua_pop(m_state, 1);  return nRet;  }  return 0;
}
//执行一个LUA脚本文件。  int CCLuaEngine::executeScriptFile(const char* filename)
{  //执行一个LUA脚本文件。返回值存放到nRet中。  int nRet = luaL_dofile(m_state, filename);
//    lua_gc(m_state, LUA_GCCOLLECT, 0);  //如果出错,打印日志。  if (nRet != 0)  {  CCLOG("[LUA ERROR] %s", lua_tostring(m_state, -1));  lua_pop(m_state, 1);  return nRet;  }  return 0;
}
//调用一个全局函数。
int    CCLuaEngine::executeGlobalFunction(const char* functionName)
{  //将全局函数放在栈顶
lua_getglobal(m_state, functionName);  /* query function by name, stack: function */
//判断是否是函数。  if (!lua_isfunction(m_state, -1))  {  CCLOG("[LUA ERROR] name '%s' does not represent a Lua function", functionName);  lua_pop(m_state, 1);  return 0;  }  //调用函数。  int error = lua_pcall(m_state, 0, 1, 0);         /* call function, stack: ret */
//    lua_gc(m_state, LUA_GCCOLLECT, 0);  if (error)  {  CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1));  lua_pop(m_state, 1); // clean error message  return 0;  }  // get return value  //如果取得的第一个参数不是数字,返回错误。  if (!lua_isnumber(m_state, -1))  {  lua_pop(m_state, 1);  return 0;  }  //取得数字的参数存放在ret中。
int ret = lua_tointeger(m_state, -1);
//参数出栈,恢复堆栈。  lua_pop(m_state, 1);                                            /* stack: - */  return ret;
}
//通过句柄调用函数多种形态。
//通过句柄调用函数,参数二为参数数量。
int CCLuaEngine::executeFunctionByHandler(int nHandler, int numArgs)
{  if (pushFunctionByHandler(nHandler))  {  if (numArgs > 0)  {  lua_insert(m_state, -(numArgs + 1));                        /* stack: ... func arg1 arg2 ... */  }  int error = 0;  // try  // {  error = lua_pcall(m_state, numArgs, 1, 0);                  /* stack: ... ret */  // }  // catch (exception& e)  // {  //     CCLOG("[LUA ERROR] lua_pcall(%d) catch C++ exception: %s", nHandler, e.what());  //     lua_settop(m_state, 0);  //     return 0;  // }  // catch (...)  // {  //     CCLOG("[LUA ERROR] lua_pcall(%d) catch C++ unknown exception.", nHandler);  //     lua_settop(m_state, 0);  //     return 0;  // }  if (error)  {  CCLOG("[LUA ERROR] %s", lua_tostring(m_state, - 1));  lua_settop(m_state, 0);  return 0;  }  // get return value  int ret = 0;  //如果返回参数是数字转为整数。  if (lua_isnumber(m_state, -1))  {  ret = lua_tointeger(m_state, -1);  }//如果是布尔型转为true或false  else if (lua_isboolean(m_state, -1))  {  ret = lua_toboolean(m_state, -1);  }  //参数出栈,恢复堆栈。  lua_pop(m_state, 1);  return ret;  }  else  {  return 0;  }
}  //通过句柄调用函数,参数二为整数数据。
int CCLuaEngine::executeFunctionWithIntegerData(int nHandler, int data)
{  lua_pushinteger(m_state, data);  return executeFunctionByHandler(nHandler, 1);
}
//通过句柄调用函数,参数二为浮点数据。
int CCLuaEngine::executeFunctionWithFloatData(int nHandler, float data)
{  lua_pushnumber(m_state, data);  return executeFunctionByHandler(nHandler, 1);
}
//通过句柄调用函数,参数二为布尔型数据。
int CCLuaEngine::executeFunctionWithBooleanData(int nHandler, bool data)
{  lua_pushboolean(m_state, data);  return executeFunctionByHandler(nHandler, 1);
}
//通过句柄调用函数,参数二为CCObject指针数据和其类型名称。
int CCLuaEngine::executeFunctionWithCCObject(int nHandler, CCObject* pObject, const char* typeName)
{  tolua_pushusertype_ccobject(m_state, pObject->m_uID, &pObject->m_nLuaID, pObject, typeName);  return executeFunctionByHandler(nHandler, 1);
}
//将一个整数数值压栈做为参数。
int CCLuaEngine::pushIntegerToLuaStack(int data)
{  //将整数值压入堆栈
lua_pushinteger(m_state, data);
//返回参数的数量。  return lua_gettop(m_state);
}
//将一个浮点数值压栈做为参数。
int CCLuaEngine::pushFloatToLuaStack(int data)
{  //将数字值压入堆栈
lua_pushnumber(m_state, data);
//返回参数的数量。  return lua_gettop(m_state);
}
//将一个布尔数值压栈做为参数。
int CCLuaEngine::pushBooleanToLuaStack(int data)
{  //将boolean值压入堆栈
lua_pushboolean(m_state, data);
//返回参数的数量。  return lua_gettop(m_state);
}
//将一个CCObject指针和类型名压栈做为参数。
int CCLuaEngine::pushCCObjectToLuaStack(CCObject* pObject, const char* typeName)
{  tolua_pushusertype_ccobject(m_state, pObject->m_uID, &pObject->m_nLuaID, pObject, typeName);  return lua_gettop(m_state);
}  // 执行单点触屏事件
int CCLuaEngine::executeTouchEvent(int nHandler, int eventType, CCTouch *pTouch)
{
CCPoint pt = CCDirector::sharedDirector()->convertToGL(pTouch->getLocationInView());
//将参数压栈后调用函数。  lua_pushinteger(m_state, eventType);  lua_pushnumber(m_state, pt.x);  lua_pushnumber(m_state, pt.y);  return executeFunctionByHandler(nHandler, 3);
}  //执行多点触屏事件。
int CCLuaEngine::executeTouchesEvent(int nHandler, int eventType, CCSet *pTouches)
{
//将类型参数压栈后调用函数。
lua_pushinteger(m_state, eventType);
//创建一个表  lua_newtable(m_state);
//将多个触点信息参数放入表中。  CCDirector* pDirector = CCDirector::sharedDirector();  CCSetIterator it = pTouches->begin();  CCTouch* pTouch;  int n = 1;  while (it != pTouches->end())  {  pTouch = (CCTouch*)*it;  CCPoint pt = pDirector->convertToGL(pTouch->getLocationInView());  //将位置x压入堆栈  lua_pushnumber(m_state, pt.x);  //将栈顶的数值放入到表中对应索引n的数值中  lua_rawseti(m_state, -2, n++);  //将位置x压入堆栈  lua_pushnumber(m_state, pt.y);  //将栈顶的数值放入到表中对应索引n的数值中  lua_rawseti(m_state, -2, n++);  ++it;  }  //以表做为第二参数压栈,调用函数。  return executeFunctionByHandler(nHandler, 2);
}
//通过句柄调用函数,参数二为CCObject指针数据和其类型名称。
int CCLuaEngine::executeSchedule(int nHandler, float dt)
{  return executeFunctionWithFloatData(nHandler, dt);
}
// 加入一个多线程加载LUA脚本的实时回调函数,此函数用于ANDROID
void CCLuaEngine::addLuaLoader(lua_CFunction func)
{  if (!func) return;  //取得全局表
lua_getglobal(m_state, "package");
//取得全局表中的“loaders”表  lua_getfield(m_state, -1, "loaders");
//将设定的函数和参数压栈
lua_pushcfunction(m_state, func);
//将参数压栈  for (int i = lua_objlen(m_state, -2) + 1; i > 2; --i)
{  //取得原"loaders"表第i-1个参数  lua_rawgeti(m_state, -2, i - 1);                                                                                //将取出的值放到新"loaders"表中第i个数值  lua_rawseti(m_state, -3, i);
}
//将函数设为新"loaders"表的第2个参数  lua_rawseti(m_state, -2, 2);
//把“loaders” 表放到全局表中  lua_setfield(m_state, -2, "loaders");                    //参数出栈,恢复堆栈。  lua_pop(m_state, 1);
}
//将一个句柄压栈
bool CCLuaEngine::pushFunctionByHandler(int nHandler)
{  //找出注册函数表的第nHandler个数值
lua_rawgeti(m_state, LUA_REGISTRYINDEX, nHandler);  /* stack: ... func */
//判断是否是函数。  if (!lua_isfunction(m_state, -1))  {  CCLOG("[LUA ERROR] function refid '%d' does not reference a Lua function", nHandler);  lua_pop(m_state, 1);  return false;  }  return true;
}

然后我们来看一下CCScriptEngineManager,这个类被称为脚本引擎管理器,其实很简单,只是用来设定当前项目的唯一正在使用的脚本引擎。也许Cocos2d-x打算用它管理多种类型脚本引擎,比如python,js等。

[cpp]

class CC_DLL CCScriptEngineManager

{

public:

//析构

~CCScriptEngineManager(void);

//取得单例指针

CCScriptEngineProtocol* getScriptEngine(void) {

return m_pScriptEngine;

}

//设置使用的LUA管理器

void setScriptEngine(CCScriptEngineProtocol *pScriptEngine);

//移除使用的LUA管理器。

void removeScriptEngine(void);

//取得单例指针

static CCScriptEngineManager* sharedManager(void);

//销毁单例

static void purgeSharedManager(void);

private:

//构造,单例,你懂的。

CCScriptEngineManager(void)

: m_pScriptEngine(NULL)

{

}

//使用的LUA脚本引擎

CCScriptEngineProtocol *m_pScriptEngine;

};

其对应的CPP实现:

//全局唯一的

static CCScriptEngineManager* s_pSharedScriptEngineManager = NULL;

//析构

CCScriptEngineManager::~CCScriptEngineManager(void)

{

removeScriptEngine();

}

//设置使用的LUA管理器

void CCScriptEngineManager::setScriptEngine(CCScriptEngineProtocol *pScriptEngine)

{

removeScriptEngine();

m_pScriptEngine = pScriptEngine;

m_pScriptEngine->retain();

}

//移除使用的LUA管理器。

void CCScriptEngineManager::removeScriptEngine(void)

{

if (m_pScriptEngine)

{

m_pScriptEngine->release();

m_pScriptEngine = NULL;

}

}

//取得单例指针

CCScriptEngineManager* CCScriptEngineManager::sharedManager(void)

{

if (!s_pSharedScriptEngineManager)

{

s_pSharedScriptEngineManager = new CCScriptEngineManager();

}

return s_pSharedScriptEngineManager;

}

//销毁单例

void CCScriptEngineManager::purgeSharedManager(void)

{

if (s_pSharedScriptEngineManager)

{

delete s_pSharedScriptEngineManager;

s_pSharedScriptEngineManager = NULL;

}

}

现在我们来实际操作一下。

打开HelloLua工程中的AppDelegate.cpp:

在AppDelegate::applicationDidFinishLaunching()函数中看这几行代码:

[cpp]

//取得LUA脚本引擎

CCScriptEngineProtocol* pEngine = CCLuaEngine::engine();

//设置脚本引擎管理器使用新创建的LUA脚本引擎

CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);

//如果是ANDROID平台,获hello.lua内存到字符串然后执行字符串

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)

CCString* pstrFileContent = CCString::createWithContentsOfFile("hello.lua");

if (pstrFileContent)

{

pEngine->executeString(pstrFileContent->getCString());

}

#else

//如果不是ANDROID平台,取得hello.lua文件全路径并执行文件。

std::string path = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath("hello.lua");

pEngine->addSearchPath(path.substr(0, path.find_last_of("/")).c_str());

pEngine->executeScriptFile(path.c_str());

#endif

就这样,hello.lua中的脚本就可以被执行了。

现在我们将HelloLua工程目录拷出一份来,将目录和工程命名为StudyLua,并在程序运行目录中加入相关资源图片。之后我们打开hello.lua:

[html]

-- 设置内存回收

collectgarbage("setpause", 100)

collectgarbage("setstepmul", 5000)

-- 取得窗口大小

local winSize = CCDirector:sharedDirector():getWinSize()

-- 将Hello背景图加入

local function createLayerHello()

local layerHello = CCLayer:create()

-- 加入背景图

local bg = CCSprite:create("Hello.png")

bg:setPosition(winSize.width / 2 , winSize.height / 2)

layerHello:addChild(bg)

-- 创建HelloWorld

local label = CCLabelTTF:create("Hello Cocos2d-x", "Arial", 50)

label:setPosition(winSize.width / 2 ,60)

label:setVisible(true)

layerHello:addChild(label)

return layerHello

end

--将关闭按钮菜单加入

local function createExitBtn()

local layerMenu = CCLayer:create()

--局部函数,用于退出

local function menuCallbackExit()

CCDirector:sharedDirector():endToLua()

end

-- 创建退出按钮

local menuPopupItem = CCMenuItemImage:create("CloseNormal.png", "CloseSelected.png")

-- 放在居上角附近

menuPopupItem:setPosition(winSize.width - 50, winSize.height - 50)

-- 注册退出函数

menuPopupItem:registerScriptHandler(menuCallbackExit)

-- 由菜单按钮项创建菜单

local   menuClose = CCMenu:createWithItem(menuPopupItem)

menuClose:setPosition(0, 0)

menuClose:setVisible(true)

-- 将菜单加入层中

layerMenu:addChild(menuClose)

return layerMenu

end

--创建场景

local sceneGame = CCScene:create()

--将Hello背景图加入

sceneGame:addChild(createLayerHello())

--将关闭按钮菜单加入

sceneGame:addChild(createExitBtn())

--运行场景

CCDirector:sharedDirector():runWithScene(sceneGame)

运行一下:

Cocos2d-x之LUA脚本引擎深入分析相关推荐

  1. 在windows程序中嵌入Lua脚本引擎--编写自己的Lua库

    在<在windows程序中嵌入Lua脚本引擎--建立一个简易的"云命令"执行的系统>一文中,我提到了使用Lua的ffi库,可以让我们像写C代码一样写lua程序.这是个非 ...

  2. 在windows程序中嵌入Lua脚本引擎--建立一个简易的“云命令”执行的系统

    在<在windows程序中嵌入Lua脚本引擎--使用VS IDE编译Luajit脚本引擎>开始处,我提到某公司被指责使用"云命令"暗杀一些软件.本文将讲述如何去模拟一个 ...

  3. 在windows程序中嵌入Lua脚本引擎--使用VS IDE编译Luajit脚本引擎

    前些天听到一个需求:某业务方需要我们帮忙清理用户电脑上的一些废弃文件.同事完成这个逻辑的方案便是在我们程序中加入了一个很"独立"的业务逻辑:检索和删除某个程序产生的废弃文件.试想, ...

  4. Windows编译安装AzerothCore魔兽世界开源服务端Lua脚本引擎Eluna和防作弊anticheat模块教程

    Windows编译安装AzerothCore魔兽世界开源服务端Lua脚本引擎Eluna和防作弊anticheat模块教程 大家好,我是艾西今天和大家聊聊魔兽世界游戏内的脚步以及防作弊模块 Eluna是 ...

  5. 深入分析 Redis Lua 脚本运行原理

    Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题.Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向服务器发送 lua 脚本来执 ...

  6. 基于Lua脚本语言的嵌入式UART通信的实现

    随着变电站智能化程度的逐步提高,对温度.湿度等现场状态参量的采集需求也越来越多.就目前而言,在现场应用中,此类设备多采用RS232或RS485等UART串行通信方式和IED(Intelligent E ...

  7. 欲求不满之 Redis Lua 脚本的执行原理

    Redis 提供了非常丰富的指令集,但是用户依然不满足,希望可以自定义扩充若干指令来完成一些特定领域的问题.Redis 为这样的用户场景提供了 lua 脚本支持,用户可以向服务器发送 lua 脚本来执 ...

  8. 如何在C++中集成LUA脚本(LuaWrapper For C++篇)

    为什么要用Lua作脚本? 使用Lua作脚本,主要是因为它小巧玲珑(体积小,运行快),而且它的语法又比较简单明了.不过,使用LuaAPI将Lua引擎集成到程序中,确实有一些不方便--用落木随风网友的话来 ...

  9. Redis:EVAL执行Lua脚本

    EVAL 脚本 numkeys 键[键...] arg [arg ...] 自Redis2.6.0版本起可用. 时间复杂度:取决于执行的脚本. EVAL介绍 EVAL和EVALSHA用于从Redis2 ...

最新文章

  1. Nacos源码集群一致性
  2. iOS项目架构 小谈
  3. 匿名方法,lambad表达式,匿名类
  4. JDK和Spring中的设计模式
  5. lesson6 复数及复指数
  6. e83服务器电源键位置,正文-新华三集团-H3C
  7. weblogic apache 整合 代理
  8. OpenCV_ImageMatching with SURF and SIFT(使用SURF和 SIFT进行图像匹配 对比)
  9. oracle常用高级函数,oracle常用函数详解(详细)
  10. GMS Apps安装
  11. 图像处理之均值滤波器、高斯滤波器和双边滤波器
  12. Windows的CMD的NET命令net start , net stop ...
  13. matlab z变换 差分,matlab z变换
  14. pscc2018安装服务器无响应,win10系统无法安装ps cc2018提示Microsoft visualc++ 2017的解决方法...
  15. 数字人民币支付新选择 没有网络时也能使用
  16. 数独解法-变形数独(第二讲:数独基础方法(行唯一列唯一宫唯一唯余数))
  17. CC00416.CloudKubernetes——|KuberNetesNetworkPolicy.V08|——|NetworkPolicy.v08|隔离中间件服务.v04|
  18. 同级最强!天玑8200实测成绩放出,iQOO Neo7 SE神机配神U
  19. python 文件批量转换格式_使用python批量化音乐文件格式转换的实例
  20. C语言100题打卡—第7题

热门文章

  1. 有了这些接口测试用例+工具,测试效率想不提升都难
  2. 基于持续集成的轻量级接口自动化测试 【持续更新...】
  3. mysql主库从库在同一台服务器_通过两种方式增加从库——不停止mysql服务
  4. 算法的优缺点_各种电磁仿真算法的优缺点和适用范围(FDTD, FEM和MOM等)
  5. python dict批量选择_用python实现word内容批量替换
  6. 计算机vf知识点总结,计算机等级考试二级VF常用函数总结
  7. pycharm TabError: inconsistent use of tabs and spaces in indentation
  8. Java实现对字符串的快速排序-程序解读
  9. python生成验证码的程序_Python基础篇生成4位随机验证码
  10. 基于tensorflow 1.x 的bert系列预训练模型工具