• 1 最重要的两个数据结构
    • 1.1 lua_State(Lua虚拟机/Lua协程)
    • 1.2 global_State(Lua全局状态)
  • 2 环境相关的变量
    • 2.1 Global表
      • 2.1.1 Global表在lua_State结构中
      • 2.1.2 Global表在 f_luaopen 时被初始化
    • 2.2 env表
      • 2.2.1 env表在Closure结构中
      • 2.2.2 查找一个全局变量<=>在当前函数的env表中寻找
      • 2.2.3 lua函数的env表何时被设置
      • 2.2.4 C函数的env表何时被设置
      • 2.2.5 getfenv(读取函数的环境)
      • 2.2.6 setfenv(强制设置函数的环境)
    • 2.3 registry表(注册表)
    • 2.4 UpValue

1 最重要的两个数据结构

1.1 lua_State(Lua虚拟机/Lua协程)

每个lua虚拟机(协程)对应一个lua_State结构体

(lstate.h) lua_State

struct lua_State
{CommonHeader;//#define CommonHeader    GCObject *next; lu_byte tt; lu_byte markedlu_byte status;//协程的状态码StkId top;//栈顶位置,“寄存器”第一个可用位置StkId base;//当前函数调用的栈基址global_State* l_G;//全局状态机CallInfo* ci;//当前函数调用信息const Instruction* savedpc;//指令指针StkId stack_last;//“寄存器”最后一个可用位置StkId stack;//栈数组的起始位置CallInfo* end_ci;//函数调用信息数组的最后一个位置的下一个位置CallInfo* base_ci;//函数调用信息数组首地址int stacksize;//栈的大小int size_ci;//函数调用信息数组大小unsigned short nCcalls;//内嵌C调用的层数unsigned short baseCcalls;//唤醒协程时的内嵌C调用层数lu_byte hookmask;lu_byte allowhook;int basehookcount;int hookcount;lua_Hook hook;TValue l_gt;//Global表TValue env;//环境表的临时位置GCObject* openupval;//栈上open状态的uvaluesGCObject* gclist;struct lua_longjmp* errorJmp;//当前跳转信息,实现try catch的关键结构ptrdiff_t errfunc;//当前错误处理函数相对于“寄存器数组首地址”的偏移地址
};

1.2 global_State(Lua全局状态)

(lstate.h) global_State

typedef struct global_State
{stringtable strt;//全局字符串表lua_Alloc freealloc;//内存重分配函数void* ud;//freealloc的辅助数据lu_byte currentwhite;//当前白色,见GC章节lu_byte gcstate;//GC状态,见GC章节int sweepstrgc;//strt中GC扫描到的位置GCObject* rootgc;//所有可回收对象的链表GCObject** sweepgc;//rootgc中扫描到的位置GCObject* gray;//灰色对象链表GCObject* grayagain;//需要被原子性遍历地对象链表GCObject* week;//弱表的链表GCObject* tmudata;//需要被GC的userdata的链表的最后一个元素Mbuffer* buff;//字符串连接操作用的临时缓冲对象lu_mem GCthreshold;//触发GC的边界值,见GC章节lu_mem totalbytes;//当前分配的总字节数lu_mem estimate;//实际上使用的总字节数的估计值lu_mem gcdept;//在预定的回收字节数中,还欠多少字节没有回收int gcpause;//连续的GC中的停顿步长相关值,见GC章节int gcstepmul;//GC的粒度lua_CFunction panic;//对于未捕获异常,会调用这个函数TValue l_registry;//注册表struct lua_State* mainthread;//主协程UpVal uvhead;//open状态的upvalue双链表的头部struct Table* mt[NUM_TAGS];//存放 基本类型的元表 的数组TString* tmname[TM_N];//存放 元方法名字符串对象地址 的数组
}

2 环境相关的变量

2.1 Global表

Global表存放在lua_State结构体中,每个lua_State实例都有一个对应的Global表,对于一个lua_State这个表就是用来存放全局变量的。

2.1.1 Global表在lua_State结构中

