tolua源码分析(十一)代码生成

news2024/10/7 6:48:54

tolua源码分析(十一)代码生成

上一节我们分析了tolua中struct数据在lua和C#之间传递的过程,这一节我们来看一下tolua自动生成各种辅助代码的流程。

生成所有代码的入口位于ToLuaMenu.cs的GenLuaAll

[MenuItem("Lua/Generate All", false, 5)]
static void GenLuaAll()
{
    if (EditorApplication.isCompiling)
    {
        EditorUtility.DisplayDialog("警告", "请等待编辑器完成编译再执行此功能", "确定");
        return;
    }

    beAutoGen = true;
    GenLuaDelegates();
    AssetDatabase.Refresh();
    GenerateClassWraps();
    GenLuaBinder();
    beAutoGen = false;
}

首先是生成委托的GenLuaDelegates

[MenuItem("Lua/Gen Lua Delegates", false, 2)]
static void GenLuaDelegates()
{
    if (!beAutoGen && EditorApplication.isCompiling)
    {
        EditorUtility.DisplayDialog("警告", "请等待编辑器完成编译再执行此功能", "确定");
        return;
    }

    ToLuaExport.Clear();
    List<DelegateType> list = new List<DelegateType>();
    list.AddRange(CustomSettings.customDelegateList);
    HashSet<Type> set = GetCustomTypeDelegates();        

    foreach (Type t in set)
    {
        if (null == list.Find((p) => { return p.type == t; }))
        {
            list.Add(new DelegateType(t));
        }
    }

    ToLuaExport.GenDelegates(list.ToArray());
    set.Clear();
    ToLuaExport.Clear();
    AssetDatabase.Refresh();
    Debug.Log("Create lua delegate over");
}    

该函数首先对CustomSettings里的customDelegateList所包含的委托类型做处理,这个list里只需放系统内置的委托类型,自定义的委托类型不需要放在这里:

//附加导出委托类型(在导出委托时, customTypeList 中牵扯的委托类型都会导出, 无需写在这里)
public static DelegateType[] customDelegateList = 
{        
    _DT(typeof(Action)),                
    _DT(typeof(UnityEngine.Events.UnityAction)),
    _DT(typeof(System.Predicate<int>)),
    _DT(typeof(System.Action<int>)),
    _DT(typeof(System.Comparison<int>)),
    _DT(typeof(System.Func<int, int>)),
};

DelegateType类主要包含type,strType,name三个成员,它们分别表示委托的类型,对应类型的字符串,以及tolua即将自动生成的类型名称:

public DelegateType(Type t)
{
    type = t;
    strType = ToLuaExport.GetTypeStr(t);                
    name = ToLuaExport.ConvertToLibSign(strType);
}

对于customDelegateList中的委托类型,它们对应的这三个成员,打印如下:

tolua源码分析(十一)1

接下来,GetCustomTypeDelegates函数对CustomSettings里的customTypeList进行扫描,customTypeList包含所有要导出给lua的C#类型,通过扫描得到list中所有C#类型引用(通过field,property,method parameter引用)的委托类型。例如UnityEngine中的Camera类,它包含若干类型为CameraCallback的成员,而CameraCallback是一个委托类型:

namespace UnityEngine
{
    public sealed class Camera : Behaviour
    {
        public static CameraCallback onPreCull;
        public static CameraCallback onPreRender;
        public static CameraCallback onPostRender;
        public delegate void CameraCallback(Camera cam);
    }
}

由于Camera类在customTypeList中,所以这里的CameraCallback也会加入进来。IsDelegateType用于判断一个类型是否为需要加入的委托类型:

public static bool IsDelegateType(Type t)
{
    if (!typeof(System.MulticastDelegate).IsAssignableFrom(t) || t == typeof(System.MulticastDelegate))
    {
        return false;
    }        

    if (IsMemberFilter(t))
    {
        return false;
    }

    return true;
}

public static bool IsMemberFilter(Type t)
{
    string name = LuaMisc.GetTypeName(t);
    return memberInfoFilter.Contains(t) || memberFilter.Find((p) => { return name.Contains(p); }) != null;
}

