AutoHotkey可以说是一门非常注重符号运用的脚本语言了,在热键那节我们学习了修饰符,这节集中讲解下运算符,运算符有数学运算符、逻辑运算符、位运算符,还有很特别的%(百分号),让人疑惑的,(逗号),猜不出来具体意思的=(等号)……。
所谓符号呢,可以是图形图像、文字组合,也不妨是声音信号、建筑造型,甚至可以是一种思想文化、一个时事人物。例如“=”在数学中是等价的符号,“紫禁城”在政治上是中国古代皇权的象征。总的来说,符号的意思就是一种“特征纪念”,就像绰号是为了让人容易记住,方便辨认的称呼。你记张三李四可能麻烦,但你记“大胡子”、“小眼镜儿”就方便多了,所以符号也可以说是由人的认识习惯造成的。
autohotkey将符号用到了极致,灵活、奇妙。不过不熟悉的话,只能臣妾心里苦了。这节课呢我们就一起来领略一下autohotkey符号的风骚吧!
一、数学运算符 (:=、=、+=、-=、*=、/=、//=、.=、|=、&=、^=、>>=、<<= 、+、++、-、–、*、**、/、//)
+、-、*、/这四个符号属于加、减、乘、除的标准写法,这里不在赘述。
(一)赋值运算符:=
先介绍其中相对好理解的 := 运算符。
:= 和其他语言中 = 用法基本一致,来看几个例子:
a := 123
b := 456.789
c := "AHK中文社区"
d := a
e := a . c
可以看到如果将变量赋值成整数、浮点数、字符串,直接写就好了。我们也可以把一个已经存在的变量赋值给一个新变量。这和其他语言中 = 的用法并无二致。.(点运算符)用来拼接字符串,这个在后续文章中也会单独介绍。
如果我们将一个不存在的变量赋值给一个新变量,像这样:
f := abc
那么 f 将是一个空字符串,也就是和这样效果一样:
f := ""
(二)运算符=(赋值和比较)
简单来说, ( = )赋值是为了字符串而生的,它的眼中没有表达式,它所赋值内容,作用规则与命令式的参数编辑办法 完全相同。
为解决字符串中变量引用的问题,有了( % )这个磨人的小妖精,这也是AHK新手纠结的主要难点。
例如:
Box = (5 = 5)
上例Box得到”(5 = 5)”这个字符串。
Box := (5 = 5)
上例Box得到布尔值结果( 1 ),(:=) 赋值符号之后,此处(=)作为比较运算符使用。
Box = % (5 = 5)
上例Box同样得到( 1 )
这里说明一下( % )符号其中一个用法,当 放置于 ( = )赋值符号后首位,其后参数性质将等同于 ( := ) 赋值符号的应用规则。
Box := % 表达式
( := )赋值这种写法无碍,就是多此一举。
它近似于( := )符号后跟的所有编辑的代码都跟着( “” )双引号,但是( % )符号是原意,故无法在( “” )双引号字符串中使用( % )符号对格式转义或变量取值。
( = ) 赋值的作用范围仅限于:
变量名 = ...
上例这样的单行格式开头,其余情况下(如if后接的表达式),(=) 符号皆视作比较运算符。
它在多行段字符串变量赋值有不错的表现。格式如下。
Box =
(
行1字符串
行2字符串
)
这在( := )多行赋值得是另外的写法如下:
Box := "行1字符串"
. "`n行2字符串"
顺带一提,( “`n” )是 ( ` )转义符对 字母( n ) 的转义,类似c语言中(\n),表示换行符,单行代码欲赋值多行字符串时,两个赋值符的写法分别为:
Box := "行1字符串`n行2字符串"
Box = 行1字符串`n行2字符串
= 后边的内容全部被认为是字符串,看几个例子:
a = 123
b = 456.789
c = AHK中文社区
d = a
e = %a%
f = %a%456
注意这里边的 a 和 b 已经不是整数和浮点数了,而全是字符串。d 的值也不是 123,而是字符串 a。那么是不是 =
只能用来将一个字符串常量赋值给一个变量呢?如果这样的话,= 基本也没有存在的必要了。我们可以看下 e = %a%,里边有一个奇怪的 % 符号(%也是 AHK 中的大坑),%a% 的意思是取 a 变量的值,所以 e 的值是字符串 123,同理 f 的值是字符串123456。是不是 = 比 := 稍微难理解一些?
另外 = 还有一些高级用法,比如将多行文本赋值给一个变量:
a =
(
123
456
789
)
在括号中的多行文本会被赋值给 a,这在某些需要赋值大段文字的场景是非常方便的。
用 = 赋值空字符串也更加简洁:
a =
(其实 a := 这样用也是可以的,虽然看起来怪怪的。)
此外在某些场景只能用 = 赋值,比如处理命令行参数的场景:
argc = %0%
argv1 = %1%
(三)两种赋值方式的使用场景
既然 = 和 := 在多数情况都可以实现相同的功能,什么场景应该使用 =,什么场景应该使用 := 呢?
这个并没有明确的规定,可以因人而异,根据自己的习惯来。但尽量前后统一,以免给自己和别人带来麻烦。
一般来说,使用 := 是更加严谨的,这也是和其他编程语言的用法对齐,更易于被接受。但也并不是弃 = 而不用,在某些场景,比如赋值大段文字,= 还是非常方便的。
(四)赋值运算符拓展
+=、-=、*=、/=、//=、.=、|=、&=、^=、>>=、<<= 这些都是赋值的表达形式. 对变量的内容进行运算, 然后把结果保存到同一个变量中 。
+=、-=、*=和 /=运算符是用变量的值加、减、乘以或除以另一个值的一种简写形式。如,Var*=2
和Var:=Var*2
会得到相同的结果。
//=是向下舍除,例如Var //= 2
执行向下舍除, 把 Var 除以 2, 然后把结果保存回 Var. 同样地, Var .= "abc"
为 Var := Var . "abc"
的一种简写形式。
.、|、&、^、>>、<<这几个运算符在下文中讲解,他们与=连在一起使用的作用与上面的意义是一样的
注意:
- 与其他大多数运算符不同, 赋值运算是从右往左执行的. 因此,
Var1 := Var2 := 0
这个语句中首先把 0 赋值给 Var2, 然后把 Var2 赋值给 Var1。 - 如果使用赋值运算的结果作为其他某些运算符的输出, 那么输入的值是变量自身. 例如, 如果变量 Var 新增值后大于 50, 那么表达式
(Var+=2) > 50
结果为真. 这样还允许赋值被作为 ByRef传递或获取它的地址, 例如:&(x:="abc")。
- 需要避免语法错误或提供更直观的操作时, 会自动提升赋值运算符的优先级. 例如:
not x:=y
等价于not (x:=y)
. 同样地,++Var := X
等价于++(Var := X)
;而Z>0 ? X:=2 : Y:=2
等价于Z>0 ? (X:=2) : (Y:=2)。
由向后兼容引起的已知限制(可能会在未来的版本中解决):
- 当 /= 为表达式中最左边的运算符并且它不是多语句表达式的一部分时, 而且输入都不是浮点数时, 它会执行向下舍除(在其他所有情况中, /= 会执行真除);
- 仅当 += 和 -= 为一行中最左边的运算符时, 它们才支持日期/时间的计算;
- 运算符 +=, -= 和 *= 仅在空变量在一行单独使用时才把空变量视为零; 例如, y:=1, x+=1 和 MsgBox % x-=3 当 x 为空时都得到空的结果。
(五)++、–、**、//
++、–是前置和后置的自增/自减. 从变量中增加或减去 1(但在 1.0.46 之前的版本中, 这些只能在一行上单独使用; 此行中不存在其他运算符). 运算符可以放在变量名的前面或后面. 如果放在变量名的 前面, 则立即执行自增/减运算并把结果用于下一运算. 例如, Var := ++X 让 X 自增后才把它的值赋给 Var. 相反地, 如果运算符放在变量名的 后面, 则在下一运算使用该变量 之后 才对其进行自增/减运算. 例如, Var := X++ 把 X 的当前值赋给 Var 后才进行自增. 由于向后兼容性, 仅当空变量在一行中单独使用时, 运算符 ++ 和 — 才把它们视为零; 例如, y:=1, ++x 和 MsgBox % ++x 当 x 为空时结果都为空. 该运算符用下面的例子来说明,
x=1
y=1
var1:=++x ; 结果为 var1=2, x=2
var2:=y++ ; 结果为 var2=1, y=2
++z ; 空变量单独使用
msgbox % z ; 结果为 z=1
幂(**). 底数和指数都可以为小数. 如果指数为负数, 即使底数和指数都为整数, 结果也会被格式化为浮点数. 因为 ** 的优先级高于一元负号, 所以 -2**2 的计算过程和 -(2**2) 一样且得到结果 -4. 因此, 要让负号的优先级高于幂运算, 需要把它们包围在括号中, 例如 (-2)**2.
注意:
- 不支持底数为负数且指数为小数的情况, 例如
(-2)**0.5
; 它会产生空字符串. 但(-2)**2
和(-2)**2.0
都是支持的. 该运算符用下面的例子来说明: - 与数学上的对应项不同, ** 在 AutoHotkey v1 中是左结合的. 例如, x ** y ** z 计算为 (x ** y) ** z.
msgbox % 2**-2 ; 结果为 0.250000
msgbox % 2**(-2) ; 结果为 0.250000
msgbox % -2**2 ; 结果为 -4
msgbox % -(2**2) ; 结果为 -4
msgbox % (-2)**2 ; 结果为 4
msgbox % (-2)**2.0 ; 结果为 4.000000
msgbox % (-2)**0.5 ; 结果为空
向下舍除(//): 如果两个输入都是整数, 那么双斜杠运算符使用高效的整数除法. 例如, 5//3 结果为 1 而 5//-3 结果为 -1. 如果任何一个输入为浮点数, 则执行浮点除法并把结果往下取整到最近的整数. 例如, 5//3.0 结果为 1.0 而 5.0//-3 结果为 -2.0. 尽管浮点除法的结果为整数, 但它被保存为浮点格式, 以便其他使用者能使用浮点格式. 关于求模运算, 请参阅 mod().
二、逻辑运算符:(>、<、!、=、==、!=、<>、|、||、&、&&)
(一) ! 取反符号, 逻辑非,真值转为假值,假值转为真值。
!和not是逻辑非的两种表达方式,not除了优先级较低外, 其他的与 ! 运算符相同. 例如, not (x = 3 or y = 3)
等同于 !(x = 3 or y = 3)
.
真转假: 将布尔真值计算为假0,如下:
!5
!"中文社区"
假转真: 将布尔假值计算为真1,如下:
!未赋值的变量
!""
!0
以上括号可省,仅为阅读便利用途, ! 取反符常用于开关逻辑。
! 作用于右侧的单项值,它优先级在普通算数运算符之上(也在所有逻辑/比较运算符之上),not 运算符是同样作用,要留心not优先级在算数运算符之下。如:
!1 + 1 ;得 1
not 1 + 1 ;得 0
是优先级次序导致的计算顺序差异, 如下顺序:
!1 + 1
not (1+ 1)
故( ! )取反符号作用于长表达式时,用()可以解决这类麻烦。
!(1 + 1)
可以试着理解下面示例表达式/赋值语句,它初次被激发,变量(开关)是何值,再次激发是何值?
开关 := !开关
(二) && 逻辑与、 || 逻辑或
&& 优先级高于 || ,这两个运算符根据左右两端布尔属性判断取值。
&& 表示该运算符2边都是真值为真(返回1), 有1项为假值则返回假(0),同运算符 and, 建议固定选用其中一种。and和&&是逻辑与的两种表达方式,例如:x > 3 and x < 10。要提高性能, 则要应用求值优化。注意:以 AND/OR/&&/|| (或其他任何运算符) 开始的行会自动 附加到前一行的末尾。
555&& "AutoHotkey中文社区" ;返回1
0 && 666 ;返回0
|| 两个分隔符表示两端有一项是真值即真(1), 两端都为假返回0,同运算符 or。
"中文社区" || 0 ;返回1
0 || "" ;返回0
&& 或 || 连用性质与单独使用相仿, 不做赘述。
... && ... && ... && ...
... || ... || ... || ...
(三)>、<和=、==、<> (!=)比较运算符
从== 说起,它严格匹配左右两端每一位字符,区别大小写。
= 符号作用类似 == , 只不过 = 就字符串匹配忽略大小写差异。
a := "abc"
b := "ABC"
if (a = b)
{
; 条件成立
}
if (a == b)
{
; 条件不成立
}
2.5!= 和 <> 的用法(不等)
在纯数间判断差异不大
005 = 5 ;返回1
005 == 5 ;返回1
2.100 = 2.1 ;返回1
2.100 == 2.1 ;返回1
但是字符串与数型混用比较可能造成麻烦
"005" = 5 ;返回0
"005" == 5 ;返回0
"2.10" = 2.1 ;返回0
"2.10" == 2.1 ;返回0
是( 0 )的锅。
"5" = 5 ;返回1
"5" == 5 ;返回1
"2.1" = 2.1 ;返回1
"2.1" == 2.1 ;返回1
<> 与 != 是同样的用途,包括优先级相等,意思是 不等于,即符号两边值不相等时返回1。
但是对字符串的比较没那么苛刻,将忽略大小写。
若需要严格区分大小写可以参考!(“abc” == “Abc”) 返回1,如:
("Abc" != "abc") . ("Abc" <> "abc") . (!("Abc" =="abc"))
此行表达式返回001,代表这三段表达式的运行结果。
其实大小写的区分还跟StringCaseSense命令有关。
我们需要先了解一个命令:
StringCaseSense, On|Off|Locale
StringCaseSense 用于设置在字符串处理时是否区分大小写。如果设置了 On,就是区分;如果设置了 Off,就是不区分。先不用关注 Locale 参数。默认是 Off。
注意这个命令不影响 = 和 == 的功能。也就是说即使设置了 StringCaseSense, On,用 = 比较字符串还是不区分大小写的。但影响 != 和 <> 的结果:
a := "abc"
b := "ABC"
StringCaseSense, On
if (a != b)
{
; 条件成立
}
StringCaseSense, Off
if (a != b)
{
; 条件不成立
}
这个也并不难理解,因为通常情况我们无需设置 StringCaseSense,比较的结果都是不区分大小写的。
> 大于号、 < 小于号、大于等于 (>=), 和 小于等于 (<=)这样的比较运算符仅用于数型值判断,要注意的是,它与一般双值运算符无异,是成对执行计算的。如果某个输入不是数字, 则按字母顺序比较 (加了引号的原义字符串例如 “55” 在这种情况中总是被当成是非数值的)。仅当命令StringCaseSense打开时, 比较才区分大小写。
如:
10 > 5 > 2
这个表达式在我们一般观念中似乎没有什么错误。
但实际它在AHK的运算流程是:
(10 > 5) > 2
(1) > 2
(0)
布尔相关的运算符计算结果为数型,参与算数表达式无碍,却容易在概念上混淆,无论是逻辑表达式,比较表达式,还是算数与真假值计算的运算符混用的表达式,日常编码中尽可能以()区别开来,如:
(10 > x) && (x > 2)
这个表达式判断变量x是否在2至10这个区间的数值。
上面说到这类运算符常见于if判断,我们仍然可以将布尔性质的表达式赋值于变量中。
由于字符串与数型数值界限暧昧,为避免两者交集的深坑,非必要情况请区别使用。
重要说明:含有表达式的 if 语句与传统的 if 语句(例如 If FoundColor <> Blue
),可以通过单词“if”后是否有开括号来区分。尽管通常把整个表达式包围在括号中,不过也可以写成这样:if (x > 0) and (y > 0)
。此外, 如果单词 “if” 后的第一项为函数调用或类似 “not” 或 “!” 这样的运算符时, 开括号可以完全省略
Shift::
if (Count<1 && A_TimeSincePriorHotkey<400 && A_PriorHotkey = A_ThisHotkey)
{
Count++
}
else
{
Count:=0
}
if Count>0
{
Run notepad
Count:=0
}
return
三、RegExMatch 的简写~=
~= 是对 RegExMatch 的简写。 例如, “abc123” ~= “\d” 会设置 ErrorLevel 为 0 并返回数字 4(首个数字的位置). 在 [v1.1.03] 之前, 此运算符和 等号 (=) 或 (:=) 运算符优先级相同, 但并未在文档中说明.
译者注, ~= 的优先级低于 not(!), 也就是说会先取反再匹配. 下面是一个很深的坑, 请测试下面这个错误的例子:
str := "d"
if (! str ~= "abc"){ ;~ 必须用括号包围才能获得预期的效果 if ! (str ~= "abc")
Msgbox 无法匹配字母 %str% ;~ 不会进入这里
} else {
Msgbox 可以匹配字母 %str% ;~ 出乎意料的结果
}
再来测试, not(!) 与 := 相比优先级更低, 这是一个结果可以预料的例子:
if (!str := "a"){ ;~ 效果同 if ! (str := "a")
Msgbox 不正常: %str% 为 false
}else{
Msgbox 正常: %str% 为 true ;~ 预期的效果
}
四、格式转义符号:百分号 (%)
AHK 中有一个很特别的符号 %。几乎随便从网上下一段 AHK 代码,就可以看到里边有 % 符号,而且 % 在 AHK 里的用法和所有其他编程语言都不一样,基本靠猜的话是猜不出来的。很多人觉得这是一个坑,其实深入理解后,也是一种巧妙的设计。
(一)用法一:%var%
第一种用法我们在介绍赋值表达式的时候接触过:
a = 123
b = %a%
c = %a%456
当用 = 对变量赋值时,如果一个字符串被两个 % 包围,并且中间没有空格,那么含义是取这个变量的值。
在调用一个命令时,也是这样。
a = 123
MsgBox, %a%
如果我们这样用,Name, xx, yy,Name 就是命令;如果我们这样用,Name(“xx”, “yy”),Name 就是函数。命令和函数的区别我们也会在之后的文章了解到,现在只需要关注命令即可。
注意两个%之间只能有一个单独的变量名,像 x[1]、x.y、x[y]、fun() 等一概不支持。
(二)用法二:% var
% 还有另一种用法,这回它只出现一个,并且 % 的后边有至少一个空格。
a = 123
b = % a
MsgBox, % a
这样用表示 % 后边的内容按表达式来解析。我们可以认为:
b = %a%
MsgBox, %a%
和
b = % a
MsgBox, % a
是一样的,实际上后者更强大一些。
a:=1
b:=2
c:=3
; 计算 a + b + c,结果为 6
MsgBox, % a + b + c
; 语法错误
MsgBox, %a + b + c%
;语法正确,但 + 失去计算功能,变成了字符串的一部分 ;
MsgBox, %a% + %b% + %c%
如果 % 后边不只是一个变量名,而是一个表达式,那么“用法二”有效,“用法一”无效。
(三)使用“用法一”还是“用法二”
我们发现“用法一”和“用法二”的功能上是有重叠的,那么实际情况应该怎么用呢?这个还是因人而异,尽量前后一致即可。但在某些场景“用法一”更方便,某些情况“用法二”更方便,甚至只能用“用法二”。
“用法一”更方便的场景:
; 变量本身包含双引号等特殊字符
c = "%a% %b%"
; 多行字符串中包含变量
c =
(
xxx
%a%
%b%
)
“用法二”更方便的场景:
; 字符串中的变量居多
MsgBox, % a " " b " " c " " d
只能用“用法二”的场景:
; 需要使用表达式
MsgBox, % a + b - c
另外如果考虑性能的话,“方法一”是要比“方法二”快二分之一到一倍的,但一般情况瓶颈不应该出现在这里,所以也不用过多在乎性能问题,如果真的是因为在很大的循环里使用而产生性能问题,修改一下也是很轻松的事情。
下面的例子展示了什么时候该使用百分号, 什么时候不该.
; 下面的例子展示了什么时候该使用百分号, 什么时候不该.
Var = Text ; 赋值一些文本给一个变量(传统的).
Number := 6 ; 赋值一个数字给一个变量(表达式).
Var2 = %Var% ; 赋值一个变量给另一个(传统的).
Var3 := Var ; 赋值一个变量给另一个(表达式).
Var4 .= Var ; 追加一个变量到另一个的末尾(表达式).
Var5 += Number ; 将变量的值与另一个相加(表达式).
Var5 -= Number ; 将变量的值减去另一个(表达式).
Var6 := SubStr(Var, 2, 2) ; 变量在函数中. 这总是一个表达式.
Var7 = %Var% Text ; 赋值一个变量给另一个变量并带有一些额外的文本(传统的).
Var8 := Var " Text" ; 赋值一个变量给另一个变量并带有一些额外的文本(表达式).
MsgBox, %Var% ; 变量在命令中.
StringSplit, Var, Var, x ; 在命令中的变量, 但是它们作为输入或输出变量.
if (Number = 6) ; 只要 IF 有括号, 它就是一个表达式, 所以不需要百分号.
if (Var != Number) ; 只要 IF 有括号, 它就是一个表达式, 所以不需要百分号.
if Number = 6 ; 如果没有括号, 那么 IF 是传统的. 不过, 只有赋值语句 "右边" 的变量需要百分号.
if Var1 < %Var2% ; 如果没有括号, 那么 IF 是传统的. 不过, 只有赋值语句 "右边" 的变量需要百分号.
五、拼接运算符:英文句号 .
AHK有一个为字符串准备的 . (英文句号) 运算符,它负责以字符串形式拼接两个数据,它的优先级低于普通的算数运算符。
如下:
aStr := "喂呀"
Box := 5 * 6 . 666 . "哎哟" . aStr
最终结果得到Box值为:
"30666哎哟喂呀"
在表达式中若只以空格分隔数据,如同. 运算符一般将直接被拼接,如:
aStr := "喂呀"
Box := 5 * 6 666 "哎哟" aStr
与上个示例结果一致。
在运用拼接时, 建议把各拼接项目以()括起,没什么特别,可读性嘛。
aStr := "喂呀"
Box := (5 * 6) . (666) . ("哎哟") . (aStr)
同样是结果一致。
在拼接项目过多时,可以分行进行字符串元素的拼接,如:
Box := 5 * 6
. 666
. "哎哟"
. aStr
结果一致。
其中首位拼接元素必须置于 := 赋值符号之后,由第2行开始以( . )拼接符号起头。
注意:这样x.y写时,是作为对象访问的运算符。表示 从对象 x 中读取或设置值或调用其方法, 此处 y 是个原义值。
六、取地址运算符&
取址(&): &MyVar 获取 MyVar 的内容的内存地址, 此地址一般和 DllCall 结构一起使用. 同时 &MyVar 也禁用了此变量中的二进制数的缓存, 如果它经常用于数学或数值比较, 那么这会拖慢其性能. 每当变量的地址改变时会重新启用它的缓存(例如使用 VarSetCapacity()).
七、没有存在感的逗号运算符,
任何命令中的首个逗号可以省略 (除非首个参数为空或以 := 或 = 开始, 或命令单独处于延续片段的顶部). 例如:
MsgBox This is ok.
MsgBox,This is ok.
使用逗号运算符常常比分开写单独的表达式速度更快, 尤其是把一个变量的值赋给另一个变量时(例如: x:=y, a:=b
). 当越多的表达式联合成单个表达式时, 性能会持续得到提升; 例如, 把五个或十个简单的表达式联合成单个表达式速度可能提升 35%.
八、位运算符(~、&、^、|、>>、<<)
九、运算符优先级
此处优先级是定义表达式中计算的顺序。
赋值运算符是一个最低优先级的运算符,*乘法运算符优先级高于:=赋值运算符。
四则运算加减乘除优先级与小学教育所教授的相同,乘除优先于加减。*为乘法运算符,/为除法。
我们可以用()括号决定运算顺序,让你可以在陌生的算数运算符中不至于在优先级上做太多纠缠。
括号内可以是表达式如:
(1 * (3 + 2))
或单独的数值如
(88)
(变量名)
("字符串来一个")
我们阅读代码时, 面对一个较长的表达式。
可以从()括号内部看起。
接着寻找最高优先级的运算符,根据运算符特性求值。
表达式根据运算符优先级依序计算数值,如此顺序往复,直到取得最终值。
相同优先级的运算符则从左至右逐对运算, 而赋值运算符比较特别,它从右至左开始运算。
如:
a := 3 + b := 3 + c := 3
得到a(9) b(6) c(3), 在实际编码赋值语句编写时不建议如此写法,请按部就班以可读性为修养操守。
位运算符为啥要支付才能看啊?
20230201:给位运算增加一个例子
太棒了,正则表达式解决了模糊匹配的问题.
正则没在这
谢谢
谢谢
客气
好复杂啊
谈不上复杂,需要心静
看不懂,,,, ❓