struct lua_State
{//..//TValue l_gt;//Global表//..//
}

2.1.2 Global表在 f_luaopen 时被初始化

(lstate.c) f_luaopen (f_luaopen 又被 lua_newstate 调用)

static void f_luaopen(lua_State* L, void* ud)
{//...//sethvalue(L, gt(L), luaH_new(L, 0, 2));//创建了一个arraysize=0, hashsize=2的表作为全局表//...//
}

2.2 env表

env表存放在Closure结构体中,也就是每个函数都有自己独立的环境

2.2.1 env表在Closure结构中

(lobject.h) Closure

#define ClosureHeader \CommonHeader; \lu_byte isC; \lu_byte nupvalues; \GCObject *gclist; \struct Table *env //函数的环境表地址typedef struct CClosure
{ClosureHeader;lua_CFunction f;TValue upvalue[1];
} CClosure;typedef struct LClosure
{ClosureHeader;struct Proto* p;UpVal* upvals[1];
} LClosure;typedef union Closure
{CClosure c;LClosure l;
} Closure;

2.2.2 查找一个全局变量<=>在当前函数的env表中寻找

(lvm.c) luaV_execute 模拟CPU,后续虚拟机章节细说

//我们暂且只关心 全局变量 的 查找或设置
void luaV_execute (lua_State *L, int nexeccalls)
{LClosure* cl;StkId base;TValue* k;const Instruction* pc;
reentry:pc = L->savedpc;cl = &clvalue(L->ci->func)->l;base = L->base;k = cl->p->k;//循环取指令,执行指令for (;;) {const Instruction i = *pc++;//取出当前指令i,pc指向下一个指令,完全符合CPU工作原理if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)){traceexec(L, pc);//traceexec后面说if (L->status == LUA_YIELD){L->savedpc = pc - 1;return;}base = L->base;}StkId ra = RA(i);//#define RA(i) (base+GETARG_A(i))//#define GET_OPCODE(i)  (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))switch (GET_OPCODE(i)) {//..//case OP_GETGLOBAL: //OP_GETGLOBAL 指令格式A Bx 指令意义 R(A) := Gbl[Kst(Bx)]{TValue g;TValue* rb = KBx(i);//#define KBx(i) k+GETARG_Bx(i)sethvalue(L, &g, cl->env);//g设为去当前函数的env表Protect(luaV_gettable(L, &g, rb, ra));continue;}//..//case OP_SETGLOBAL:{TValue g;sethvalue(L, &g, cl->env);Protect(luaV_settable(L, &g, KBx(i), ra));continue;}//..//}}
}

(lvm.c) Protect宏

  • 执行代码x前 先将L->savedpc设为 下一步药执行的指令地址pc (因为代码x有异常的可能,所以记录pc到L上以便从下一条指令继续执行)
  • 执行代码x
  • 执行代码x后,再将 base设为 L->base(一位代码x可能会触发栈的重新分配内存)
#define Protect(x)   { L->savedpc = pc; {x;}; base = L->base; }

2.2.3 lua函数的env表何时被设置

  • f_parser函数里,env被设置为&L->l_gt(lua_State的Global表)
  • luaV_execute的OP_CLOSURE指令分支,env被设置为&clvalue(L->ci->func)->l->env (当前调用信息对应的函数的env表)

(ldo.c) f_parser

static void f_parser(lua_State* L, void* ud)
{struct SParser* p = cast(struct SParser*, ud);int c = luaZ_lookahead(p->z);luaC_checkGC(L);Proto* tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z, &p->buff, p->name);//很显然,在解析二进制或原文件时得到的lua函数,默认的env表时L的Global表Closure* cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));cl->l.p = tf;for (i = 0; i < tf.nups; i++){cl->l.upvals[i] = luaF_newupval(L);}setclvalue(L, L->top, cl);incr_top(L);
}

(lvm.c) luaV_execute

