QLExpression学习使用教程

2023-11-16

文章目录

QLExpress

简介

由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。

为了解决当时电商规则动态编译、表达式高精度计算、复杂布尔运算、自定义函数和操作符号、语法树生成等需求而设计的。

支持特性

  • 1、线程安全,引擎运算过程中的产生的临时变量都是threadlocal类型。
  • 2、高效执行,比较耗时的脚本编译过程可以缓存在本地机器,运行时的临时变量创建采用了缓冲池的技术,和groovy性能相当。
  • 3、弱类型脚本语言,和groovy,javascript语法类似,虽然比强类型脚本语言要慢一些,但是使业务的灵活度大大增强。
  • 4、安全控制,可以通过设置相关运行参数,预防死循环、高危系统api调用等情况。
  • 5、代码精简,依赖最小,250k的jar包适合所有java的运行环境,在android系统的低端pos机也得到广泛运用。

Maven引入

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>QLExpress</artifactId>
  <version>3.2.2</version>
</dependency>

例子github源码

Github地址:QLExpressionStudy

图分解

在这里插入图片描述

从语法树分析、上下文、执行过程三个方面提供二次定制的功能扩展

提示

若要展示其中的编译过程,将log4j.properties中的log4j.logger.com.imooc.mapper改成DEBUG。

例子

初次使用

参见 TestQLExpress 类的 testQLExpression函数:

   		ExpressRunner runner = new ExpressRunner();
        /**
         * 表达式计算的数据注入接口
         */
        DefaultContext<String, Object> context = new DefaultContext<String, Object>();
        context.put("a",1);
        context.put("b",2);
        context.put("c",3);
        String express = "a+b*c";//===> 定义规则,计算表达式等等
        Object r = runner.execute(express, context, null, true, true);// 解析规则+执行规则
        System.out.println(r);

Runner执行器设置

创建ExpressRunner的执行器示例时,可以设置下面两个参数用以达到精度计算。

  • aIsPrecise 是否需要高精度计算支持
  • aIstrace 是否跟踪执行指令的过程

执行器执行命令的设置

针对执行器的execute方法,可以设置如下参数。

  • expressString 程序文本,即要执行的表达式或规则
  • context 执行上下文。【IExpressContext对象(如果是Spring的Bean,则创建SpringBeanContext对象) 表示执行上下文】
  • errorList 输出的错误信息List
  • isCache 是否使用Cache中的指令集,多次执行同一语句的情况下用以提高执行效率
  • isTrace 是否输出详细的执行指令信息
  • log 输出的log

从简单的例子来看,QLExpress的运行过程为:

单词分解–>单词分析–>构建语法树进行语法分析–>生成运行期指令集合–>执行生成的指令集合。

其中前4个过程涉及语法的匹配运算等非常耗时,可以设置execute方法的 isCache 是否使用Cache中的指令集参数,它可以缓存前四个过程。
即把 express语句在本地缓存中换成一段指令,第二次重复执行的时候直接执行指令,极大的提高了性能。

或者ExpressRunner设置成singleton(结合spring是非常容易做到的)。

支持普通的Java语法执行

运算符支持

运算符 备注 示例
+,-,*,/,<,>,<=,>=,==,!=,%,++,– mod等同于% a * b
in,like,&&,! in,like类似于sql语法 a in [1,2,3]
for,break、continue,if then else 不支持try{}catch{}
不支持java8的lambda表达式
不支持for循环集合操作
弱类型语言,请不要定义类型声明,更不要用Templete
array的声明不一样
min,max,round,print,println,like,in 都是系统默认函数的关键字,请不要作为变量名
int n=10;
int sum=0;
for(int i=0;i<n;i++){   sum=sum+i;}
return sum;

运算符分类

分类 运算符 示例
位运算 ^~&|<<>> 1<<2
四则运算 +-*/,%,++,-- 3%2
Boolean运算 !,<,>,<=,>=,==,!=,&&,|| 2==3
其他运算 =,?: 2>3?1:0
示例
n=10;
sum=0;
for(i=0;i<n;i++){   
	sum=sum+i;
}
return sum;

