本文版权归 csdn whitehack 所有,转载请自觉标明原创作者及出处,以示尊重!!

作者:whitehack

出处:http://blog.csdn.net/whitehack/article/details/6402779

[-]

  1. Registering Callbacks(注册lua c函数)
  2. Registering Object Dispatch Functors(注册一个c++类到lua)
  3. Registering Functions Directly(直接注册普通函数)
  4. Object Dispatch to Directly Called C++ Member Functions(注册普通c++成员函数)
  5. Unregistering Callbacks(撤销已经注册到lua内的对象)

Registering Callbacks(注册lua c函数)

函数原型

int Callback(LuaState* state);  

作为一种替代机制 lua stack 是通过LuaStack类提供的

LuaPlus的回调函数使用了一种简单的函数机制  可以让全局函数 静态函数 非虚成员函数 与虚成员函数 成为回调函数

下面是一个示例 例子很简单就不注释了 (唯一需要注意的是  LuaStack args(state);)

static int LS_LOG(LuaState* state) { printf("In static function/n"); return 0; } class Logger { public: int LS_LOGMEMBER(LuaState* state) { LuaStack args(state);//需要注意这里哦 printf("In member function. Message: %s/n", args[1].GetString()); return 0; } virtual int LS_LOGVIRTUAL(LuaState* state) { printf("In virtual member function/n"); return 0; } }; LuaObject globalsObj = state->GetGlobals(); globalsObj.Register("LOG", LS_LOG); state->DoString("LOG()"); Logger logger; globalsObj.Register("LOGMEMBER", logger, &Logger::LS_LOGMEMBER); state->DoString("LOGMEMBER('The message')"); globalsObj.Register("LOGVIRTUAL", logger, &Logger::LS_LOGVIRTUAL); state->DoString("LOGVIRTUAL()");  

使用 Register() 函数注册回调函数.

LuaObject 提供了几种重载的 Register() 函数:

void Register( const char* funcName, lua_CFunction function, int nupvalues = 0 ); void Register( const char* funcName, int (*func)(LuaState*), int nupvalues = 0 ); void Register( const char* funcName, const Callee& callee, int (Callee::*func)(LuaState*), int nupvalues = 0 );  

Registering Object Dispatch Functors(注册一个c++类到lua)

虽然Register() 可以注册 c++类成员函数 但是他的第二个参数需要提供一个类对象

但是内部调用成员函数时候 this指针是一个常量  所以 Register()函数不是适合镜像c++类到lua中

this指针的问题  是通过 RegisterObjectFunctor() 函数解决的

官方文档原文


Even though Register() can dispatch to C++ member functions, it uses a 'this' pointer as provided by the second argument passed to the function.  The 'this' pointer is constant, andRegister() is not suited for mirroring class hierarchies in Lua.

The solution to the 'this' pointer issue is through RegisterObjectFunctor().  It is a specialized form ofRegister() where a 'this' pointer isn't provided during the closure registration.  Instead, it is retrieved from either the calling userdata or the calling table's__object member, which must be a full or light userdata.


下面 是实现2个c++类到lua的例子

class MultiObject { public: MultiObject(int num) : m_num(num) { } int Print(LuaState* state) { printf("%d/n", m_num); return 0; } void Print2(int num) { printf("%d %d/n", m_num, num); } protected: int m_num; }; //注册一个metatable LuaObject metaTableObj = state->GetGlobals().CreateTable("MultiObjectMetaTable"); metaTableObj.SetObject("__index", metaTableObj);//设置metatable是他自己 metaTableObj.RegisterObjectFunctor("Print", &MultiObject::Print);//在table内注册一个 print函数 //现在 我们在lua内实现两个对象 obj1 obj2 MultiObject obj1(10); LuaObject obj1Obj = state->BoxPointer(&obj1); obj1Obj.SetMetaTable(metaTableObj); state->GetGlobals().SetObject("obj1", obj1Obj); MultiObject obj2(20); LuaObject obj2Obj = state->BoxPointer(&obj2); obj2Obj.SetMetaTable(metaTableObj); state->GetGlobals().SetObject("obj2", obj2Obj); //两个c++对象已经设置好了 现在测试下 state->DoString("obj1:Print() ; print(obj1)"); state->DoString("obj2:Print() ; print(obj2)"); // 10 // userdata: 002EB940 // 20 // userdata: 002EB9D0 
上面的obj1 和 obj2 是作为  userdata 然后设置它的metatables 的方法创建的 
另一种方法是将代表的c++对象的tuserdata作为表的成员 __object
LuaObject table1Obj = state->GetGlobals().CreateTable("table1"); table1Obj.SetLightUserData("__object", &obj1); table1Obj.SetMetaTable(metaTableObj); LuaObject table2Obj = state->GetGlobals().CreateTable("table2"); table2Obj.SetLightUserData("__object", &obj2); table2Obj.SetMetaTable(metaTableObj); state->DoString("table1:Print()"); state->DoString("table2:Print()");  

Registering Functions Directly(直接注册普通函数)

LuaPlus通过RegisterDirect() 可以直接注册c++函数

float Add(float num1, float num2) { return num1 + num2; } LuaStateOwner state; state->GetGlobals().RegisterDirect("Add", Add); state->DoString("print(Add(10, 5))");  

以这种方式注册的函数  函数可以是任何形式的 内部会自动进行函数参数的类型检查

如果lua调用函数 传入的参数无效 则触发 luaL_argassert  导致调用失败

例如 state->DoString("print(Add(10, 'Hello'))"); 这样就导致失败了

全局函数可以这样注册  而成员函数也同样可以这样注册

void LOG(const char* message) { printf("In global function: %s/n", message); } class Logger { public: void LOGMEMBER(const char* message) { printf("In member function: /n", message); } virtual void LOGVIRTUAL(const char* message) { printf("In virtual member function: %s/n", message); } }; LuaObject globalsObj = state->GetGlobals(); globalsObj.RegisterDirect("LOG", LOG); Logger logger; globalsObj.RegisterDirect("LOGMEMBER", logger, &Logger::LOGMEMBER); globalsObj.RegisterDirect("LOGVIRTUAL", logger, &Logger::LOGVIRTUAL); state->DoString("LOG('Hello')"); state->DoString("LOGMEMBER('Hello')"); state->DoString("LOGVIRTUAL('Hello')");  

直接注册的函数在目前版本中最多只支持7个参数

Object Dispatch to Directly Called C++ Member Functions(注册普通c++成员函数)

官方文档原文


Even though RegisterDirect() can dispatch directly to C++ member functions, it uses a 'this' pointer as provided by the second argument passed to the function.  The 'this' pointer is constant, andRegisterDirect() is not suited for mirroring class hierarchies in Lua.

The solution to the 'this' pointer issue is through RegisterObjectDirect().  It is a specialized form of RegisterDirect() where a 'this' pointer isn't provided during the closure registration.  Instead, it is retrieved from either the calling userdata or the calling table's __object member, which must be a full or light userdata.  The techniques presented in this section mirror closely theRegisterObjectFunctor() description above.


使用上一个注册c++对象的那个示例

向metatable内直接注册普通的c++成员函数

metaTableObj.RegisterObjectDirect("Print2", (MultiObject*)0, &MultiObject::Print2);  

测试

state->DoString("obj1:Print2(5)"); state->DoString("obj2:Print2(15)"); state->DoString("table1:Print2(5)"); state->DoString("table2:Print2(15)");  

Unregistering Callbacks(撤销已经注册到lua内的对象)

注销回调非常简单 只需将其设置为nil

globalsObj.SetNil("LOG");

注册C++函数

当Lua 调用C 函数的时候, 使用和C 调用Lua 相同类型的栈来交互。C 函数从栈中获取她的参数, 调用结束后将返回结果放到栈中。为了区分返回结果和栈中的其他的值, 每个C函数还会返回结果的个数 。这儿有一个重要的概念:用来交互的栈不是全局变量, 每一个函数都有他自己的私有栈。当Lua 调用C 函数的时候,第一个参数总是在这个私有栈的index=1 的位置

LUA中可注册的C函数类型

任何在Lua 中注册的函数必须有同样的原型,这个原型声明定义就是lua.h 中的

lua_CFunction :typedef int (*lua_CFunction) (lua_State *L);

例子

lua_pushcfunction(l, l_sin);
lua_setglobal(l, "mysin");

第一行将类型为function 的值入栈, 第二行将

function 赋值给全局变量mysin

注册任意类型的C函数:

如果要向lua注册一个非lua_CFunction类型的函数,需要:
1. 为该函数实现一个封装调用。
2. 在封装调用函数中从lua栈中取得提供的参数。
3. 使用参数调用该函数。
4. 向lua传递其结果。

首先必须有一个LUA规定类型的C函数,例如:

template<typename Func>
int TempCallFun(lua_State* L)

注意这里有个typename Func,是函数的类型,稍后会讲这个的作用

然后必须在这个函数中调用真正的C函数,这个函数通过栈来传递,LUA中提供了传递用户数据的接口

用户数据

Lua提供了一个函数可以存储用户数据:

LUA_API  void * lua_newuserdata (lua_State *L, size_t size)

在适当的时刻,我们可以通过这个函数再取出这个数据:这样我们可以在注册C++函数时,把这个函数指针当作用户数据压栈,然后在调用TempCallFun时把这个函数取出

LUA_API  void *     lua_touserdata (lua_State *L, int idx)

这里有个关键就是在调用时必须得到正确的参数类型和个数,以正确调用函数并向LUA传递结果,在网上流传的LUA的C++封装中,实现这一功能都是用模板,在TempCallFun中,可以这样调用从栈中取出的函数指针:

buffer = (unsigned char*)lua_touserdata(L,lua_upvalueindex1));//取出用户数据
return Call((*(Func*)buffer),L,1);//调用

注意这个Func就是我们要调用的C++的函数类型,也就是上面说的要把函数指针类型传进来的目的

接下来是Call的其中两个定义

template <typename RT>
int Call(RT (*func)(), lua_State*   L, int index)//匹配没有参数的C++函数
{
     return ReturnType<RT>::Call(func, L, index);
}

template <typename RT, typename P1>
int Call(RT (*func)(P1), lua_State*   L, int index)//匹配有一个参数的C++函数
{
    return ReturnType<RT>::Call(func, L, index);
}

假如有一个 int Test(int a)的C++函数,那么在调用时,就会转到int Call(RT (*func)(P1), lua_State* L, int index)里面,这样我们就可以在这个函数具体处理有一个参数的C++函数的情况,因为参数类型也已经通过模板传进来了,所以可以继续通过模板来取得把栈中的参数转为正确的类型以供C++函数调用,这里有个技巧是封装栈操作:
这里的TypeWrapper<typename T>只是为了传递栈中的参数类型

template<class T> struct TypeWrapper {...};
inline char              Get(TypeWrapper<char>, lua_State* L, int idx)
inline short             Get(TypeWrapper<short>, lua_State* L, int idx)

定义所有类型可能的类型的Get函数,就能方便的取得栈中的元素了,在上面的ReturnType<RT>::Call(func, L, index)里面,可以这样调用真正的C++函数,

RT ReturnVal = (*func)(Get(TypeWrapper<P1>(), L, index + 0))

最后把返回值压栈传给LUA,这样就实现了任意C++函数类型的注册。 注册C++类的成员函数方法一样,只是要把这个类的某个实例也当作用户数据压栈

注册C++类

实现这个要比较复杂,因为LUA并不支持面向对象的特性,要实现这个必须通过一些技巧扩展,LUA中的表就是实现这个功能的媒介,也就是用表模拟C++中类的行为,具体实现方法就不详细说了,大家可以去看LuaTinker的代码,这里只说一下要点

表其实就是一种数据元素的集合,每个元素都有一个索引,用户可通过索引来访问表里的元素

要注册类,关键要做到两点

1、 LUA中的表跟C++中的类的关联,也就是在LUA中构造一个表相应在C++中也必须构造一个类

2、 表中元素跟类中的元素的映射,以得到LUA中的表跟C++中的类的行为的一致性

因为类是自己定义的类型,要实现一个通用的注册类的功能的话,还必须对传递给LUA中的类做一个封装,在LuaTinker中,这个类是:

struct user
{
     user(void* p) : m_p(p) {}

  virtual ~user() {}

   void* m_p;

};

template<typename T>
struct val2user : user
{
      val2user() : user(new T) {} //构造函数没有参数的类

   template<typename T1>      //构造函数有一个参数的类
  
      val2user(T1 t1) : user(new T(t1)) {}

//以此类推。。。。。。。

   ~val2user() { delete ((T*)m_p); }

};

与LUA中的表关联的只是这个val2user,构造一个表就构造一个val2user,在val2user中再构造具体的类

下面是几个在LUA中预定义的事件

The __call Metamethod

这是在创建一个表的时候会触发的事件,可以通过在此事件的元方法中调用类的构造函数,以达到在LUA中创建元表的同时在C++中创建类

LUA中的表有几个比较重要的预定义的错误行为的事件

The __index Metamethod

当我们访问一个表的不存在的域, 返回结果为nil , 这是正确的, 但并不一定正确。实际上, 这种访问触发lua 解释器去查找__index metamethod : 如果不存在, 返回结果为nil ,如果存在则由__index metamethod 返回结果。

The __newindex metamethod

用来对表更新, __index 则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod : 如果存在则调用这个函数而不进行赋值操作。像__index 一样, 如果metamethod 是一个表,解释器对指定的那个表, 而不是原始的表进行赋值操作。

可以通过定义这两个特性的元方法来实现对类中变量的访问和设置,因为userdata是没有元素的,所以访问时一定会触发__index,_newindex元方法,通过设置此元方法既可实现对类以及其基类中变量的访问

The __gc Metamethod

这个元方法只对userdata 类型的值有效。当一个userdatum 将被收集的时候, 并且usedatum 有一个__gc 域, Lua 会调用这个域的值( 应该是一个函数):以userdatum作为这个函数的参数调用。这个函数负责释放与userdatum 相关的所有资源。

可以设置此事件的元方法来析构类

注册C++函数

当Lua 调用C 函数的时候, 使用和C 调用Lua 相同类型的栈来交互。C 函数从栈中获取她的参数, 调用结束后将返回结果放到栈中。为了区分返回结果和栈中的其他的值, 每个C函数还会返回结果的个数 。这儿有一个重要的概念:用来交互的栈不是全局变量, 每一个函数都有他自己的私有栈。当Lua 调用C 函数的时候,第一个参数总是在这个私有栈的index=1 的位置

LUA中可注册的C函数类型

任何在Lua 中注册的函数必须有同样的原型,这个原型声明定义就是lua.h 中的

lua_CFunction :typedef int (*lua_CFunction) (lua_State *L);

例子

lua_pushcfunction(l, l_sin);
lua_setglobal(l, "mysin");

第一行将类型为function 的值入栈, 第二行将

function 赋值给全局变量mysin

注册任意类型的C函数:

如果要向lua注册一个非lua_CFunction类型的函数,需要:
1. 为该函数实现一个封装调用。
2. 在封装调用函数中从lua栈中取得提供的参数。
3. 使用参数调用该函数。
4. 向lua传递其结果。

首先必须有一个LUA规定类型的C函数,例如:

template<typename Func>
int TempCallFun(lua_State* L)

注意这里有个typename Func,是函数的类型,稍后会讲这个的作用

然后必须在这个函数中调用真正的C函数,这个函数通过栈来传递,LUA中提供了传递用户数据的接口

用户数据

Lua提供了一个函数可以存储用户数据:

LUA_API  void * lua_newuserdata (lua_State *L, size_t size)

在适当的时刻,我们可以通过这个函数再取出这个数据:这样我们可以在注册C++函数时,把这个函数指针当作用户数据压栈,然后在调用TempCallFun时把这个函数取出

LUA_API  void *     lua_touserdata (lua_State *L, int idx)

这里有个关键就是在调用时必须得到正确的参数类型和个数,以正确调用函数并向LUA传递结果,在网上流传的LUA的C++封装中,实现这一功能都是用模板,在TempCallFun中,可以这样调用从栈中取出的函数指针:

buffer = (unsigned char*)lua_touserdata(L,lua_upvalueindex1));//取出用户数据
return Call((*(Func*)buffer),L,1);//调用