//我们暂且只关心 OP_CLOSURE 指令
void luaV_execute (lua_State *L, int nexeccalls)
{LClosure* cl;StkId base;TValue* k;const Instruction* pc;
reentry:pc = L->savedpc;cl = &clvalue(L->ci->func)->l;base = L->base;k = cl->p->k;//循环取指令,执行指令for (;;) {const Instruction i = *pc++;//取出当前指令i,pc指向下一个指令,完全符合CPU工作原理if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)){traceexec(L, pc);//traceexec后面说if (L->status == LUA_YIELD){L->savedpc = pc - 1;return;}base = L->base;}StkId ra = RA(i);//#define RA(i) (base+GETARG_A(i))//#define GET_OPCODE(i)  (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))switch (GET_OPCODE(i)) {//..//case OP_CLOSURE:{//根据索引获取当前函数的内嵌函数原型Proto* p = cl->p->p[GETARG_Bx(i)];//获取内嵌函数的upvalue数量int nup = p->nups;//根据upvalue数量和env表创建内嵌函数CLosure* ncl = luaF_newLclosure(L, nup, cl->env);ncl->l.p = p;for (int j = 0; j < nup; j++, pc++){if (GET_OPCODE(*pc) == OP_GETUPVAL){ncl->l.upvals[j] = cl->upvals[GETARG_B(*pc)];}else{ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc));}}setclvalue(L, ra, ncl);Protect(luaC_checkGC(L));continue;}//..//}}
}

(lfunc.c) luaF_newLclosure 创建一个新的lua函数(闭包)

Closure* luaF_newLclosure(lua_State* L, int nupvalues, Table* env)
{//#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + cast(int, sizeof(TValue *)*((n)-1))) Closure* cl = cast(Closure*, luaM_malloc(L, sizeLClosure(nupvalues)));luaC_link(L, obj2gco(cl), LUA_TFUNCTION);cl->l.isC = 0;cl->l.env = e;cl->l.nupvalues = cast_byte(nupvalues);while (nupvalues--){cl->l.upvals[nupvalues] = NULL;}return c;
}

2.2.4 C函数的env表何时被设置

  • lua_pushcclosure函数里,env被设置为getcurrenv(L)
  • f_Ccall函数里,env被设置为getcurrenv(L)

(lapi.c) lua_pushcclosure 创建一个C闭包入栈

LUA_API void lua_pushcclosure(lua_State* L, lua_CFunction fn, int n)
{luaC_checkGC(L);Closure* cl = luaF_newCclosure(L, n, getcurrentv(L));cl->c.f = fn;L->top -= n;while(n--){setobj2n(L, &cl->c.upvalue[n], L->top + n);}setclvalue(L, L->top, cl);api_incr_top(L);//#define api_incr_top(L)   {api_check(L, L->top < L->ci->top); L->top++;}
}

(lfunc.c) luaF_newCclosure 创建C闭包

Closure* luaF_newCclosure(lua_State* L, int nupvals, Table* env)
{//#define sizeCclosure(n)  (cast(int, sizeof(CClosure)) + cast(int, sizeof(TValue)*((n)-1)))Closure* c = cast(Closure*, luaM_malloc(L, sizeCclosure(nupvals)));luaC_link(L, obj2gco(c), LUA_TFUNCTION);c->c.isC = 1;c->c.env = env;c->c.nupvals = cast_byte(nupvals);return c;
}

(lapi.c) getcurrentv 获取当前的函数env表(获取L->ci的env表,当然L->base_ci的env表就是Global表)

  • 若不是内嵌函数,则返回Global表
  • 若是内嵌函数,则返回母函数的env表
static Table* getcurrenv(lua_State* L)
{if (L->ci == L->base_ci){return hvalue(gt(L));}Closure* func = curr_func(L);//#define curr_func(L)    (clvalue(L->ci->func))return func->c.env;
}

(lapi.c) f_Ccall 创建并调用一个C函数

static void f_Ccall(lua_State* L, void* ud)
{struct CCallS* c = cast(struct CCallS*, ud);Closure* cl = luaF_newCclosure(L, 0, getcurrenv(L));cl->c.f = c->func;setclvalue(L, L->top, cl);api_incr_top(L);luaD_call(L, L->top - 2, 0);
}