参见 TestQLExpress类的 testExpressForOperater函数。

a=1;
b=2;
maxnum = a>b?a:b;

参见 TestQLExpress类的 testTernaryOperator函数。

部分运算符列举

运算符 描述 运算符 描述
mod %等同 for 循环语句控制符
return 进行值返回 if 条件语句控制符
in 类似sql语句的in then 与if同用
exportAlias 创建别名,并转换为全局别名 else 条件语句控制符
alias 创建别名 break 退出循环操作符
macro 定义宏 continue 继续循环操作符
exportDef 将局部变量转换为全局变量 function 进行函数定义
like 类似sql语句的like new 创建一个对象
import 引入包或类,需在脚本最前头 class 定义类
NewMap 创建Map NewList 创建集合

部分说明:

include:在一个表达式中引入其它表达式。例如: include com.taobao.upp.b; 资源的转载可以自定义接口IExpressResourceLoader来实现,缺省是从文件中装载
[]:匿名创建数组.int[][] abc = [[11,12,13],[21,22,23]];
NewMap:创建HashMap. Map abc = NewMap(1:1,2:2);Map abc = NewMap("a":1,"b":2)
NewList:串接ArrayList.List abc = NewList(1,2,3);
exportDef: 将局部变量转换为全局变量,例如:exportDef long userId
alias:创建别名,例如: alias 用户ID user.userId
macro: 定义宏,例如: macro 降级  {level = level - 1}
in: 操作符号,例如: 3 in (3,4,5)
like:操作符号,例如: "abc" like ‘ab%‘

自定义系统函数

max:取最大值max(3,4,5)
min:最最小值min(2,9,1)
round:四舍五入round(19.08,1)
print:输出信息不换行print("abc")
println:输出信息并换行 println("abc")

样例使用

表达式样例
n=10;
for(sum=0,i=0;i<n;i++){
sum=sum+i;
}
return sum;

参见 TestQLExpress类的 testExpressForOperater 函数。

三目运算符样例
a=1;
b=2;
maxnum = a>b?a:b;

参见 TestQLExpress类的 testTernaryOperator 函数。

数组定义
keys = new ArrayList();
deviceName2Value = new HashMap();
deviceNames = ["ng","si","umid","ut","mac","imsi","imei"];
mins = [5,30];

参见 GrammarTest 类的 TestArrayCreate 函数。

Java对象使用
object.amount*2+object.volume

参见 GrammarTest 类的 testJavaObjectOperate 函数。

import org.envision.tqw.study.BeanDefine.ObjectBean;
aObject = new ObjectBean();
aObject.setAmount(100);
aObject.volume = 20;
System.out.println("Volume:\n"+aObject.getVolume());

参见 GrammarTest 类的 testJavaObject2Operate 函数。

使用注意

  • 对象调用属性赋值实际上是调用相应的Set,故而Set和Get方法不能为私有。其次属性私有并不影响调用。例如 aObject.volume = 20;相当于调用 aObject.setVolume(20);
  • 调用对象,确保能访问到对象,即对象的访问权限。此外,该对象所属类需要先import,而且是在脚本定义之前部分【参考java类的使用,import语句一般位于定义类的文件前面部分】。
  • import 自定义类,默认会导入 import java.lang.*,import java.util.*;
数组遍历
list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
for(i=0;i<list.size();i++){
    item = list.get(i);
    System.out.println(item);
}

参见 GrammarTest 类的 testArrayErgodicTest 函数。

map遍历
map = new HashMap();
map.put("T","tan");
map.put("Q","qi");
map.put("W","wei");
keySet = map.keySet();
objArr = keySet.toArray();
for (i=0;i<objArr.length;i++) {
  key = objArr[i];
  System.out.println(map.get(key));
}

参见 GrammarTest 类的 testMapErgodicTest 函数。

定义函数

示例

function add(int a,int b){
  return a+b;
};