其中IsMemberFilter函数可以过滤掉我们不想要导出的委托类型,也就是黑名单。

得到所有要导出的委托类型之后,接下来就是正式的生成代码了,我们来看下GenDelegates这个函数,通过前几行代码就能发现,它是用于生成DelegateFactory.cs这个文件的:

public static void GenDelegates(DelegateType[] list)
{        
    ...

    sb.Append("public class DelegateFactory\r\n");
    sb.Append("{\r\n");        
    sb.Append("\tpublic delegate Delegate DelegateCreate(LuaFunction func, LuaTable self, bool flag);\r\n");
    sb.Append("\tpublic static Dictionary<Type, DelegateCreate> dict = new Dictionary<Type, DelegateCreate>();\r\n");
    sb.Append("\tstatic DelegateFactory factory = new DelegateFactory();\r\n");
    sb.AppendLineEx();
    sb.Append("\tpublic static void Init()\r\n");
    sb.Append("\t{\r\n");
    sb.Append("\t\tRegister();\r\n");
    sb.AppendLineEx("\t}\r\n");        
    
    sb.Append("\tpublic static void Register()\r\n");
    sb.Append("\t{\r\n");
    sb.Append("\t\tdict.Clear();\r\n");

    ...
}

我们知道DelegateFactory的作用就是记录不同委托类型所对应的转换,检查和push函数,分别用于将lua栈上的function转换为对应的委托类型,或者检查当前lua栈上的userdata,是否为对应的委托类型,以及将当前委托类型压入到lua栈上。tolua使用4个数据结构来管理这些函数,以System.Action为例:

dict.Add(typeof(System.Action), factory.System_Action);
DelegateTraits<System.Action>.Init(factory.System_Action);
TypeTraits<System.Action>.Init(factory.Check_System_Action);
StackTraits<System.Action>.Push = factory.Push_System_Action;

对应这4句代码的生成逻辑如下:

for (int i = 0; i < list.Length; i++)
{            
    string type = list[i].strType;
    string name = list[i].name;
    sb.AppendFormat("\t\tdict.Add(typeof({0}), factory.{1});\r\n", type, name);            
}

sb.AppendLineEx();

for (int i = 0; i < list.Length; i++)
{
    string type = list[i].strType;
    string name = list[i].name;            
    sb.AppendFormat("\t\tDelegateTraits<{0}>.Init(factory.{1});\r\n", type, name);                        
}

sb.AppendLineEx();

for (int i = 0; i < list.Length; i++)
{
    string type = list[i].strType;
    string name = list[i].name;            
    sb.AppendFormat("\t\tTypeTraits<{0}>.Init(factory.Check_{1});\r\n", type, name);            
}

sb.AppendLineEx();

for (int i = 0; i < list.Length; i++)
{
    string type = list[i].strType;
    string name = list[i].name;
    sb.AppendFormat("\t\tStackTraits<{0}>.Push = factory.Push_{1};\r\n", type, name);            
}

sb.Append("\t}\r\n");

接下来要为这4句代码用到的方法进行生成,为了实现这些方法,还需要先生成辅助的委托类,依旧以System.Action为例:

class System_Action_Event : LuaDelegate
{
    public System_Action_Event(LuaFunction func) : base(func) { }
    public System_Action_Event(LuaFunction func, LuaTable self) : base(func, self) { }

    public void Call()
    {
        func.Call();
    }

    public void CallWithSelf()
    {
        func.BeginPCall();
        func.Push(self);
        func.PCall();
        func.EndPCall();
    }
}

这里的Call和CallWithSelf方法,是对lua的function进行了封装,方便赋值给System.Action对象用的。对应的生成逻辑如下:

//生成委托类
sb.AppendFormat("\tclass {0}_Event : LuaDelegate\r\n", name);
sb.AppendLineEx("\t{");
sb.AppendFormat("\t\tpublic {0}_Event(LuaFunction func) : base(func) {{ }}\r\n", name);
sb.AppendFormat("\t\tpublic {0}_Event(LuaFunction func, LuaTable self) : base(func, self) {{ }}\r\n", name);
sb.AppendLineEx();
sb.AppendFormat("\t\tpublic {0} Call({1})\r\n", GetTypeStr(mi.ReturnType), args);
GenDelegateBody(sb, t, "\t\t");
sb.AppendLineEx();
sb.AppendFormat("\t\tpublic {0} CallWithSelf({1})\r\n", GetTypeStr(mi.ReturnType), args);
GenDelegateBody(sb, t, "\t\t", true);
sb.AppendLineEx("\t}\r\n");

有了辅助类之后,就可以方便地将lua function转换为C#的委托类型:

public System.Action System_Action(LuaFunction func, LuaTable self, bool flag)
{
    if (func == null)
    {
        System.Action fn = delegate() { };
        return fn;
    }

    if(!flag)
    {
        System_Action_Event target = new System_Action_Event(func);
        System.Action d = target.Call;
        target.method = d.Method;
        return d;
    }
    else
    {
        System_Action_Event target = new System_Action_Event(func, self);
        System.Action d = target.CallWithSelf;
        target.method = d.Method;
        return d;
    }
}

这里的生成逻辑如下:

//生成转换函数
sb.AppendFormat("\tpublic {0} {1}(LuaFunction func, LuaTable self, bool flag)\r\n", strType, name);
sb.AppendLineEx("\t{");
sb.AppendLineEx("\t\tif (func == null)");
sb.AppendLineEx("\t\t{");
sb.AppendFormat("\t\t\t{0} fn = delegate({1}) {2}", strType, args, GetDefaultDelegateBody(mi));
sb.AppendLineEx("\t\t\treturn fn;");
sb.AppendLineEx("\t\t}\r\n");
sb.AppendLineEx("\t\tif(!flag)");
sb.AppendLineEx("\t\t{");
sb.AppendFormat("\t\t\t{0}_Event target = new {0}_Event(func);\r\n", name);
sb.AppendFormat("\t\t\t{0} d = target.Call;\r\n", strType);
sb.AppendLineEx("\t\t\ttarget.method = d.Method;");
sb.AppendLineEx("\t\t\treturn d;");
sb.AppendLineEx("\t\t}");
sb.AppendLineEx("\t\telse");
sb.AppendLineEx("\t\t{");
sb.AppendFormat("\t\t\t{0}_Event target = new {0}_Event(func, self);\r\n", name);
sb.AppendFormat("\t\t\t{0} d = target.CallWithSelf;\r\n", strType);
sb.AppendLineEx("\t\t\ttarget.method = d.Method;");
sb.AppendLineEx("\t\t\treturn d;");
sb.AppendLineEx("\t\t}");
sb.AppendLineEx("\t}\r\n");

此外还要生成检查委托类型和push委托类型的代码。这里直接调用已有的方法即可,不需要再额外实现,因此生成逻辑比较简单:

sb.AppendFormat("\tbool Check_{0}(IntPtr L, int pos)\r\n", name);
sb.AppendLineEx("\t{");
sb.AppendFormat("\t\treturn TypeChecker.CheckDelegateType(typeof({0}), L, pos);\r\n", strType);
sb.AppendLineEx("\t}\r\n");

sb.AppendFormat("\tvoid Push_{0}(IntPtr L, {1} o)\r\n", name, strType);
sb.AppendLineEx("\t{");
sb.AppendLineEx("\t\tToLua.Push(L, o);");
sb.AppendLineEx("\t}\r\n");

自此,DelegateFactory的生成就结束了。下面就是为导出到lua的C#类生成对应的wrap类,也就是GenerateClassWraps函数。与委托类似,函数遍历CustomSettings里的customTypeList,它包含了所有要导出给lua的C#类型:

//在这里添加你要导出注册到lua的类型列表
public static BindType[] customTypeList =
{                
    _GT(typeof(Component)),
    _GT(typeof(Transform)),
    _GT(typeof(Material)),
    ...
}

BindType主要包括以下成员:

public class BindType
{
    public string name;                 //类名称
    public Type type;
    public bool IsStatic;        
    public string wrapName = "";        //产生的wrap文件名字
    public string libName = "";         //注册到lua的名字
    public Type baseType = null;
    public string nameSpace = null;     //注册到lua的table层级
}

同样我们也打印下看看具体这些数据长什么样:

tolua源码分析(十一)2

IsStatic字段表示是否作为静态类导出,截图中可以看到UnityEngine.Application和UnityEngine.Physics的IsStatic字段为true,这两个类中的方法和成员基本都是static的,作为静态类导出可以比普通类要节省一些开销,static class在lua层实现中只有一个ref table,而且index和newindex元方法也不会递归查找,因为static class不存在基类的概念。tolua中abstract和sealed的类会自动识别为静态类,如果要手动添加则需要在CustomSettings里的staticClassTypes中添加:

//导出时强制做为静态类的类型(注意customTypeList 还要添加这个类型才能导出)
//unity 有些类作为sealed class, 其实完全等价于静态类
public static List<Type> staticClassTypes = new List<Type>
{        
    typeof(UnityEngine.Application),
    typeof(UnityEngine.Time),
    typeof(UnityEngine.Screen),
    typeof(UnityEngine.SleepTimeout),
    typeof(UnityEngine.Input),
    typeof(UnityEngine.Resources),
    typeof(UnityEngine.Physics),
    typeof(UnityEngine.RenderSettings),
    typeof(UnityEngine.QualitySettings),
    typeof(UnityEngine.GL),
    typeof(UnityEngine.Graphics),
};

接下来就是遍历这个BindType类型的list,对每个type生成对应的wrap文件。这个逻辑在Generate函数中:

public static void Generate(string dir)
{
    sb = new StringBuilder();
    usingList.Add("System");

    if (wrapClassName == "")
    {
        wrapClassName = className;
    }

    if (type.IsEnum)
    {
        BeginCodeGen();
        GenEnum();                                    
        EndCodeGen(dir);
        return;
    }

    InitMethods();
    InitPropertyList();
    InitCtorList();

    BeginCodeGen();

    GenRegisterFunction();
    GenConstructFunction();
    GenItemPropertyFunction();             
    GenFunctions();
    GenIndexFunc();
    GenNewIndexFunc();
    GenOutFunction();
    GenEventFunctions();

    EndCodeGen(dir);
}

BeginCodeGen和EndCodeGen函数的实现比较简单,就是生成一下wrap类的开头和结尾:

static void BeginCodeGen()
{
    sb.AppendFormat("public class {0}Wrap\r\n", wrapClassName);
    sb.AppendLineEx("{");
}

static void EndCodeGen(string dir)
{
    sb.AppendLineEx("}\r\n");
    SaveFile(dir + wrapClassName + "Wrap.cs");
}

先看一下枚举类型的生成,这是在GenEnum函数中完成的,我们以UnityEngine.Space这个枚举为例,看一下最后生成出来的UnityEngine_SpaceWrap文件:

//this source code was auto-generated by tolua#, do not modify it
using System;
using LuaInterface;

public class UnityEngine_SpaceWrap
{
	public static void Register(LuaState L)
	{
		L.BeginEnum(typeof(UnityEngine.Space));
		L.RegVar("World", get_World, null);
		L.RegVar("Self", get_Self, null);
		L.RegFunction("IntToEnum", IntToEnum);
		L.EndEnum();
		TypeTraits<UnityEngine.Space>.Check = CheckType;
		StackTraits<UnityEngine.Space>.Push = Push;
	}

	static void Push(IntPtr L, UnityEngine.Space arg)
	{
		ToLua.Push(L, arg);
	}

	static bool CheckType(IntPtr L, int pos)
	{
		return TypeChecker.CheckEnumType(typeof(UnityEngine.Space), L, pos);
	}