2.2.5 getfenv(读取函数的环境)

(lbaselib.c) luaB_getfenv (若为C函数,则返回Global表;否则lua_getfenv)

static int luaB_getfenv(lua_State* L)
{getfunc(L, 1);if (lua_iscfuntion(L, -1)){lua_pushvalue(L, LUA_GLOBALSINDEX);}else{lua_getfenv(L, -1);}return 1;
}

2.2.6 setfenv(强制设置函数的环境)

(lbaselib.c) luaB_setfenv

static int luaB_setfenv(lua_State* L)
{//检查L->base+2-1处是不是table类型luaL_chechtype(L, 2, LUA_TTABLE);getfunc(L, 0);//把 L->base+2-1 处的值复制到 L->top 处,并L->top++lua_pushvalue(L, 2);//若level为0,则改变当前lua线程的环境if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0){lua_pushthread(L);lua_insert(L, -2);lua_settenv(L, -2);return 0;}//若L->top-2 是C函数,或者给 L->top-2 设置env失败,则报错if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0){//#define LUA_QL(x)    "'" x "'"luaL_error(L, LUA_QL("setfenv") " cannot change environment of given object");}return 1;
}

(lauxlib.c) luaL_checktype 检查类型是否满足,否则报错

LUALIB_API void luaL_checktype(lua_State* L, int narg, int t)
{if (lua_type(L, narg) != t){tag_error(L, narg, t);}
}

(lauxlib.c) tag_error 根据类型标识报错

static void tag_error(lua_State* L, int narg, int tag)
{luaL_typeerror(L, narg, lua_typename(L, tag));
}

(lauxlib.c) luaL_typeerror 根据类型名报错

LUALIB_API int luaL_typeerror(lua_State* L, int narg, const char* tname)
{const char* msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg));return luaL_argerror(L, narg, msg);//luaL_argerror见异常章节
}

(lbaselib.c) getfunc (获取L->base 处的函数,并压入栈顶)

static void getfunc(lua_State* L, int opt)
{//若L->base处是函数,则把L->base处的值复制到L->top处,L->top++if (lua_isfunction(L, 1)){lua_pushvalue(L, 1);return;}//#define luaL_optint(L,n,d)   ((int)luaL_optinteger(L, (n), (d)))//#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))//opt为非0时,对L->base处的值进行判断,若为nil或none,则取默认值,否则执行整数转换操作;若opt为0,则仅仅执行整数转换操作int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1);//level必须为>=0luaL_argcheck(L, level >= 0, 1, "level must be non-negative");lua_Debug ar;if (lua_getstack(L, level, &ar) == 0){luaL_argerror(L, 1, "invalid level");}lua_getinfo(L, "f", &ar);if (lua_isnil(L, -1)){luaL_error(L, "no function environmnent for tail call at level %d", level);//luaL_error见异常章节}
}

(lauxlib.c) luaL_optinteger

//不是数字类型且转为整数后不是0,则报错
LUALIB_API lua_chechinteger(lua_State* L, int narg)
{lua_Integer d = lua_tointeger(L, narg);if (d == 0 && !lua_isnumber(L, narg)){tag_error(L, narg, LUA_TNUMBER);}return d;
}LUALIB_API lua_Integer luaL_optinteger(lua_State* L, int narg, lua_Integer def)
{//#define luaL_opt(L,f,n,d)    (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))return luaL_opt(L, luaL_checkinteger, narg, def);
}

(ldebug.c) lua_getstack 获取指定层级的调试信息