function sub(int a,int b){
  return a - b;
};

a=10;
return add(a,4) + sub(a,9);

参见 FunctionTest 类的 testFunction1 函数。

预定义装载函数定义

装载表达式,但不执行,例如一些宏定义,或者自定义函数。

runner.loadMutilExpress("",funExp);

操作符

中文操作符替换

如果  (语文+数学+英语>270) 则 {return 1;} 否则 {return 0;}

参见 OperatorTest 类的 testOperateReplace 函数。

二元操作符自定义

1 addT 22 addT 2

参见 OperatorTest 类的 testAddBinaryOperate 函数。

二元操作符定义示例

/**
 * @ClassName: BinaryOperator
 * @Description:
 *      定义二元操作符,该操作符实现
 *      a + ( b + b)
 * @Author: qiwei.tan
 * @Date: 2019/8/26 16:05
 * @Version: 1.0
 */
public class BinaryOperator extends Operator {


    public Object executeInner(Object[] list) throws Exception {
        int a = (Integer)list[0];
        int b = (Integer)list[1];
        return a + b + b;
    }
}
自定义操作符需注意
  • 自定义操作符类需继承com.ql.util.express.Operator
  • 二元操作符和多元操作符考虑实现方式的不同

多元操作符

4 addN (1,2,3)

参见 OperatorTest 类的 testNElementOperate 函数。

多元操作符定义示例

/**
 * @ClassName: AddNOperator
 * @Description:
 *      多元操作符
 *      4 addN (1,2,3)
 *      4+1+2+3
 * @Author: qiwei.tan
 * @Date: 2019/8/29 15:35
 * @Version: 1.0
 */
public class AddNOperator extends Operator {

    public Object executeInner(Object[] list) throws Exception {
        int r = 0;
        for(int i=0;i<list.length;i++){
            r = r + (Integer)list[i];
        }
        return r;
    }
}

操作符的使用

1 join 2 join 3
1 + 2 + 3
join(1,2,3)

操作符定义

/**
 * @ClassName: JoinOperator
 * @Description:
 *      自定义连接操作符(二元或多元运算符【支持成函数列表调用】)
 *      1 join 2 join 3 ===> [1,2,3]
 *      返回ArrayList数组
 * @Author: qiwei.tan
 * @Date: 2019/8/29 11:39
 * @Version: 1.0
 */
public class JoinOperator extends Operator {
    public Object executeInner(Object[] list) throws Exception {
        Object opdata1 = list[0];
        Object opdata2 = list[1];
        if(opdata1 instanceof java.util.List){
            ((java.util.List)opdata1).add(opdata2);
            return opdata1;
        }else{
            java.util.List result = new java.util.ArrayList();
            result.add(opdata1);
            result.add(opdata2);
            if(list.length > 2){
                int index = -1;
                for(Object obj:list)
                    if(++index < 2)
                        continue;
                    else
                        result.add(obj);
            }
            return result;
        }
    }
}

官方Readme文件的定义有误,无法支持当变成function时的多元操作符,故而更改。

addOperator

runner.addOperator("join",new JoinOperator());

实现表达式中的词“name”与自定义操作符的绑定

表达式:

1 join 2 join 3

参见 OperatorTest 类的 testOperateUserDefine 函数。

replaceOperator

runner.replaceOperator("+",new JoinOperator());

实现将内置的操作符定义变换为自定义操作符定义

表达式:

1 + 2 + 3

参见 OperatorTest 类的 testOperateUserDefine2 函数。

addFunction

runner.addOperator("join",new JoinOperator());

实现表达式转换为函数的使用,实际上是多元运算符的一种操作

表达式:

join(1,2,3)

参见 OperatorTest 类的 testOperateUserDefine3 函数。

小技巧

本部分不提供相应案例代码

1、提供表达式上下文,属性的值不需要在初始的时候全部加入,而是在表达式计算的时候,需要什么信息才通过上下文接口获取。