注意这个Func就是我们要调用的C++的函数类型,也就是上面说的要把函数指针类型传进来的目的

接下来是Call的其中两个定义

template <typename RT>
int Call(RT (*func)(), lua_State*   L, int index)//匹配没有参数的C++函数
{
     return ReturnType<RT>::Call(func, L, index);
}

template <typename RT, typename P1>
int Call(RT (*func)(P1), lua_State*   L, int index)//匹配有一个参数的C++函数
{
    return ReturnType<RT>::Call(func, L, index);
}

假如有一个 int Test(int a)的C++函数,那么在调用时,就会转到int Call(RT (*func)(P1), lua_State* L, int index)里面,这样我们就可以在这个函数具体处理有一个参数的C++函数的情况,因为参数类型也已经通过模板传进来了,所以可以继续通过模板来取得把栈中的参数转为正确的类型以供C++函数调用,这里有个技巧是封装栈操作:
这里的TypeWrapper<typename T>只是为了传递栈中的参数类型

template<class T> struct TypeWrapper {...};
inline char              Get(TypeWrapper<char>, lua_State* L, int idx)
inline short             Get(TypeWrapper<short>, lua_State* L, int idx)

定义所有类型可能的类型的Get函数,就能方便的取得栈中的元素了,在上面的ReturnType<RT>::Call(func, L, index)里面,可以这样调用真正的C++函数,