	[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
	static int get_World(IntPtr L)
	{
		ToLua.Push(L, UnityEngine.Space.World);
		return 1;
	}

	[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
	static int get_Self(IntPtr L)
	{
		ToLua.Push(L, UnityEngine.Space.Self);
		return 1;
	}

	[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
	static int IntToEnum(IntPtr L)
	{
		int arg0 = (int)LuaDLL.lua_tonumber(L, 1);
		UnityEngine.Space o = (UnityEngine.Space)arg0;
		ToLua.Push(L, o);
		return 1;
	}
}

枚举wrap类的Register函数固定以BeginEnum打头,以EndEnum结尾,类似地,作为枚举类导出可以比普通类要节省一些开销。枚举不能继承,也不能对其对象进行赋值,因此index元方法不需要递归查找,而newindex元方法是直接禁止触发的。枚举类型中所有满足BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static的字段都会以RegVar的形式注册进来,而RegFunction则只有一个IntToEnum,而且所有的枚举wrap类都只有这一个。IntToEnum的实现很简单,就是把lua栈上的number类型转换为相应的枚举类型,再push到lua栈上。除此之外,wrap类还会生成TypeTraits中用于检查lua栈对象类型的CheckType函数,和将枚举对象压入lua栈的StackTraits的push函数。

对于普通类,则需要通过反射得到该类可以被导出的普通方法,属性,以及构造函数的列表,然后才开始生成Register函数:

static void GenRegisterFunction()
{
    sb.AppendLineEx("\tpublic static void Register(LuaState L)");
    sb.AppendLineEx("\t{");

    if (isStaticClass)
    {
        sb.AppendFormat("\t\tL.BeginStaticLibs(\"{0}\");\r\n", libClassName);            
    }
    else if (!type.IsGenericType)
    {
        if (baseType == null)
        {
            sb.AppendFormat("\t\tL.BeginClass(typeof({0}), null);\r\n", className);
        }
        else
        {
            sb.AppendFormat("\t\tL.BeginClass(typeof({0}), typeof({1}));\r\n", className, GetBaseTypeStr(baseType));
        }
    }
    else
    {
        if (baseType == null)
        {
            sb.AppendFormat("\t\tL.BeginClass(typeof({0}), null, \"{1}\");\r\n", className, libClassName);
        }
        else
        {
            sb.AppendFormat("\t\tL.BeginClass(typeof({0}), typeof({1}), \"{2}\");\r\n", className, GetBaseTypeStr(baseType), libClassName);
        }
    }

    GenRegisterFuncItems();
    GenRegisterOpItems();
    GenRegisterVariables();
    GenRegisterEventTypes();            //注册事件类型

    if (!isStaticClass)
    {
        if (CustomSettings.outList.IndexOf(type) >= 0)
        {
            sb.AppendLineEx("\t\tL.RegVar(\"out\", get_out, null);");
        }

        sb.AppendFormat("\t\tL.EndClass();\r\n");
    }
    else
    {
        sb.AppendFormat("\t\tL.EndStaticLibs();\r\n");
    }

    sb.AppendLineEx("\t}");
}

如果是静态类导出,函数会使用BeginStaticLibs进行注册,如果是普通类则会使用BeginClass;如果普通类有基类,则需要在BeginClass时把基类的类型也传进去。接下来调用的是GenRegisterFuncItems函数,这个函数就是扫描类的导出方法,过滤掉类的重载操作符方法,这部分需要特殊处理。如果类重载了下标操作符,则会根据get/set增加相应的导出方法,同时还会新增一个this函数,用来在lua层下标索引。如果类导出了构造函数,那么还会新增一个New函数用于lua层构造。我们以UnityEngine.Animation这个类为例,看一下GenRegisterFuncItems生成的部分:

L.RegFunction("Stop", Stop);
L.RegFunction("Rewind", Rewind);
L.RegFunction("Sample", Sample);
L.RegFunction("IsPlaying", IsPlaying);
L.RegFunction("get_Item", get_Item);
L.RegFunction("Play", Play);
L.RegFunction("CrossFade", CrossFade);
L.RegFunction("Blend", Blend);
L.RegFunction("CrossFadeQueued", CrossFadeQueued);
L.RegFunction("PlayQueued", PlayQueued);
L.RegFunction("AddClip", AddClip);
L.RegFunction("RemoveClip", RemoveClip);
L.RegFunction("GetClipCount", GetClipCount);
L.RegFunction("SyncLayer", SyncLayer);
L.RegFunction("GetEnumerator", GetEnumerator);
L.RegFunction("GetClip", GetClip);
L.RegFunction("New", _CreateUnityEngine_Animation);
L.RegVar("this", _this, null);

我们看到这里导出的方法有get_Itemthis,但是没有set_Item,是因为UnityEngine.Animation类中重载了下标操作符,它只有get属性:

public sealed class Animation : Behaviour, IEnumerable
{
    public Animation();
    public AnimationState this[string name] { get; }
    ...

}

然后我们看一下GenRegisterOpItems函数,这个函数就是统计了C#重载的运算符和ToString方法,然后对应到lua层的元方法上去,还是以UnityEngine.Animation类为例,这里生成了两个导出函数:

L.RegFunction("__eq", op_Equality);
L.RegFunction("__tostring", ToLua.op_ToString);

GenRegisterVariables函数就是对C#类中要导出的属性和字段进行扫描,如果是属性需要看一下是否可以get和set,只生成可以访问的导出方法:

L.RegVar("clip", get_clip, set_clip);
L.RegVar("playAutomatically", get_playAutomatically, set_playAutomatically);
L.RegVar("wrapMode", get_wrapMode, set_wrapMode);
L.RegVar("isPlaying", get_isPlaying, null);
L.RegVar("animatePhysics", get_animatePhysics, set_animatePhysics);
L.RegVar("cullingType", get_cullingType, set_cullingType);
L.RegVar("localBounds", get_localBounds, set_localBounds);

可以看到这里isPlaying没有导出set方法,因为Animation类的isPlaying只有get属性:

public sealed class Animation : Behaviour, IEnumerable
{
    public Bounds localBounds { get; set; }
    public bool playAutomatically { get; set; }
    public AnimationCullingType cullingType { get; set; }
    public AnimationClip clip { get; set; }
    public WrapMode wrapMode { get; set; }
    public bool animatePhysics { get; set; }
    [Obsolete("Use cullingType instead")]
    public bool animateOnlyIfVisible { get; set; }
    public bool isPlaying { get; }
    ...

}

最后是GenRegisterEventTypes这个函数,这个函数负责将C#类中定义的委托类型导出为函数,这样lua层就可以通过这个函数构造出对应的委托,例如UnityEngine.Application类中有如下这些委托类型:

public class Application
{
    ...
    public delegate void AdvertisingIdentifierCallback(string advertisingId, bool trackingEnabled, string errorMsg);
    public delegate void LowMemoryCallback();
    public delegate void MemoryUsageChangedCallback(in ApplicationMemoryUsageChange usage);
    public delegate void LogCallback(string condition, string stackTrace, LogType type);
}

对应生成的导出函数如下:

L.RegFunction("AdvertisingIdentifierCallback", UnityEngine_Application_AdvertisingIdentifierCallback);
L.RegFunction("LogCallback", UnityEngine_Application_LogCallback);
L.RegFunction("MemoryUsageChangedCallback", UnityEngine_Application_MemoryUsageChangedCallback);
L.RegFunction("LowMemoryCallback", UnityEngine_Application_LowMemoryCallback);

在所有的导出类都生成完毕之后,还有一步就是要生成LuaBinder类。这个类提供了唯一的Bind方法,用来初始化所有导出类的注册逻辑,大致分为三个部分:

public static void Bind(LuaState L)
{
    L.BeginModule("UnityEngine");
    UnityEngine_ComponentWrap.Register(L);
    L.EndModule();

    L.BeginModule("System");
    L.RegFunction("Action", System_Action);
    L.EndModule();

    L.BeginPreLoad();
    L.AddPreLoad("UnityEngine.MeshRenderer", LuaOpen_UnityEngine_MeshRenderer, typeof(UnityEngine.MeshRenderer));
    L.EndPreLoad();
}

第一部分就是普通导出类的注册过程,第二部分是对所有用到的委托类型增加导出函数,这个与上文类似,就是方便lua层直接构造委托,最后一个部分是preload,实际上是一个延迟加载逻辑,这里面的导出类,不会在Bind的时候就注册,而是在lua层调用require的时候,才会注册进来。延迟导出的类配置在CustomSettings里的dynamicList中:

public static List<Type> dynamicList = new List<Type>()
{
    typeof(MeshRenderer),

    typeof(BoxCollider),
    typeof(MeshCollider),
    typeof(SphereCollider),
    typeof(CharacterController),
    typeof(CapsuleCollider),

    typeof(Animation),
    typeof(AnimationClip),
    typeof(AnimationState),

    typeof(SkinWeights),
    typeof(RenderTexture),
    typeof(Rigidbody),
};

自此,整个tolua的框架基本上算是梳理完了。

如果你觉得我的文章有帮助,欢迎关注我的微信公众号 我是真的想做游戏啊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1017704.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

达梦数据库-DW-国产化--九五小庞

武汉达梦数据库股份有限公司成立于2000年&#xff0c;是国内领先的数据库产品开发服务商&#xff0c;国内数据库基础软件产业发展的关键推动者。公司为客户提供各类数据库软件及集群软件、云计算与大数据等一系列数据库产品及相关技术服务&#xff0c;致力于成为国际顶尖的全栈…

读取yaml文件的值

记录一下&#xff0c;读取yaml文件中属性的值&#xff0c;这里用Kubernetes的deployment.yaml文件来举例。 读取yaml文件中的image的值 yaml文件 apiVersion: apps/v1 # 1.9.0 之前的版本使用 apps/v1beta2&#xff0c;可通过命令 kubectl api-versions 查看 kind: Deploy…

获取中文词组的汉语拼音首字母拼接

我们需要一个快捷批量处理&#xff1a;中文词组获取其汉语拼音首字母并拼接起来。 比如&#xff1a; 输出功率3&#xff1a;SCGL3 一鸣惊人&#xff1a;YMJR 我们可以采用字符字典法&#xff0c;穷举出所有的汉字【暂只考虑简体中文】 Dictionary<char,string> dict…

【数据分享】2006-2021年我国省份级别的市容环境卫生相关指标(20多项指标)

《中国城市建设统计年鉴》中细致地统计了我国城市市政公用设施建设与发展情况&#xff0c;在之前的文章中&#xff0c;我们分享过基于2006-2021年《中国城市建设统计年鉴》整理的2006—2021年我国省份级别的市政设施水平相关指标、2006-2021年我国省份级别的各类建设用地面积数…

【C++】STL—— unordered_map的介绍和使用、 unordered_map的构造函数和迭代器、 unordered_map的增删查改函数

文章目录 1. unordered_map的介绍2. unordered_map的使用2.1unordered_map的构造函数2.2unordered_map的迭代器2.3unordered_map的容量和访问函数2.4unordered_map的增删查改函数 1. unordered_map的介绍 unordered_map的介绍 &#xff08;1&#xff09;unordered_map是存储&l…

ElasticSearch(ES)简答了解

ES简介 Elasticsearch&#xff08;通常简称为ES&#xff09;是一个开源的分布式搜索和分析引擎&#xff0c;旨在处理各种类型的数据&#xff0c;包括结构化、半结构化和非结构化数据。它最初是为全文搜索而设计的&#xff0c;但随着时间的推移&#xff0c;它已经演变成一个功能…

web系统安全设计原则

一、前言 近日&#xff0c;针对西工大网络被攻击&#xff0c;国家计算机病毒应急处理中心和360公司对一款名为“二次约会”的间谍软件进行了技术分析。分析报告显示&#xff0c;该软件是美国国家安全局&#xff08;NSA&#xff09;开发的网络间谍武器。当下&#xff0c;我们发现…

【骑行之旅】昆明草海湿地公园和海晏村的美丽邂逅

这是一个九月的星期六&#xff0c;在昆明的大观公园门口&#xff0c;我们集合了一群热爱骑行的骑友。今天&#xff0c;阳光明媚&#xff0c;天空湛蓝&#xff0c;一切都充满了活力。我们的旅程从这里开始&#xff0c;一路向西&#xff0c;向着下一站&#xff0c;美丽的草海湿地…

虚拟机用户切换及设置root权限的密码

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

驱动开发,IO多路复用(select,poll,epoll三种实现方式的比较)

1.IO多路复用介绍 在使用单进程或单线程情况下&#xff0c;同时处理多个输入输出请求&#xff0c;需要用到IO多路复用&#xff1b;IO多路复用有select/poll/epoll三种实现方式&#xff1b;由于不需要创建新的进程和线程&#xff0c;减少了系统资源的开销&#xff0c;减少了上下…

从0到1搭建Halo博客系统教程

前期准备 云服务器&#xff0c;域名&#xff0c;命令工具&#xff08;这里使用是Mobaxterm&#xff09; 安装环境 宝塔面板 yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec在命令工…

【计算机视觉】Image Generation Models算法介绍合集

文章目录 一、Diffusion二、Guided Language to Image Diffusion for Generation and Editing&#xff08;GLIDE&#xff09;三、classifier-guidance四、Blended Diffusion五、DALLE 2六、AltDiffusion七、Group Decreasing Network八、Make-A-Scene九、Iterative Inpainting十…

c++ - 抽象类 和 使用多态当中一些注意事项

抽象类 纯虚函数 在虚函数的后面写上 0 &#xff0c;则这个函数为纯虚函数。 class A { public:virtual void func() 0; }; 纯虚函数不需要写函数的定义&#xff0c;他有类似声明一样的结构。 抽象类概念 我们把具有纯虚函数的类&#xff0c;叫做抽象类。 所谓抽象就是&a…

docker gitlab+jenkins搭建

一&#xff1a;gitlab搭建: 1&#xff1a;docker部署 2&#xff1a;修改root密码 3&#xff1a;创建普通账户 4&#xff1a;设置sshken 二&#xff1a;jenkins搭建 配置脚本 bash -x /var/jenkins_home/shell/game01.sh

图解数据结构

&#x1f31e;欢迎来到数据结构的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2023年9月17日&…

【探索Linux】—— 强大的命令行工具 P.8(进程地址空间)

阅读导航 前言一、内存空间分布二、什么是进程地址空间1. 概念2. 进程地址空间的组成 三、进程地址空间的设计原理1. 基本原理2. 虚拟地址空间 概念 大小和范围 作用 虚拟地址空间的优点 3. 页表 四、为什么要有地址空间五、总结温馨提示 前言 前面我们讲了C语言的基础知识&am…

性能测试-性能调优主要方向和原则(15)

性能调优主要方向明确性能瓶颈之后,就需要进行性能调优了,调优主要从图所示的多个方向入手。能优化手段并不一定是独立应用的,在一次优化过程中很可能应用了多种优化技巧。 硬件层面优化 硬件层面优化更偏向于监控,当定位到硬件资源成为瓶颈后,更多是采用扩容等手段来解决…

代码随想录算法训练营第三十六天| 435. 无重叠区间 763.划分字母区间 56. 合并区间

今天的三道题目&#xff0c;都算是 重叠区间 问题&#xff0c;大家可以好好感受一下。 都属于那种看起来好复杂&#xff0c;但一看贪心解法&#xff0c;惊呼&#xff1a;这么巧妙&#xff01; 还是属于那种&#xff0c;做过了也就会了&#xff0c;没做过就很难想出来。 不过大…

synchronized实战:synchronized 锁升级过程

下面程序通过对加锁前后Object对象字节码的打印验证了对象由无锁到偏向锁的过程。 public class T01 {public static void main(String[] args) {Object o new Object();System.out.println(ClassLayout.parseInstance(o).toPrintable());o.hashCode();System.out.println(Cl…

Linux界的老古董

Slackware 是由 Patrick Volkerding 制作的 Linux 发行版&#xff0c;从 1993 年发布至今也一直在 Patrick 带领下进行维护。7 月 17 日&#xff0c;Slackware 才刚刚过完它 24 岁的生日&#xff0c;看似年纪轻轻的它&#xff0c;已然是 Linux 最古老的发行版。 Slackware 的发…