避免因为不知道计算的需求,而在上下文中把可能需要的数据都加入。
runner.execute(“三星卖家 and 消保用户”,errorList,true,expressContext) "三星卖家"和"消保用户"的属性是在需要的时候通过接口去获取。

2、可以将计算结果直接存储到上下文中供后续业务使用。例如:

  runner.execute("c = 1000 + 2000",errorList,true,expressContext); 
  //则在expressContext中会增加一个属性c=3000,也可以在expressContext实现直接的数据库操作等。

3、支持高精度浮点运算,只需要在创建执行引擎的时候指定参数即可:new ExpressRunner(true,false);
4、可以将Class和Spring对象的方法映射为表达式计算中的别名,方便其他业务人员的立即和配置。例如

     //将 Math.abs() 映射为 "取绝对值"。
      runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs",new String[] { "double" }, null); 
      runner.execute("取绝对值(-5.0)",null,true,null); 

5、可以为已经存在的boolean运算操作符号设置别名,增加错误信息同步输出,在计算结果为false的时候,同时返回错误信息,减少业务系统相关的处理代码

   runner.addOperatorWithAlias("属于", "in", "用户$1不在允许的范围")//用户自定义的函数同样也可以设置错误信息:例如: 
   runner.addFunctionOfClassMethod("isOk", BeanExample?.class.getName(),"isOk", new String[] { "String" }, "$1 不是VIP用户"); 
  则在调用:
     List errorList = new ArrayList?(); 
     Object result =runner.execute("( (1+1) 属于 (4,3,5)) and isOk("玄难")",errorList,true,null); 
    // 执行结果 result = false.同时在errorList中还会返回2个错误原因: 
    //     1、"用户 2 不在允许的范围"
    //     2、玄难 不是VIP用户 

6、可以自定义函数,自定一个操作函数 group

class GroupOperator extends Operator {
	public GroupOperator(String aName) {
		this.name= aName;
	}
	public Object executeInner(Object[] list)throws Exception {
		Object result = new Integer(0);
		for (int i = 0; i < list.length; i++) {
			result = OperatorOfNumber.Add.execute(result, list[i]);
		}
		return result;
	}
}

则执行:

     runner.addFunction("累加", new GroupOperator("累加"));
     runner.addFunction("group", new GroupOperator("group"));
    //则执行:group(2,3,4)  = 9 ,累加(1,2,3)+累加(4,5,6)=21

7、可以自定操作符号。自定义的操作符号优先级设置为最高。例如自定一个操作函数 love:

class LoveOperator extends Operator {	
	public LoveOperator(String aName) {
		this.name= aName;
	}
	public Object executeInner(Object[] list)
			throws Exception {
		String op1 = list[0].toString();
		String op2 = list[1].toString();
		String result = op2 +"{" + op1 + "}" + op2;		
		return result;
	}
}

然后增加到运算引擎:

 runner.addOperator("love", new LoveOperator("love"));
    //则执行:'a' love 'b' love 'c' love 'd' = "d{c{b{a}b}c}d"

8、可以重载已有的操作符号。例如替换“+”的执行逻辑。
9、可以延迟运算需要的数据
10、一个脚本可以调用其它脚本定义的宏和函数
11、可以类似VB的语法来使用操作符号和函数。print abc; 等价于 print(abc).
12、支持类定义
13、对 in 操作支持后面的是一个数组或者List变量

绑定java类或者对象的method

  • addFunctionOfClassMethod

        /**
         * 添加一个类的函数定义,例如:Math.abs(double) 映射为表达式中的 "取绝对值(-5.0)"
         * @param name 函数名称
         * @param aClassName 类名称
         * @param aFunctionName 类中的方法名称
         * @param aParameterTypes 方法的参数类型名称
         * @param errorInfo 如果函数执行的结果是false,需要输出的错误信息
         * @throws Exception
         */
         public void addFunctionOfClassMethod(String name, String aClassName,
    			String aFunctionName, String[] aParameterTypes, String errorInfo)
    			throws Exception{...}
    
  • addFunctionOfServiceMethod

    /**
     * 用于将一个用户自己定义的对象(例如Spring对象)方法转换为一个表达式计算的函数
     * @param name
     * @param aServiceObject
     * @param aFunctionName
     * @param aParameterTypes
     * @param errorInfo
     * @throws Exception
     */
	public void addFunctionOfServiceMethod(String name, Object aServiceObject,
			String aFunctionName, String[] aParameterTypes, String errorInfo)
			throws Exception