RT ReturnVal = (*func)(Get(TypeWrapper<P1>(), L, index + 0))

最后把返回值压栈传给LUA,这样就实现了任意C++函数类型的注册。 注册C++类的成员函数方法一样,只是要把这个类的某个实例也当作用户数据压栈

注册C++类

实现这个要比较复杂,因为LUA并不支持面向对象的特性,要实现这个必须通过一些技巧扩展,LUA中的表就是实现这个功能的媒介,也就是用表模拟C++中类的行为,具体实现方法就不详细说了,大家可以去看LuaTinker的代码,这里只说一下要点

表其实就是一种数据元素的集合,每个元素都有一个索引,用户可通过索引来访问表里的元素

要注册类,关键要做到两点

1、 LUA中的表跟C++中的类的关联,也就是在LUA中构造一个表相应在C++中也必须构造一个类

2、 表中元素跟类中的元素的映射,以得到LUA中的表跟C++中的类的行为的一致性

因为类是自己定义的类型,要实现一个通用的注册类的功能的话,还必须对传递给LUA中的类做一个封装,在LuaTinker中,这个类是:

struct user
{
     user(void* p) : m_p(p) {}

  virtual ~user() {}

   void* m_p;

};

template<typename T>
struct val2user : user
{
      val2user() : user(new T) {} //构造函数没有参数的类

