任何语言都有一元布尔切换运算符吗?

Does any language have a unary boolean toggle operator?

所以这更像是一个理论问题。 C++ 和(in)直接基于它的语言(Java、C#、PHP)有 shortcut operators 用于将大多数二元运算符的结果分配给第一个操作数,例如

a += 3;   // for a = a + 3
a *= 3;   // for a = a * 3;
a <<= 3;  // for a = a << 3;

但是当我想切换一个布尔表达式时,我总是发现自己在写类似

的东西
a = !a;

a 是一个很长的表达式时,这会很烦人。

this.dataSource.trackedObject.currentValue.booleanFlag =
    !this.dataSource.trackedObject.currentValue.booleanFlag;

(是的,得墨忒尔定律,我知道)。

所以我想知道,是否有任何带有一元布尔切换运算符的语言允许我缩写 a = !a 而无需重复 [=14= 的表达式],例如

!=a;  
// or
a!!;

让我们假设我们的语言有一个合适的布尔类型(比如 C++ 中的 bool)并且 a 是那种类型(所以没有 C 风格的 int a = TRUE)。

如果您能找到文档来源,我也有兴趣了解是否当 bool 成为内置类型时,C++ 设计者考虑过添加这样的运算符,如果是这样,他们为什么决定反对它。


(注意:我知道有些人认为赋值不应该使用 =+++= 不是有用的运算符,而是设计缺陷;让我们假设我对它们很满意,并关注为什么它们不会扩展到布尔值。

我假设您不会仅基于此来选择一种语言 :-) 无论如何,您可以在 C++ 中使用类似以下内容的方式来执行此操作:

inline void makenot(bool &b) { b = !b; }

完整程序示例如下:

#include <iostream>

inline void makenot(bool &b) { b = !b; }

inline void outBool(bool b) { std::cout << (b ? "true" : "false") << '\n'; }

int main() {
    bool this_dataSource_trackedObject_currentValue_booleanFlag = false;
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);
}

如预期的那样输出:

false
true
false

切换布尔位

... that would allow me to abbreviate a = !a without repeating the expression for a ...

这种方法并不是真正的纯粹的 "mutating flip" 运算符,但确实满足您的上述标准;表达式的右侧不涉及变量本身。

任何具有布尔异或赋值的语言(例如 ^=)都允许通过对 true 的异或赋值来翻转变量的当前值,例如 a

// type of a is bool
a ^= true;  // if a was false, it is now true,
            // if a was true, it is now false

正如@cmaster 在下面的评论中指出的那样,上面假设 abool 类型,而不是例如整数或指针。如果 a 实际上是其他东西(例如,非 bool 的东西评估为 "truthy" 或 "falsy" 值,其位表示不是 0b10b0,分别),以上不成立。

举个具体的例子,Java 是一种定义明确且不受任何静默转换影响的语言。从下面引用@Boann 的评论:

In Java, ^ and ^= have explicitly defined behavior for booleans and for integers (15.22.2. Boolean Logical Operators &, ^, and | ), where either both sides of the operator must be booleans, or both sides must be integers. There's no silent conversion between those types. So it's not going to silently malfunction if a is declared as an integer, but rather, give a compile error. So a ^= true; is safe and well-defined in Java.


Swift: toggle()

自Swift 4.2 起,以下演进提案已被接受并实施:

这会在 Swift 中添加原生 toggle() function to the Bool 类型。

toggle()

Toggles the Boolean variable’s value.

Declaration

mutating func toggle()

Discussion

Use this method to toggle a Boolean value from true to false or from false to true.

var bools = [true, false]

bools[0].toggle() // bools == [false, false]

这本身不是运算符,但确实允许使用语言本机方法进行布尔值切换。

在 C++ 中,可能会犯下重新定义运算符含义的大罪。考虑到这一点,再加上一点 ADL,我们需要做的就是对我们的用户群造成混乱:

#include <iostream>

namespace notstd
{
    // define a flag type
    struct invert_flag {    };

    // make it available in all translation units at zero cost
    static constexpr auto invert = invert_flag{};

    // for any T, (T << invert) ~= (T = !T)    
    template<class T>
    constexpr T& operator<<(T& x, invert_flag)
    {
        x = !x;
        return x;
    }
}