两种方法,无论是何种形式,启用errorInfo一般都应该是boolean类型函数合适。

参见 GrammarTest 类的 testBindingClassOrObjectMethod 函数。

macro 宏定义

测试脚本

(语文+数学+英语)/3.0
计算平均成绩>90
是否优秀

参见 GrammarTest 类的 testMacro 函数。

编译脚本,查询外部需要定义的变量和函数

脚本:

int 平均分 = (语文+数学+英语+综合考试.科目2)/4.0;
return 平均分;

参见 GrammarTest 类的 testVarNeedDef 函数。

关于不定参数的使用

脚本:

getTemplate([11,'22',33L,true])
getTemplate(11,'22',33L,true)

参见 GrammarTest 类的 testMethodReplace 函数。

集合的快捷写法

脚本

abc = NewMap(1:1,2:2); return abc.get(1) + abc.get(2);
abc = NewList(1,2,3); return abc.get(1)+abc.get(2)
abc = [1,2,3]; return abc[1]+abc[2];

参见 GrammarTest 类的 testSetCreateFast 函数。

其他相关

缓存性能比较

参见 PerfomanceTest 类的 testLocalCacheMutualImpact 函数。

检查自定义的java类

running.checkSyntax(String text, boolean mockRemoteJavaClass, List<String> remoteJavaClassNames) 

参见 GrammarTest 类的 testCheckySyntax 函数。

是否将char类型识别为String

runner的setIgnoreConstChar方法。

  • 是否忽略charset类型的数据,而识别为string,比如 'a' -> "a"
  • 默认为忽略,正常识别为String【官方注释此处有误或实现有误】

参见 GrammarTest 类的 testIgnoreConstChar 函数。

类定义

参见 GrammarTest 类的 TestClassDefine 函数和TestClassDefine2 函数。

位相关的定义

参见 GrammarTest 类的 testBitTest 函数。

类需导入才能使用

参见 GrammarTest 类的 testImportClassPath 函数。

关于栈的深度优化

参见 GrammarTest 类的 testStack 函数。

短路逻辑

针对runner的 isShortCircuit 属性,来判断,是否使用逻辑短路特性增强质量的效率

runner.setShortCircuit(false);

参见 GrammarTest 类的 testShortCircuit 函数和 testNoShortCircuit 函数。

场景

场景一

参见 SceneOne 类

在业务系统中存在一些逻辑判断,例如 "商品A"只能是三星卖家,而且已经开通淘宝店铺的用户才能订购
那么我们期望业务人员看到的配置信息就是:“三星卖家 而且 已经开店”
则通过下列步骤可能实现这个功能:

定义一个用户信息对象

class UserInfo {
    long id;
    long tag;
    String name;

    public UserInfo(long aId,String aName, long aUserTag) {
        this.id = aId;
        this.tag = aUserTag;
        this.name = aName;
    }
    public String getName(){
        return name;
    }
    public long getUserId() {
        return id;
    }

    public long getUserTag() {
        return tag;
    }
}

定义两个基础的功能函数

 /**
     * 判断一个用户TAG的第X位是否为1。这个的demo,其实现合理性不考虑
     * @param user
     * @param tagBitIndex
     * @return
     */
    public boolean userTagJudge(UserInfo user,int tagBitIndex){
        boolean r =  (user.getUserTag() & ((long)Math.pow(2, tagBitIndex))) > 0;
        return r;
    }

    /**
     * 判断一个用户是否订购过某个商品
     * @param user
     * @param goodsId
     * @return
     */
    public boolean hasOrderGoods(UserInfo user,long goodsId){
        //随机模拟一个
        if(user.getUserId() % 2 == 1){
            return true;
        }else{
            return false;
        }
    }

