• ShoneSharp语言(S#)的设计和使用介绍系列(8)— 最炫“公式”风


    ShoneSharp语言(S#)的设计和使用介绍

    系列(8)— 最炫“公式”风

    作者:Shone

    声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp。

    摘要: S#公式是由各种操作数(常量、变量、或子公式)和操作符(算符、函数、属性、方法、或子公式)组合而成,公式和子公式可以形成复杂嵌套结构。S#还在公式级别提供了相当于其他语言语句级别的系统专用公式,使得S#公式表达能力超强,易用性也好,可以说是最为炫酷的公式表达风格。

    软件: S#语言编辑解析运行器(ShoneSharp.13.6.exe),运行环境.NET4.0,单EXE直接运行,绿色软件无副作用。网盘链接https://pan.baidu.com/s/1nv1hmJn

    公式是编程语言最基础的表达结构,所有语言都支持公式。各种语言的公式语法各不相同,表达能力也差别很大。比如LISP公式语法很简单,就是S表达式,但表达能力却非常强大,负面的影响的易用性不好。有的语言则语法比较好用,而表达能力有限。

    S#语言的公式语法符号很多,表达能力非常强,易用性也不错,其综合能力在所有语言中也是领先的,可以说“S#带来了最炫的公式表达风格”。

    对于上述主观论断,看好本博文后有不服者可以来辩。另外语言是相通的,其他语言公式的特性基本上S#都支持,因此其他语言爱好者看一眼本文,也可以提升对自己所学语言的领悟。

    一、公式概念

    公式是由各种操作数(常量、变量、或子公式)和操作符(算符、函数、属性、方法、或子公式)组合而成,可以解析并计算得到数据值。

    由于公式的操作数和操作符都可能是另一个公式构成,因此一个公式可以包含非常复杂的嵌套结构,用来表达或计算各种更加复杂的数据。这一点和LISP语言非常相似。

    S#公式中可以包含注释,注释分行注释//xxx和块注释/*xxx*/,使用方法与C#相同。

    二、常量引用公式

    常量是系统预先定义命名的数据值,在任何上下文中都是保持数据值不变。不同数据类型预定义了不同的常量,具体可以在软件的"所有"面板中查阅。

    常量引用公式是直接引用常量名称,是最简单的公式之一,其计算结果就是系统预定义的数据值(如true、false、null等)。

    三、变量引用公式

    变量是用户自定义命名的数据值,必须先定义后引用。在不同上下文中用户可以定义同名变量,因此变量引用是依赖于上下文的。

    变量的命名规则基本与C#相同,即字母或_开头的包含一道多个字母或数字或_的名字,并排除与系统预定义常量以及其他关键字重复的名字(包括para, eval, if, case, switch, each, eachx, for, forx, dup, dupx, iter, do, sum, prod, integal, load, parse, call, func, class, foreach, while, var, default, return, break, continue, in, else, true, false, null, local, parent, this, base, host, import, include等)。

    变量引用公式是直接引用变量名称,也比较简单,但其计算结果需要在上下文中进行查找。上下文其实指的是变量堆栈链,S#的有些公式(下面会介绍)带有局部变量堆栈,允许定义变量,如果这些公式中又嵌套了其他带有局部变量堆栈的子公式,那么就会形成变量堆栈链。

    变量引用默认会先在当前公式最近的局部变量堆栈查找,如果找不到就会到上一级的变量堆栈,一层层找直至找到为止(类似JavaScript)。看一下代码实例就明白了:

    {
    
        a = 10  ,
    
        b = {
    
              c = 20 ,
    
              d = a + c   //d计算结果为30
    
            }
    
    }
    

    四、运算符号公式

    运算符号(简称算符)是系统预定义的一些操作符符号,用于对操作数进行计算求值。其功能类似与函数调用,只不过写法上有指定的格式,如单目、双目、三目以及其他。

    通常算符都是针对特定数据类型的,但有些算符可以处理多种类型,为便于理解下面从简单类型到复杂类型分开介绍。

    4.1 数值算符  +  -  *  /  %  ^

    数值算符用于对操作数进行数值计算,与其他语言不同,S#可以处理多种类型,另外操作数互换结果可能不同。举例如下:

    -10, -[10,20], -{10,20,30}             //取负
    
    10+3, [10,20]+3, {10,20,30}+3          //加法
    
    10-3, [10,20]-3, {10,20,30}-3          //减法
    
    10*3, [10,20]*3, {10,20,30}*3          //乘法
    
    10/3, [10,20]/3, {10,20,30}/3          //除法
    
    10%3, [10,20] %3, {10,20,30}%3         //取余
    
    10^3, [10,20]^3, {10,20,30}^3          //乘方
    

    4.2 布尔算符  <  >  <=  >=  ==  !=  !  &&  ||

    布尔算符用于对操作数进行计算获得布尔值,基本用法与C#相同。举例如下:

    10<20    //判断是否小于
    
    10>20    //判断是否大于
    
    10<=20   //判断是否小于等于
    
    10>=20   //判断是否大于等于
    
    10==20   //判断是否等于
    
    10!=20   //判断是否不等于
    
    !true                //判断条件取反
    
    true && false        //并且判断,即两个条件是否都成立
    
    true || false        //或者判断,即两个条件是否有一个条件成立
    

    4.3 数组算符  [,]  ,  ~  []  .  $  #  &  |  .. .$ .~ .< .>

    数组是包含同类型数据的一个集合,S#有专用于对数组进行构造和运算的算符。

    [10, 20, 30, 40, 50]     //构造数组的最基本用法
    
    10, 20, 30, 40, 50       //构造数组的简化用法,独立没有冲突时可以省略[]
    
    ~[10, 20, 30, 40, 50]    //数组元素反向重排,结果[50,40,30,20,10]
    
    [10, 20, 30, 40, 50][2]  //索引数组元素,索引号从0开始,可以变量,结果30
    
    [10, 20, 30, 40, 50].2   //索引数组元素,索引号从0开始,必须数字,结果30
    
     [10, 20, 30, 40, 50][2,3,4]  //离散索引多个数组元素,索引号从0开始,可以变量,结果[30,40,50]
    
    [10, 20, 30, 40, 50][2:4]     //连续索引多个数组元素,索引号从0开始,可以变量,结果[30,40,50]
    
    3$10, 2$[20,30]          //整体重复数组元素,结果[10,10,10,20,30,20,30]
    
    3#10, 2#[20,30]          //交错重复数组元素,结果[10,10,10,20,20,30,30]
    
    [10,20,30]&[1,2]         //整体插入数组元素,结果[10,1,2,20,1,2,30]
    
    [10,20,30]|[1,2]         //交错插入数组元素,结果[10,1,20,2,30]
    
    10..15           //构造连续范围数组,结果[10,11,12,13,14,15]
    
    10..15.$2        //构造指定步长的连续范围数组,结果[10,12,14]
    
    10..15.~2        //构造接近步长的等距范围数组,结果[10,11.666666666666666,13.333333333333332,14.999999999999998]
    
    10..15.<2        //构造小于等于步长的等距范围数组,结果[10,11.666666666666666,13.333333333333332,14.999999999999998]
    
    10..15.>2        //构造大于等于步长的等距范围数组,结果[10,12.5,15]
    

    4.4 列表算符  {,}  ~  []  .  $$  ## $$$ ###  &  |  ... .$ .~ .< .>

    列表是可以包含不同类型数据的一个集合,与数组类似,S#有专用于对列表进行构造和运算的算符。

    列表与数组的使用原则:单层同类型尽量用数组,性能更好;多层次数据结构使用列表,表达能力更强。

    {true, 20, 30, 40, 'hjx'}     //构造列表的最基本用法
    
    ~{10, 20, 30, 40, 50}         //数组元素反向重排,结果{50,40,30,20,10}
    
    {true, 20, 30, 40, 'hjx'} [2]  //索引列表元素,索引号从0开始,可以变量,结果30
    
    {true, 20, 30, 40, 'hjx'}.2    //索引列表元素,索引号从0开始,必须数字,结果30
    
    {true, 20, 30, 40, 'hjx'} [2,3,4]  //离散索引多个列表元素,索引号从0开始,可以变量,结果{30,40,'hjx'}
    
    {true, 20, 30, 40, 'hjx'} [2:4]    //连续索引多个列表元素,索引号从0开始,可以变量,结果{30,40,'hjx'}
    
    2$${true, 10, 'hjx'} //整体重复列表元素,结果{true,10,'hjx', true,10,'hjx'}
    
    2##{true, 10, 'hjx'} //交错重复列表元素,结果{True,True,10,10,'hjx','hjx'}
    
    2$$${true, 10, 'hjx'}     //整体多重列表元素,结果{{true,10,'hjx'}, {true,10,'hjx'}}
    
    2###{true, 10, 'hjx'}     //交错多重列表元素,结果{{True,True},{10,10},{'hjx','hjx'}}
    
    {true, 10, 'hjx'}&{1,2}   //整体插入列表元素,结果{True,1,2,10,1,2,'hjx'}
    
    {true, 10, 'hjx'}|{1,2}   //交错插入列表元素,结果{True,1,10,2,'hjx'}
    
    10...15              //构造连续范围列表,结果{10,11,12,13,14,15}
    
    10...15.$2           //构造指定步长的连续范围列表,结果{10,12,14}
    
    10...15.~2          //构造接近步长的等距范围列表,结果{10,11.666666666666666,13.333333333333332,14.999999999999998}
    
    10...15.<2          //构造小于等于步长的等距范围列表,结果{10,11.666666666666666,13.333333333333332,14.999999999999998}
    
    10...15.>2          //构造大于等于步长的等距范围列表,结果{10,12.5,15}
    

    4.5 数据表算符

    数据表是包含一系列键值数据对的集合,注意与其他语言不同,数据表带有局部变量堆栈,其键就是堆栈中的变量名称,而值就是变量值,而且数据表的基类是数据表,这就意味着数据表也可以通过索引进行访问。

    数据表可以很复杂,其中变量的作用范围可以有多种,后面将专题介绍。

    {A=5, B=[1,2], C={10,20,30}}      //构造基本数据表
    
    {A=5, B=[1,2], C={10,20,30}}['B'] //通过键值索引数据表元素,结果[1,2]
    
    {A=5, B=[1,2], C={10,20,30}}.B    //通过属性访问数据表元素,结果[1,2]
    
    {A=5, B=[1,2], C={10,20,30}}[1]   //通过列表索引数据表元素,结果[1,2]
    
    {A=5, B=[1,2], C={10,20,30}}.1    //通过列表索引数据表元素,结果[1,2]
    

    4.6 特殊算符  ()  <>  ??  ?  $  &  *

    (10+20)              //用于对公式进行隔离计算,可以改变其优先级,结果15
    
    (10, 20)             //构造二维点坐标
    
    (10, 20, 30)         //构造三维点坐标
    
    <10, 20>             //构造二维向量
    
    <10, 20, 30>         //构造三维向量
    
    null??10             //非空值计算符号,左侧值为空则取右侧值,结果10
    
    ?’(20+30)/2’     //对字符串解析并求值,结果25
    
    $’c:hjx.shone’  //打开字符串表示的文件,并对其内容解析并求值
    
    &xxx             //获取右侧公式解析树节点的标记引用,类似C#中指针取地址
    
    *xxx             //获取右侧公式解析树节点的结果值引用,类似C#中指针取值
    

    五、面向对象公式

    5.1 属性调用

    属性调用是面向对象的表达方式之一,可以方便表达被调用对象直接暴露的相关信息,其基本格式是:对象.属性名称。

    [10,20,30,40,50].Count           //获取数组的个数,结果5

    注意属性调用优先查找被调用对象的局部变量堆栈,如果没有变量堆栈或找不到,就会去调用该对象类型的系统预定义属性(不同数据类型预定义了不同的方法,具体可以在软件"所有"面板中查阅),如果还没有则报错。例如:

    {A=1,B=2}.Count                 //结果2
    
    {A=1,B=2,Count=100}.Count       //结果100,注意优先调用Count变量而不是列表的属性Count。
    

    理论上属性写法可以用函数代替(如count(xxx)),但属性写法更加简洁直观,可以形成很有特色的链式写法,如A.B.C….,一直点下去。

    5.2 方法调用

    方法也是面向对象的表达方式之一,可以方便表达针对被调用对象的各种操作,其基本格式是:对象.方法名称(参数,…)。

    [10,20,30,40,50].Sub(2)          //从数组[10,20,30,40,50]的索引2位置开始提取子数组,结果[30,40,50]

    注意方法调用也会优先查找被调用对象的局部变量堆栈,如果没有变量堆栈或找不到,就会去调用该对象类型的系统预定义方法(不同数据类型预定义了不同的方法,具体可以在软件"所有"面板中查阅),如果还没有则报错。例如:

    {A=1,B=2}.Sub(1)                      //结果{2}
    
    {A=1,B=2,Sub=x=>10*x}.Sub(1)          //结果10,注意优先调用Sub函数变量而不是列表本身的方法Sub
    

    理论上方法写法也可以用函数代替(如Sub(xxx,2)),但方法比较直观,可以形成很有特色的链式写法,如A.B().C()….,一直点下去。

    六、面向函数公式

    6.1 函数调用

    函数调用是S#公式使用最为广泛的表达方式,其基本格式是:函数名称(参数,…)。其中每个参数又可以是一个子公式,从而可以形成更加复杂的公式嵌套结构。

    最常用的函数是数值函数,注意与其他语言不同,S#数值函数大都支持多种数据类型。例如求余弦函数值:

    cos( 30 )                           //结果0.86602540378443871
    
    cos( [ 10 , 20 , 30 ] )             //结果[0.984807753012208,0.93969262078590843,0.86602540378443871]
    
    cos( { 10 , [ 20 , 30 ] , 40 } )    //结果{0.984807753012208,[0.93969262078590843,0.86602540378443871],0.766044443118978}
    

    注意函数调用也会优先查找当前最近的局部变量堆栈,如果找不到就会到上一级的变量堆栈,一层层往上找,如果还找不到,就会去调用系统预定义函数(不同数据类型预定义了不同的函数,具体可以在软件"所有"面板中查阅),如果还没有则报错。例如:

    {a=1, b=cos(30)}             //结果{a=1,b=0.86602540378443871}
    
    {cos=x=>10*x, b=cos(30)}     //结果{cos=x=>10*x,b=300},注意优先调用cos函数变量而不是求余弦函数值。
    

    6.2 函数定义

    函数式语言强调的“函数是一等公民”,指的是函数自身也可以作为一个变量,即用户可自定义命名的函数变量,也支持先定义后引用,在不同上下文中可以定义同名函数变量。

    上面的x=>10*x其实就是一种匿名的函数定义(与C#类似),由于函数定义有多种形式和高级使用特性,下面一个章节“一等公民函数爱巧”会专门讲函数定义。

    六、系统专用公式

    前面讲的都是其他语言大都有公式表达结构,本节列出的很多是S#特有的表达方式,语法上类似函数调用,但是使用专用关键字和分隔符。这里很多公式其实都等价于其他语言的语句功能了。

    6.1 直接求值公式

    parse/include/call(参数)

    parse('(20+'+'30)/2')        //?算符的增强版,可对变量公式进行字符串解析并求值,结果25
    
    include('c:hjx. '+'shone')     //$算符的增强版,对变量公式进行字符串解析并打开文件,再对其内容解析并求值
    
    call('c'+'os', 30)           //可以通过字符串变量公式,动态调用变量或函数,结果0.5
    

    6.2 顺序求值公式

    eval(局部变量堆栈: 结果公式)

    顺序求值公式通过建立局部变量堆栈并对结果公式进行求值和输出。其中局部变量堆栈有一到多个变量赋值构成,用逗号分割。变量赋值写法是:变量名称=变量公式。结果公式可以直接引用局部变量,若输出数组可以采用省略写法。例如:

    eval(a=1, b=2: a+b)        //结果3
    
    eval(a=1, b=2: a,3$b,a)    //结果[1,2,2,2,1]
    
    eval(a=1, b=2: {a,3$b,a})    //结果{1,[2,2,2],1}
    

    顺序求值公式的使用非常广泛,能力等价于C#中的顺序求值语句{;;return;}。

    6.3 条件求值公式

    if(条件公式? 结果公式1 : 结果公式2)

    条件求值公式先计算条件公式并进行判断,如果为真则对结果公式1进行求值和输出,否则对结果公式2进行求值和输出。条件公式没有变量堆栈,结果公式若输出数组也可以采用省略写法。例如:

    if(10>5? 1: 2)        //结果1
    
    if(10>5? 1,2: 2)      //结果[1,2]
    
    if(10>5? 1;2: 2)      //结果{1,2}
    

    条件求值公式的使用也非常广泛,能力等价于C#中的条件求值公式(?:)或语句(if else)。

    6.4 分支求值公式

    case(数据公式; 分支公式系列 : 缺省结果公式)

    分支求值公式先计算数据公式,然后对分支公式系列的每个分支进行测试,如果等于某个分支公式值,则输出该分支结果公式的计算结果,否则输出缺省结果。每个分支的写法是:分支公式->结果公式,多个分支间用逗号分隔。分支公式没有变量堆栈,结果公式若输出数组也可以采用省略写法。例如:

    case(1+2; 1->5, 3->10: 0)       //结果10
    

    分支求值公式能力等价于C#中的分支求值语句(switch)。

    6.5 判断求值公式

    switch(单个变量赋值; 分支公式系列 : 缺省结果公式)

    判断求值公式先计算变量公式并赋值,然后对分支公式系列的每个分支进行测试,如果某个分支公式值为真,则输出该分支结果公式的计算结果,否则输出缺省结果。与case差别是,switch有变量堆栈,分支公式必须是布尔值且最好引用前面赋值的变量,结果公式若输出数组也可以采用省略写法。例如:

    switch(x=1+2; x<1->5, x>2->10: 0)          //结果10
    

    判断求值公式能力等价于其他语言的模式匹配求值语句。

    6.6循环求值公式

    each/eachx(循环变量配对序列 : 结果元素公式)

    each单重循环求值公式首先建立循环变量堆栈,并把循环变量配对的数据值(通常是数组或列表)逐个循环赋值给变量并对结果元素公式进行求值,最终合并输出为数组或列表。其中循环变量配对写法是:变量名称@数据公式。有多个循环变量配对时用逗号分割,注意每组循环次数以第一个变量为准。循环求值公式有局部变量堆栈,结果公式可以直接引用循环变量,若输出数组可以采用省略写法。另外each表示输出数组,eachx则输出列表。例如:

    each(x@[1,2,3]: 2*x)   //结果[2,4,6]
    
    each(x@1..5: 2*x)      //结果[2,4,6,8,10]
    
    each(x@1..5: x,2*x)    //结果[1,2,2,4,3,6,4,8,5,10]
    
    each(x@1..5, y@5..20: (2*x,y)) //多循环结果[(2,5),(4,6),(6,7),(8,8),(10,9)]
    
    eachx(x@[1,2,3]: 2*x)          //结果{2,4,6}
    
    eachx(x@1..5: 2*x)             //结果{2,4,6,8,10}
    
    eachx(x@1..5: x,2*x)    //结果{[1,2],[2,4],[3,6],[4,8],[5,10]}
    
    eachx(x@1..5: {x,2*x})   //结果{{1,2},{2,4},{3,6},{4,8},{5,10}}
    
    eachx(x@1..5, y@5..20: (2*x,y))   //多循环结果{(2,5),(4,6),(6,7),(8,8),(10,9)}
    

    each循环求值公式能力等价于C#语言的循环语句(foreach),甚至更强。

    each/eachx(循环变量配对序列; 过滤条件公式 : 结果元素公式)

    each单重循环求值公式中如果中间加入过滤条件,那么只输出符合过滤条件结果。例如:

    each(k@1..5;k%2==0: k)        //结果[2,4]
    

    each/eachx(索引变量: 循环变量配对序列 : 结果元素公式)

    each单重循环求值公式中如果前面加入索引变量,那么在每次循环时会自动为索引变量赋值,从0开始,每个循环自动加1。例如:

    each(i: k@1..5: k*10+i)       //结果[10,21,32,43,54]
    

    dup/dupx(循环变量配对序列 : 结果元素公式)

    dup多重循环求值公式语法与each类似,区别是多组循环时会输出多重循环的结果,数据量更多。例如:

    dup(x@1..5, y@5..20: (2*x,y))       //多重循环结果[(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),(2,14),(2,15),(2,16),(2,17),(2,18),(2,19),(2,20),…]
    
    dupx(x@1..5, y@5..20: (2*x,y))      //多重循环结果{{(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,13),(2,14),(2,15),(2,16),(2,17),(2,18),(2,19),(2,20)},… }
    

    for/forx(循环变量堆栈; 循环条件公式; 变量赋值序列 : 结果元素公式)

    for循环求值公式首先建立循环变量堆栈,并执行循环直到不满足条件公式,每次有效循环先对结果元素公式求值再执行循环赋值序列,最终合并输出为数组或列表。其中变量赋值的写法是:变量名称=赋值公式,有多个变量赋值时用逗号分割。for循环求值公式有局部变量堆栈,结果公式可以直接引用循环变量,若输出数组可以采用省略写法。另外for表示输出数组,forx则输出列表。例如:

    for(i=0; i<5; i++: i*2)        //结果[0,2,4,6,8]
    
    for(i=0,j=2; i<5; i++,j+=2: (i*2,j))      //多个变量结果[(0,2),(2,4),(4,6),(6,8),(8,10)]
    
    for(i=0; i<5; i++: for(j=2; j<5; j+=2: (i*2,j))) //多重循环结果[(0,2),(0,4),(2,2),(2,4),(4,2),(4,4),(6,2),(6,4),(8,2),(8,4)]
    

    for循环求值公式能力等价于C#语言的循环语句(for)。

    6.7 迭代求值公式

    iter (结果变量赋值; 循环变量配对序列 : 结果赋值公式)

    iter迭代求值公式首先建立循环变量堆栈,初始化结果变量赋值,并把循环变量配对的数据值(通常是数组或列表)逐个循环赋值给循环变量并对结果赋值公式进行求值,最终输出结果变量的最终值。迭代求值公式有局部变量堆栈,结果赋值公式必须引用结果变量。例如:

    iter(s=0; k@1..5: s+=2*k)    //结果30
    

    iter (结果变量赋值; 索引变量: 循环变量配对序列 : 结果赋值公式)

    iter迭代求值公式中如果中间加入索引变量,那么在每次循环时会自动为索引变量赋值,从0开始,每个循环自动加1。

    iter(s=0; i: k@1..5: s+=i)   //结果10
    

    do(结果变量赋值; 循环变量堆栈; 循环条件公式; 变量赋值序列 : 结果赋值公式)

    do迭代求值公式首先建立循环变量堆栈,并执行循环直到不满足条件公式,每次有效循环先对结果赋值公式求值再执行循环赋值序列,最终输出结果变量的最终值。do迭代求值公式有局部变量堆栈,结果赋值公式必须直接引用结果变量。例如:

    do(s=0; i=0; i<5; i++: s+=i) //结果10
    

    6.8 区间累计公式

    区间累计公式其实可以使用迭代公式替换,只是写法复杂一些。考虑到他们在数值计算中经常使用,因此设置专用公式可以提高表达能力。

    sum(自变量=开始公式,结束公式; 求和公式)

    sum区间累计求和公式首先建立自变量堆栈,并按区间从开始到结束步长为1,逐个循环赋值给自变量并对求和公式进行求值,最终合并输出累加结果。sum公式有局部变量堆栈,结果元素公式可以引用自变量。例如:

    sum(x=1,5: x)        //结果15
    

    prod(自变量=开始公式,结束公式; 求积公式)

    prod区间累计求积公式与sum求和类似,区别是对求积公式结果进行乘法累积。例如:

    prod(x=1,5: x)        //结果120
    

    integal(自变量=开始公式,结束公式; 积分公式)

    prod区间累计积分公式与sum求和类似,区别是结果是针对区间的定积分。注意积分公式不用包含dx。例如:

    integal(x=0,5: 1)        //结果5
    
    integal(x=0,5: x)        //结果12.5
    
    integal(x=0,5: x*x)      //结果41.6666716337204
    

      

    已有计算机专家论证过,编程语言只要具备顺序、条件、循环三大控制语句,其算法表达能力是等价的。S#在公式级别就提供了相当于其他语言语句级别的算法能力,更不用说S#还有语句级别的表达。

    看了本文,您是否同意“S#是最炫酷的公式表达”?!

     

    声明:原创文章欢迎转载,但请注明出处,https://www.cnblogs.com/ShoneSharp。

    软件: S#语言编辑解析运行器(ShoneSharp.13.6.exe),运行环境.NET4.0,单EXE直接运行,绿色软件无副作用。网盘链接https://pan.baidu.com/s/1nv1hmJn

  • 相关阅读:
    Struts初探(二)
    struts2初探(一)
    css样式表设置
    css美化Div边框的样式实例
    CSS中background样式的repeat和no-repeat
    嘘,如何激活更新的win10
    学习向上转型和向下转型的一个好例子
    atom插件安装引发的nodejs和npm安装血案
    Java--Inheritance constructor继承中的构造方法问题(二)
    Java--Inheritance constructor继承中的构造方法问题(一)
  • 原文地址:https://www.cnblogs.com/ShoneSharp/p/ShoneShapr-8.html
Copyright © 2020-2023  润新知