LUA_API int lua_getstack(lua_State* L, int level, lua_Debug* ar)
{for (CallInfo* ci = L->ci; level > 0 && ci > L->base_ci; ci--){level--;//若为lua函数,则跳过尾调用,//见函数章节if (f_isLua(ci)){level -= ci->tailcalls;//原文 /* skip lost tail calls */ 见函数章节}}int status;if (level == 0 && ci > L->base_ci){//找到了合适的levelstatus = 1;ar->i_ci = cast_int(ci - L->base_ci);}else if (level < 0){//原文 /* level is of a lost tail call? */ 见函数章节status = 1;ar->i_ci = 0;}else{status = 0;}return status;
}

(ldebug.c) lua_getinfo 获取debug信息

LUA_API int lua_getinfo(lua_State* L, const char* what, lua_Debug* ar)
{Closure* f = NULL;if (*what == '>'){StdId func = L->top - 1;what++;f = clvalue(func);L->top--;//函数出栈}else if (ar->i_ci != 0)//没有尾调用  //见函数章节{ci = L->base_ci + ar->i_ci;f = clvalue(ci->func);}int status = auxgetinfo(L, what, ar, f, ci);//auxgetinfo,见函数章节if (strchr(what, 'f')){if (f == NULL){setnilvalue(L->top);}else{setclvalue(L, L->top, f);}incr_top(L);}if (strchr(what, 'L')){collectvalidlines(L, f);//收集行号信息}return status;
}

(ldebug.c) collectvalidlines 收集行号信息(获得一个table<行号,true> 的结构,压入栈顶)

static void collectvalidlines(lua_State* L, Closure* f)
{if (f == NULL || f->c.isC){setnilvalue(L->top);}else{Table* t = luaH_new(L, 0, 0);int* lineinfo = f->l.p->lineinfo;for (int i = 0; i < f->l.p->sizelineinfo; i++){setbvalue(luaH_setnum(L, t, lineinfo[i]), 1);}sethvalue(L, L->top, t);}incr_top(L);
}

2.3 registry表(注册表)

  • 注册表在global_State中,全局唯一,这个表可以被多个lua_State访问
  • 注册表只能被C代码访问,Lua代码不能访问

(lauxlib.h) lua_ref,lua_unref,lua_getref

#define LUA_NOREF       (-2)
#define LUA_REFNIL      (-1)//往注册表新增一个唯一key
#define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \(lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0))
//取消注册表一个唯一key
#define lua_unref(L,ref)        luaL_unref(L, LUA_REGISTRYINDEX, (ref))
//获取注册表唯一key对应的值
#define lua_getref(L,ref)       lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))

(lauxlib.c) luaL_ref,luaL_unref

//对于栈顶的value,从表t中获取一个可用key,来存value
/*
表中的key=FREELIST_REF对应的value是 空闲的key的索引(而所谓的空闲的key是由于unref造成的结果)举个例子来理解这个设计:
开始t={[FREELIST_REF]=nil}
存v1:  ref=t[FREELIST_REF]=nil => ref=#t + 1 = 1, t[ref]=t[1]=v1 => t={[FREELIST_REF]=nil, [1]=v1}
存v2:  ref=t[FREELIST_REF]=nil => ref=#t + 1 = 2, t[ref]=t[2]=v2 => t={[FREELIST_REF]=nil, [1]=v1, [2]=v2}
unref(1):  ref=1, t[ref]=t[FREELIST_REF]=nil, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=nil, [2]=v2}
unref(2):  ref=2, t[ref=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=nil, [2]=1}
存v3:  t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=1, t[ref]=v3 => t={[FREELIST_REF]=1, [1]=nil, [2]=v3}
存v4:  t[FREELIST_REF]=1 => ref=1, t[FREELIST_REF]=t[ref]=nil, t[ref]=v4 => t={[FREELIST_REF]=nil, [1]=v4, [2]=v3}也就说t中的key分为2种状态:空闲 和 非空闲。若为空闲,则其value是下一个空闲key的索引;若为非空闲,则其value是有意义的值。拓展讨论:
开始t={[FREELIST_REF]=nil}
存v1:  t[FREELIST_REF]=nil => ref=#t + 1 = 1, t[ref]=t[1]=v1 => t={[FREELIST_REF]=nil, [1]=v1}
存v2:  t[FREELIST_REF]=nil => ref=#t + 1 = 2, t[ref]=t[2]=v2 => t={[FREELIST_REF]=nil, [1]=v1, [2]=v2}
unref(1):  ref=1, t[ref]=t[FREELIST_REF]=nil, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=nil, [2]=v2}
再次unref(1):  ref=1, t[ref]=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=1, [2]=v2}
unref(2):  ref=2, t[ref]=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=1, [2]=1}
再次unref(2):  ref=2, t[ref]=t[FREELIST_REF]=2, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=1, [2]=2}
存v3:  t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=2, t[ref]=v3 => t={[FREELIST_REF]=2, [1]=1, [2]=v3}
存v4:  t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=v3, t[ref]=v4 => t={[FREELIST_REF]=2, [1]=1, [2]=v4}//v3不见了,出问题了!!!*/
LUALIB_API int luaL_ref(lua_State* L, int t)
{//假设栈顶的值为v//若v==nil,则返回-1if (lua_isnil(L, -1)){lua_pop(L, 1);return LUA_REFNIL;//#define LUA_REFNIL      (-1)}t = abs_index(L, t);//#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)lua_rawgeti(L, t, FREELIST_REF);//#define FREELIST_REF 0   /* free list of references *///ref=t[FREELIST_REF], 获取一个空闲的keyint ref = (int)lua_tointeger(L, -1);lua_pop(L, -1);if (ref != 0)//ref!=0,说明有空闲key{lua_rawgeti(L, t, ref);lua_rawseti(L, t, FREELIST_REF);//t[FREELIST_REF] = t[ref]}else//ref==0,说明没有空闲位置,需要新建key{ref = (int)lua_objlen(L, t);ref++;//创建一个新的引用}lua_rawseti(L, t, ref);//t[ref]=vreturn ref;
}LUALIB_API void luaL_unref(lua_State* L, int t, int ref)
{if (ref < 0){return;}t = abs_index(L, t);lua_rawgeti(L, t, FREELIST_REF);lua_rawseti(L, t, ref);//t[ref]=t[FREELIST_REF]lua_pushinteger(L, ref);lua_rawseti(L, t, FREELIST_REF);//t[FREELIST_REF]=ref
}

(lapi.c) lua_objlen 获取对象的长度

LUA_API size_t lua_objlen(lua_State* L, int idx)
{StkId o = index2adr(L, idx);switch(ttype(o)){case LUA_TSTRING:{return tsvalue(o)->len;}case LUA_TUSERDATA:{return uvalue(o)->len;}case LUA_TTABLE:{return luaH_getn(hvalue(o));}case LUA_TNUMBER:{size_t l = luaV_tostring(L, o) ? tsvalue(o)->len : 0;return l;}default:{return 0;}}
}

2.4 UpValue

registry表是全局变量的存储,env表是函数内全局变量的存储,UpValue则是函数内静态变量的存储
通过 index2adr(L, int idx) (其中idx<LUA_GLOBALSINDEX)获取C闭包的upvalue地址
至于UpValue的一切,见函数章节。

【lua学习】7.环境相关推荐

  1. Lua学习笔记6:C++和Lua的相互调用

    曾经一直用C++写代码.话说近期刚换工作.项目组中的是cocos2dx-lua,各种被虐的非常慘啊有木有. 新建cocos2dx-lua项目.打开class能够发现,事实上就是C++项目啦,只是为什么 ...

  2. lua学习之类型与值篇

    类型与值 lua 是动态类型的语言 在语言中没有类型定义的语法 每个值都携带有它的类型信息 8种基础类型 用 type 可以返回这个值的类型的名称 将一个变量用于不同类型,通常会导致混乱的代码 但合理 ...

  3. 常用增强学习实验环境 II (ViZDoom, Roboschool, TensorFlow Agents, ELF, Coach等)

    原文链接:http://blog.csdn.net/jinzhuojun/article/details/78508203 前段时间Nature上发表的升级版Alpha Go - AlphaGo Ze ...

  4. lua学习第一课:下载安装(适合windows)、简单程序编译和学习网站推荐

    Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,优点很多.网上看到了一些良莠不齐的教程,现在自己来总结一番. 下载安装     下面介绍其中一种方法,步骤并不复杂,不需要任何环境 ...

  5. Lua语言基础入门 (Lua学习一)

    Lua语言快速入门 这篇博客主要是简单的讲解一下Lua的基础知识,涉及的东西不是很深,提供一些学习的思路,对具体的技术不展开介绍,网上资料很多写的都比我好,仅仅为了使用Lua,而不是做基于Lua的项目 ...

  6. Jenkins持续集成学习-Windows环境进行.Net开发4

    目录 Jenkins持续集成学习-Windows环境进行.Net开发4 目录 前言 目标 Github持续集成 提交代码到Github 从Github更新代码 git上显示构建状态 自动触发构建 Gi ...

  7. lua学习:使用Lua处理游戏数据

    在之前lua学习:lua作配置文件里,我们学会了用lua作配置文件. 其实lua在游戏开发中可以作为一个强大的保存.载入游戏数据的工具. 1.载入游戏数据 比如说,现在我有一份表单: data.xls ...

  8. Lua学习笔记(2)

    前段时间忙于其他事,没有继续Lua的学习,现在继续我们的Lua学习吧. 首先先推荐一本书<Programming in Lua>中文版名称<Lua编程>,这本书从最基础的部分开 ...

  9. 深度学习开发环境调查结果公布,你的配置是这样吗?(附新环境配置) By 李泽南2017年6月26日 15:57 本周一(6 月 19 日)机器之心发表文章《我的深度学习开发环境详解:Te

    深度学习开发环境调查结果公布,你的配置是这样吗?(附新环境配置) 机器之心 2017-06-25 12:27 阅读:108 摘要:参与:李泽南.李亚洲本周一(6月19日)机器之心发表文章<我的深 ...

最新文章

  1. 庆祝法国队夺冠:用Python放一场烟花秀
  2. 只会python好找工作吗-python真的不好找工作吗?
  3. LA4851餐厅(求好的坐标的个数)
  4. python基础学习[python编程从入门到实践读书笔记(连载三)]:django学习笔记web项目
  5. 监测linux一些重要文件md5值脚本
  6. 大工计算机基础在线作业答案,大工1209《计算机应用基础》在线作业123.doc
  7. python 获取路径
  8. js图片上传(配合七牛云)
  9. 【阿里巴巴Java编程规范学习 二】Java基本编程规约(下)
  10. [RK3288][Android6.0] 调试笔记 --- 开机提示mmc rescan错误
  11. 拍牌人数陡增6万人,中标率降至7.8%,我们错过了拍沪牌的黄金期
  12. UE4编辑器界面语言切换
  13. Python | 计算给定数字的平方(3种不同方式)
  14. html 组合快捷键,ctrl常用组合键有哪些
  15. Thinkphp5+JWT开发 api接口
  16. printf中如何输出长整型?
  17. 计算机网络体系结构详解(7层、5层、4层的区别)
  18. HTML小游戏19 —— html5版开心斗地主小游戏(附完整源码)
  19. e2ee连接mysql数据库_E2EE应用服务器套件 - 文档 - [高级功能] 使用数据库连接池 - E2EE易语言网站敏捷开发框架...
  20. 董卿:把这10个气质传给孩子

热门文章

  1. 使用Spring Boot,JHipster和React构建照片库PWA
  2. 回调函数中有回调函数吗_嗨,那里有回调!
  3. Oracle JDBC中的PreparedStatement占位符过多
  4. Java 9:对可选的增强
  5. 拖动滑块拼图背景图没显示_计划B? 那是计划N…没什么。 拼图于2015年问世
  6. rube3xxx_Rube GoldbergSpring整合
  7. 休眠锁定模式–乐观锁定模式如何工作
  8. Java中File的getPath(),getCanonicalPath()和getAbsolutePath()之间的区别
  9. Java的编年史和低延迟
  10. 推土机:将JAXB对象映射到业务/域对象