初始化语句执行器

	public void initial() throws Exception{
		runner.addOperatorWithAlias("而且","and",null);
		runner.addFunctionOfClassMethod("userTagJudge", SceneOne .class.getName(), "userTagJudge",new String[] {UserInfo.class.getName(),"int"}, "你不是三星卖家");
		runner.addFunctionOfClassMethod("hasOrderGoods", SceneOne .class.getName(), "hasOrderGoods",new String[] {UserInfo.class.getName(),"long"}, "你没有开通淘宝店铺");
		runner.addMacro("三星卖家", "userTagJudge(userInfo,3)");//3表示三星卖家的标志位
		runner.addMacro("已经开店", "hasOrderGoods(userInfo,100)");//100表示旺铺商品的ID
	}

定义一个逻辑判断函数

	/**
	 * 判断逻辑执行函数
	 * @param userInfo
	 * @param expression
	 * @return
	 * @throws Exception
	 */
	public String hasPermission(UserInfo userInfo,String expression) throws Exception {			
        	IExpressContext<String,Object> expressContext = new DefaultContext<String,Object>();
    		expressContext.put("userInfo",userInfo);
    		List<String> errorInfo = new ArrayList<String>();
            Boolean result = (Boolean)runner.execute(expression, expressContext, errorInfo, true, false);
            String resultStr ="";
            if(result.booleanValue() == true){
            	resultStr = "可以订购此商品";
            }else{
              for(int i=0;i<errorInfo.size();i++){
            	  if(i > 0){
            		  resultStr  = resultStr + "," ;
            	  }
            	  resultStr  = resultStr + errorInfo.get(i);
              }
              resultStr = resultStr  + ",所以不能订购此商品";
            }
            return "亲爱的" + userInfo.getName() + " : " + resultStr;
    }	