   template<typename T1>      //构造函数有一个参数的类
  
      val2user(T1 t1) : user(new T(t1)) {}

//以此类推。。。。。。。

   ~val2user() { delete ((T*)m_p); }

};

与LUA中的表关联的只是这个val2user,构造一个表就构造一个val2user,在val2user中再构造具体的类

下面是几个在LUA中预定义的事件

The __call Metamethod

这是在创建一个表的时候会触发的事件,可以通过在此事件的元方法中调用类的构造函数,以达到在LUA中创建元表的同时在C++中创建类

LUA中的表有几个比较重要的预定义的错误行为的事件

The __index Metamethod

当我们访问一个表的不存在的域, 返回结果为nil , 这是正确的, 但并不一定正确。实际上, 这种访问触发lua 解释器去查找__index metamethod : 如果不存在, 返回结果为nil ,如果存在则由__index metamethod 返回结果。

The __newindex metamethod

用来对表更新, __index 则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod : 如果存在则调用这个函数而不进行赋值操作。像__index 一样, 如果metamethod 是一个表,解释器对指定的那个表, 而不是原始的表进行赋值操作。

可以通过定义这两个特性的元方法来实现对类中变量的访问和设置,因为userdata是没有元素的,所以访问时一定会触发__index,_newindex元方法,通过设置此元方法既可实现对类以及其基类中变量的访问

