VBA:为什么逻辑非运算符会停止工作?

41

这让我彻底困惑了。

Sub testChangeBoolean()
  Dim X As Boolean       ' default value is False
  X = True               ' X is now True
  X = Not X              ' X is back to False
End Sub

但我正在尝试在Word中切换表格单元格(cell)的 .FitText 属性。.FitText 起始值为True。

如果我将其分配给X:

Sub testChangeBoolean()
  Dim X As Boolean                  ' again, default is False
  X = Selection.Cells(1).FitText    ' does set X to True
  X = Not X                         ' X is still True!
End Sub

我就是不明白我做错了什么。


回复黄金徽章标记的重复关闭:虽然其他问题讨论了Not不按预期工作的情况,但它没有处理这里行为背后的原因:Word对象模型不遵循VBA规则。在链接的重复问题中完全缺少对该方面的讨论。 - Cindy Meister
2
@Cindy,“为什么”在两种情况下完全相同。这是Visual Basic和VBA的一个经典问题。 False为0(所有位都清除),而True定义为Not False,这意味着-1(所有位都设置)。这也会导致WinAPI互操作问题。任何非零值都被视为“True”,因此只有在开始操作位并且不了解正在发生的情况,并且缺乏位/逻辑运算符之间区别时才会出现问题。当根本原因和解决方案基本相同时,我不认为重新打开此问题有任何价值。 - Cody Gray
@CindyMeister 如果这里没有已经解释过是Word对象模型生成了畸形的“Boolean”,我会在投票关闭的同时添加评论。但因为已经有一个很好的答案解释了这个问题,所以我不必再重复。像Cody Gray提到的那样,根本原因是相同的,除此之外,我认为将其视为重复关闭是将有用的问题链接在一起而不是惩罚后来的提问者的一种方式。 - GSerg
3个回答

41

我认为这一解释与早期的编程语言(如WordBasic和早期的VBA)是如何存储True和False的整数值有关。在那些年代,True = -1,False = 0。

新的编程语言仍然使用0表示False,但使用1表示True。

Word的大多数布尔类型属性仍然使用-1表示True(例如Font.Bold),这导致了在使用新语言的程序员与Interop一起工作时产生了困惑和挫败感。因此,在某个时间点,微软的一些开发人员决定采用新方法,将整数值1赋给一些新功能的True,例如FitText

考虑以下代码示例,其中X的类型为Booleany的类型为Integer

  • 如果FitText为True,则整数值为1
  • 如果反转值,使用Not显示布尔值仍然为“True”,因为其整数值不是0,而是-2
  • 直接将整数值设置为True会得到-1

这确实令人困惑,但可以解释为什么Not没有给出预期的结果。

Sub testChangeBoolean()
  Dim X As Boolean                  ' again, default is False
  Dim Y As Integer
  X = Selection.Cells(1).FitText    ' does set X to True
  Y = Selection.Cells(1).FitText
  Debug.Print X, Y                  ' result: True    1
  X = Not X                         ' X is still True!
  Y = Not Y
  Debug.Print X, Y                  ' result: True   -2
  X = False
  Y = True
  Debug.Print X, Y                  ' result: False  -1
End Sub

8
是的,这确实很棒。解释得非常好。让我感到非常高兴我不用在Word中编程。 - BigBen
7
“@BigBen,令人困惑的部分是那个整数值在'Boolean'变量底层仍然存在的部分。这基本上意味着在VBA中,'Boolean'不是类型安全的。” - Mathieu Guindon
5
考虑到 Office 编程语言的年龄,这并不令人意外。在早期,“布尔值”是描述打开和关闭的“简单方式”,它被简单地存储为 0... 和另一个值,通常是 -1。即使是 WordBasic,向后兼容性也一直是首要任务,因此没有进行现代化。同时请记住,Word 的核心——即使是为了修复 bug,也无法更改的部分——是 C。有些事情不能改变,否则就需要从头开始构建,这将非常昂贵。 - Cindy Meister
7
-2是因为逻辑运算符执行按位运算,对于符号位有些棘手。但基本上将数字1的所有二进制位取反,会导致符号位变为1,而01则变成了10,即2,在考虑符号位后得到的值是-2 - Mathieu Guindon
3
你应该从LBound()开始吧? - FreeMan
显示剩余8条评论

9

补充Cindy的优秀回答,我想指出虽然在给 Boolean 数据类型赋值时,VBA通常会有保护措施来强制转换数值,但这是可以被规避的。如果你向不属于你的内存地址写入随机的值,那么你应该期望出现未定义行为。

为了帮助演示这一点,我们将(滥用)LSet ,它基本上允许我们在不实际分配的情况下复制值。

Private Type t1
  b As Boolean
End Type

Private Type t2
  i As Integer
End Type

Private Sub Demo()
  Dim i1 As t2
  Dim b1 As t1
  Dim b As Boolean

  i1.i = 1

  LSet b1 = i1

  b = b1.b

  Debug.Print b, b1.b, i1.i
  Debug.Print CInt(b), CInt(b1.b), i1.i

End Sub

请注意,代码行b = b1.b基本上等同于我们在OP代码中所做的操作。
X = Selection.Cells(1).FitText

也就是说,将一个 Boolean 分配给另一个 Boolean。然而,由于我使用了 LSet 绕过了 VBA 运行时检查,直接写入了 b1.b,所以它没有被强制转换。当读取 Boolean 时,VBA 隐式地将其强制转换为 TrueFalse 中的一个,这似乎有些误导,但正确的是因为任何假值结果都等于 0(也就是 False),而任何真值结果则不是。注意,真值的否定意味着 1-1 都是真值。
如果我直接将 1 分配给一个 Boolean 变量,VBA 就会将其强制转换为 -1/True,因此就不会有问题。但显然,对于 FitTextLSet,我们基本上是以不受控制的方式写入内存地址,所以当 VBA 期望 Boolean 变量已经强制转换其内容时,而实际上并没有,因此它开始以奇怪的方式处理这个特定变量。

2
那么...这个bug是在Word库中(可修复),还是在VBA本身中(修复不太可能)? - Mathieu Guindon
太棒了,一个看似简单无害的问题竟然能带来这么多收获...谢谢你,我明天醒来后会更详细地研究一下。 :-) - Cindy Meister

8

这是因为该属性返回的是内部的Long值,正如Cindy Meister所解释的那样。我们应该始终使用CInt来避免此问题。

Sub testChangeBoolean2()
  Dim X As Boolean                     ' again, default is False
  X = CInt(Selection.Cells(1).FitText) ' [Fixed] does set X to True
  X = Not X                            ' X is False!
End Sub

因为包含了一个简单的修复方法以获得所需的行为,所以我点了赞。 - trlkly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接