调用执行器执行判断逻辑

	public static void main(String args[]) throws Exception{
		SceneOne demo = new SceneOne();
		demo.initial();
		System.out.println(demo.hasPermission(new UserInfo(100,"xuannan",7),  "三星卖家   而且   已经开店"));
		System.out.println(demo.hasPermission(new UserInfo(101,"qianghui",8), "三星卖家   而且   已经开店"));
		System.out.println(demo.hasPermission(new UserInfo(100,"张三",8), "三星卖家 and 已经开店"));
		System.out.println(demo.hasPermission(new UserInfo(100,"李四",7), "三星卖家 and 已经开店"));

场景二-审核流程

简单的流程管理,本场景涉及用于展示如何定义表达式,方法,并使用上下文变量。

脚本如下:

如果 (审批通过(经理,金额)){
   如果  (金额  大于 5000){ 
     如果  (审批通过(总监,金额)){
        如果  (审批通过(财务,金额)){
           报销入账(金额)
        }否则  {
            打回修改(申请人)
        }
     }否则 {
        打回修改(申请人)
     }
   }否则  {
      如果  (审批通过(财务,金额)){
        报销入账(金额)
      }否则 {
         打回修改(申请人)
      }
   }
}否则 {
   打回修改(申请人)
}
打印("完成");

查看SceneTwo

场景三-物流分配

非加权:

费用科目(物流订单.仓储TP,"仓储费")= 物流订单.重量 * 0.5 ;

if(物流订单.重量  > 5) then{ 
       费用科目(物流订单.物流TP,"运输费")= 3.0 + (物流订单.重量 - 5 ) * 1.5 ; 
} else { 
      费用科目(物流订单.物流TP,"运输费")= 3.0; 
};
费用科目(物流订单.包装TP,"包装费")= 物流订单.重量 * 2.5 ; 

加权:

费用科目(物流订单.仓储TP,"仓储费")= 物流订单.重量 * 0.5 ;

if(物流订单.重量  > 5) then{
       费用科目(物流订单.物流TP,"运输费")= 3.0 + (物流订单.重量 - 5 ) * 1.5 ;
} else {
      费用科目(物流订单.物流TP,"运输费")= 3.0;
};
费用科目(物流订单.包装TP,"包装费")= 物流订单.重量 * 2.5 ;

查看SceneThree

示例来源于官方文案

总结

  • QLExpress属于弱类型脚本语言,一般不推荐声明局部变量的类型。语法编译阶段不会做类型校验,也不会做方法的参数校验,所以很灵活。
  • QLExpress的这套自定义的指令集属于解析执行,RunEnvironment中通过programPoint函数指针的跳转来实现每条指令的逐个计算,通过dataContainer作为栈来存储中间计算结果。
  • QLExpress定义的指令类型比较少,粒度比较粗,但是足够完成所有的语法功能。
  • QLExpress整个运算过程是通过threadLocal来保证线程安全的。
  • QLExpress的脚本编译过程比较耗时,但是是上下文无关的,所以同一个脚本运行缓存之后性能提升非常明显。
  • QLExpress指令计算运算过程中,基本不会new新对象,是通过缓存池技术来尽量减少资源的消耗。
  • QLExpress的宏,function,Operator,变量是非常开放的,名字可以为中文字符,也可以随意扩展,还可以通过脚本静态分析出包含哪些变量、函数,很方便的进行二次业务开发。
  • 脚本调用classLoader资源的时候可以import,也可以使用类的全路径,构造函数、静态方法、对象方法、类的字段、函数的不定参数调用统统搞定。

参考文献

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QLExpression学习使用教程 的相关文章

随机推荐

  • java串口通讯详解

    序言 说到开源 恐怕很少有人不挑大指称赞 学生通过开源代码学到了知识 程序员通过开源类库获得了别人的成功经验及能够按时完成手头的工程 商家通过开源软件赚到了钱 总之是皆大欢喜 然而开源软件或类库的首要缺点就是大多缺乏详细的说明文档和使用的例
  • 法兰克机械手手动操作_学习FANUC机器人编程设定,必懂这2个技巧!

    原标题 学习FANUC机器人编程设定 必懂这2个技巧 本文由成途机器人编程培训中心推荐 多年来 Fanuc工业机器人在全球机器人销量市场份额中一直处于无可撼动的地位 尤其是在汽车制造行业 在机器人编程培训学习中 不同品牌的工业机器人编程设定
  • 动态规划之01背包问题(最易理解的讲解)

    01背包问题 是用来介绍动态规划算法最经典的例子 网上关于01背包问题的讲解也很多 我写这篇文章力争做到用最简单的方式 最少的公式把01背包问题讲解透彻 01背包的状态转换方程 f i j Max f i 1 j Wi Pi j gt Wi
  • 使用Qt开发VxWorks应用程序

    使用Qt开发VxWorks应用程序 在嵌入式系统开发中 VxWorks是一款广泛使用的实时操作系统 而Qt则是一款跨平台的GUI开发框架 可以帮助开发者快速创建漂亮的用户界面和交互式应用程序 本文介绍如何使用Qt在VxWorks上开发应用程
  • IEEE latex会议模版中 通讯作者的标注不显示解决方法

    在模版备注里有一段说明 conference papers do not typically use thanks and this command is locked out in conference mode If really ne
  • construct2--仿超级马里奥platform游戏

    construct2作为一个简单的游戏制作工具 能为你们带来制作游戏的快乐 接下来我将讲述一下有关construct中platform游戏的制作 学习platform游戏的制作 我们就可以轻松的做出类似超级马里奥的游戏了 下面我将带来一个制
  • python3callable使用_Python callable()函数用法实例分析

    本文实例讲述了Python callable 函数用法 分享给大家供大家参考 具体如下 python中的内建函数callable 可以检查一个对象是否是可调用的 对于函数 方法 lambda 函数式 类 以及实现了 call 方法的类实例
  • AllenNLP框架学习笔记(数据篇之tokenizers)

    tokenizers是数据模块中的一个子模块 在里面主要包含了token与tokenizer的定义和使用 现在做一个简单的介绍 描述字符串是如何载入到TextFields中的 Token 简单的token抽象 其属性包括文本 偏移量 pos
  • 单链表的原地逆置—适用于O(1)空间复杂度

    单链表的原地逆置 一 代码 二 分析 三 验证 一 代码 先放出代码 注 表尾指针 r 按需设置 在单链表的逆置过程中并不重要 void ReverseList LinkList L LNode r L gt next 建立表尾指针 其实可
  • python中怎样将字符转换成asc编码_Python字符和字符值(ASCII或Unicode码值)转换方法...

    这篇文章主要介绍了Python字符和字符值 ASCII或Unicode码值 转换方法 即把字符串在ASCII值或者Unicode值之间相与转换的方法 需要的朋友可以参考下 目的 将一个字符转化为相应的ASCII或Unicode码 或相反的操
  • 【因果推断与机器学习】Causal Inference:Chapter_4_instrument_variables

    Instrument Variables Introduction 我们在因果识别中的目标是找到一种方法 用可观察的统计关系来表达两个特征之间的因果关系 在许多情况下 我们可以使用图形假设和do calculus来理清我们对统计关系的观察
  • 如何在Linux下配置nginx以及docker环境

    nginx环境的配置 环境准备 编译 php fpm环境的搭建 docker环境的配置 什么是docker 在linux中安装docker nginx环境的配置 环境准备 我这里使用的是 centos7 以下操作都是在这个环境下进行执行的
  • JavaScript 逆向调试常用技巧

    1 断点调试 接下来介绍一个非常重要的功能 断点调试 在调试代码的时候 我们可以在需要的位置上打断点 当对应事件触发时 浏览器就会自动停在断点的位置等待调试 此时我们可以选择单步调试 在面板中观察调用栈 变量值 以更好地追踪对应位置的执行逻
  • 100个python算法超详细讲解:分糖果

    1 问题描述 10个小孩围成一圈分糖果 老师分给第1个小孩10块 第2个小孩2块 第3个小 孩8块 第4个小孩22块 第5个小孩16块 第6个小孩4块 第7个小孩10块 第8个小 孩6块 第9个小孩14块 第10个小孩20块 然后所有的小孩
  • 【AI视野·今日CV 计算机视觉论文速览 第215期】Tue, 8 Jun 2021

    AI视野 今日CS CV 计算机视觉论文速览 Tue 8 Jun 2021 showing first 100 of 133 entries Totally 100 papers 上期速览 更多精彩请移步主页 Daily Computer
  • 一文了解语音合成技术(TTS)

    TTS是Text To Speech的缩写 即 从文本到语音 它将计算机自己产生的 或外部输入的文字信息转变为可以听得懂的 流利的汉语口语 或者其他语言语音 输出的技术 隶属于语音合成 SpeechSynthesis 语音 在人类的发展过程
  • 关于深度残差网络(Deep residual network, ResNet)

    深度残差网络 深度残差网络的设计就是为了克服这种由于网络深度加深而产生的学习效率变低 准确率无法有效提升的问题 也称为网络退化 甚至在一些场景下 网络层数的增加反而会降低正确率 这种本质问题是由于出现了信息丢失而产生的过拟合问题 overf
  • conda --init 问题

    初始化编译软件出现下面问题 vscode Windows Visual Studio 2017 Developer Command Prompt v15 9 45 Copyright c 2017 Microsoft Corporation
  • vue2项目window.open打开一个新窗口刷新总回到首页(重定向页面)

    问题描述 使用window open打开新窗口跳转本系统的某一个页面 如 http 192 168 1 206 9529 previewScreen idx 1653966398075965442 刷新总是回到首页 找了很久也没找到解决办法
  • QLExpression学习使用教程

    文章目录 QLExpress 简介 支持特性 Maven引入 例子github源码 图分解 提示 例子 初次使用 Runner执行器设置 执行器执行命令的设置 支持普通的Java语法执行 运算符支持 运算符分类 示例 部分运算符列举 样例使