The __gc Metamethod

这个元方法只对userdata 类型的值有效。当一个userdatum 将被收集的时候, 并且usedatum 有一个__gc 域, Lua 会调用这个域的值( 应该是一个函数):以userdatum作为这个函数的参数调用。这个函数负责释放与userdatum 相关的所有资源。

可以设置此事件的元方法来析构类

Lua注册C++类及函数相关推荐

  1. Lua封装C++实践(三)——Lua注册C++构造函数

    一个std::tuple<int,float,std::string>这样的结构,如何传递给int call(int,float ,std::string)这样的函数作为参数?如何根据函数 ...

  2. Cocos2d-x下Lua调用自定义C++类和函数的最佳实践

    关于cocos2d-x下Lua调用C++的文档看了不少,但没有一篇真正把这事给讲明白了,我自己也是个初学者,摸索了半天,总结如下: cocos2d-x下Lua调用C++这事之所以看起来这么复杂.网上所 ...

  3. Django站点管理、视图和URL(管理界面本地化、创建管理员、注册模型类、发布内容到数据库、定义视图、配置URLconf)

    1.Django站点管理 站点: 分为内容发布和公共访问两部分 内容发布的部分由网站的管理员负责查看.添加.修改.删除数据 Django能够根据定义的模型类自动地生成管理模块 使用Django的管理模 ...

  4. 十五、linux 注册字符类设备和生成节点

    一. 注册字符类设备 • 分配内存空间函数kmalloc         – 分配连续的虚拟地址,用于小内存分配.在include/linux/slab.h文件中.         – 参数1:申请的 ...

  5. 注册窗口类 registerclass

    window把窗口以不同的类别进行设计,通过不同的类别来管理不同的资源,体现模块化管理的思想.以mfc为例Button类别有ccheckbox,cbutton,radiobutton等 ,Static ...

  6. python嵌套类(内部类相互调用)_核心解密Python函数在(类与函数之间)和(类与类之间)互相调用...

    image.png 一.类与函数之间 首先来看一个函数间的调用 类方法: #实现类中函数之间互相调用 #下面一个学生的简单自我介绍为例子 __metaclass__=type #自由的.动态的修改/增 ...

  7. QML < 5 > QML 访问C++ 类 (函数Q_INVOKABLE、枚举Q_ENUMS 、成员变量Q_PROPERTY、自定义结构体QVariantMap、List数据QVariantL

    QML < 5 > QML 访问C++ 类 (函数Q_INVOKABLE.枚举Q_ENUMS .成员变量Q_PROPERTY.自定义结构体(QVariantMap ).List数据QVar ...

  8. C++类成员函数作回调函数

    前面写了一篇文章 C语言消息注册派发模式 介绍了下我理解的C语言消息派发.因为C语言是函数式语言,写回调函数的时候很简单 参数就是一个简单的函数指针就好了, 那在C++里的时候 就有些不一样了,虽然C ...

  9. Ray----Tune(5):Tune包中的类和函数参考

    本篇主要介绍一下tune中常用的一些函数用处,可以作为一个简单的API使用. ray.tune ray.tune.grid_search(values) 用于指定值上的网格搜索的快捷方法. 参数:va ...

最新文章

  1. java里class有什么用_安装JDK时的java和javac命令有什么用?
  2. 用metfanzi识别文字
  3. mysql olap 工具_OLAP分析工具之Presto
  4. mysql表格的代码_mySQL表格内容用代码添加
  5. Win7系统安装MySQL5.5.21图解教程
  6. 火了,挡不住了:Facebook Move编程语言入门
  7. 官方文档翻译-ESP32-High Resolution Timer
  8. 独立成分分析ICA系列4:ICA的最优估计方法综述
  9. PostgreSQL 8.0 中文手册
  10. ionic - error
  11. Linux下的库文件搜索路径
  12. js 图片库 改进版
  13. 【面向对象】面向对象程序设计测试题1-Java语言的发展与特性测试题
  14. 美团取消支付宝支付引关注,称饿了么也不支持微信支付,饿了么回应绝了
  15. DocDokuPLM介绍
  16. android 创建模拟器打不开,解决Android模拟器打不开的问题!...
  17. 有监督学习问题的分类:回归问题和分类问题
  18. java简单小项目_java入门简单小项目有哪些?适合java初学者项目
  19. lisp6 暖通cad_AutoCAD超强小工具(ARKtools)说明
  20. 面向接口编程思想(的好处)

热门文章

  1. 倒数第N个字符串 (15 分)
  2. 数据库学习--DQL(数据库查询语言)
  3. mybatis 依赖于jdbc_第1章 MyBatis快速入门
  4. 【Java】环境变量配置
  5. 车牌识别算法库EasyPR的编译实战
  6. 05-树9 Huffman Codes
  7. Docker 镜像优化与最佳实践
  8. 一年前我在知乎上提了个愚蠢的问题:如何入门 Linux ?
  9. 【李宏毅2020 ML/DL】P5-7 Gradient Descent_1-3
  10. 改造二叉树 (长乐一中模拟赛day2T1)