int main()
{
    // unleash Hell
    using notstd::invert;

    int a = 6;
    std::cout << a << std::endl;

    // let confusion reign amongst our hapless maintainers    
    a << invert;
    std::cout << a << std::endl;

    a << invert;
    std::cout << a << std::endl;

    auto b = false;
    std::cout << b << std::endl;

    b << invert;
    std::cout << b << std::endl;
}

预期输出:

6
0
1
0
1

递减 C99 bool 将产生预期的效果,递增或递减 bit 某些微型微控制器方言中支持的类型(据我观察,这些方言将位视为单个位宽的位域,因此所有偶数都被截断为 0,所有奇数被截断为 1)。我不会特别推荐这种用法,部分原因是我不是 bool 类型语义的忠实拥护者 [恕我直言,该类型应该指定 bool 除了 0 之外的任何值or 1 is stored 在读取时可能表现得好像它包含一个未指定(不一定一致)的整数值;如果一个程序试图存储一个不知道是 0 或 1 的整数值,它应该首先使用 !!

Visual Basic.Net 通过扩展方法支持这一点。

像这样定义扩展方法:

<Extension>
Public Sub Flip(ByRef someBool As Boolean)
    someBool = Not someBool
End Sub

然后这样称呼它:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

因此,您的原始示例如下所示:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip

汇编语言

NOT eax

https://www.tutorialspoint.com/assembly_programming/assembly_logical_instructions.htm

只要我们包含汇编语言...

INVERT 用于按位补码。

0= 用于逻辑 (true/false) 补码。

PostScript, being a concatenative, stack-oriented 语言像 Forth,有一个一元切换,不是not 运算符切换堆栈顶部的值。例如,

true    % push true onto the stack
not     % invert the top of stack
        % the top of stack is now false

参见第 PostScript Language Reference Manual (pdf) 页。 458.

这个问题从纯理论的角度来看确实很有趣。撇开 一元、可变布尔切换运算符 是否有用,或者为什么许多语言选择不提供一个,我冒险去看看它是否确实存在.

TL;DR 显然不行,但是 Swift 让你实现一个。如果你只想看看它是如何完成的,你可以滚动到这个答案的底部。


(快速)搜索各种语言的特性后,我可以肯定地说,没有语言将此运算符实现为严格的就地变异操作(如果找到的话请纠正我)。所以接下来要做的就是看看是否有语言可以让你构建一个。这需要做两件事:

  1. 能够使用函数实现(一元)运算符
  2. 允许所述函数具有按引用传递的参数(以便它们可以直接改变它们的参数)

许多语言将立即被排除在外,因为它们不支持其中一个或两个要求。 Java 不允许运算符重载(或自定义运算符),此外,所有基本类型都按值传递。 Go 不支持运算符重载(hacks 除外)。 Rust 只允许自定义类型的运算符重载。你可以 almostScala 中实现这一点,它让你可以使用非常有创意的命名函数并省略括号,但遗憾的是没有传递引用. Fortran 非常接近,因为它允许自定义运算符,但特别禁止它们具有 inout 参数(在正常函数和子例程中允许) .


然而,至少有一种语言符合所有必要的条件:Swift。虽然有些人已经链接到即将推出的 .toggle() 成员函数,但您也可以编写自己的运算符,它确实支持 inout 参数。瞧瞧:

prefix operator ^

prefix func ^ (b: inout Bool) {
    b = !b
}

var foo = true
print(foo)
// true

^foo

print(foo)
// false

In C#:

boolean.variable.down.here ^= true;

布尔^运算符是异或,与true异或等同于取反

在 Rust 中,您可以创建自己的特征来扩展实现 Not 特征的类型:

use std::ops::Not;
use std::mem::replace;

trait Flip {
    fn flip(&mut self);
}

impl<T> Flip for T
where
    T: Not<Output = T> + Default,
{
    fn flip(&mut self) {
        *self = replace(self, Default::default()).not();
    }
}

#[test]
fn it_works() {
    let mut b = true;
    b.flip();

    assert_eq!(b, false);
}

您也可以按照建议使用 ^= true,在 Rust 的情况下,这样做没有可能的问题,因为 false 不是像 C 中那样的 "disguised" 整数或 C++:

fn main() {
    let mut b = true;
    b ^= true;
    assert_eq!(b, false);

    let mut b = false;
    b ^= true;
    assert_eq!(b, true);
}

In Python

Python 支持这样的功能,如果变量有 bool type (which is True or False) with the exclusive or (^=) 运算符:

a = False
a ^= True
print(a)  # --> True
a ^= True
print(a)  # --> False