是否有任何语言具有一元布尔切换运算符?


141

因此,这更多是一个理论问题。C ++和直接基于它的语言(Java,C#,PHP)具有快捷方式运算符,用于将大多数二进制运算符的结果分配给第一个操作数,例如

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;

(是的,我知道得墨meter耳定律)。

所以我想知道,有没有用一元布尔切换操作员的语言,让我来简化a = !a而不需要重复表达的a,例如

!=a;  
// or
a!!;

假设我们的语言具有适当的布尔类型(如boolC ++),a并且具有该类型(因此没有C样式int a = TRUE)。

如果您可以找到记录在案的资源,我也很想了解例如C ++设计人员是否在考虑bool成为内置类型时是否考虑过添加这样的运算符,如果是,为什么他们反对这样做。


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


31
函数呢void Flip(bool& Flag) { Flag=!Flag; }缩短您的长表情。
哈珀

72
this.dataSource.trackedObject.currentValue.booleanFlag ^= 1;
KamilCuk

13
长表达式可以分配给引用以简化代码
Quentin

6
@KamilCuk这可能有用,但是您使用的是混合类型。您为布尔分配一个整数。
哈珀

7
@ user463035818 *= -1但是,由于某种原因,我觉得它比直观^= true
CompuChip

Answers:


15

从纯粹的理论角度来看,这个问题确实很有趣。抛开一元,可变的布尔型触发器运算符是否有用,或者为什么许多语言选择不提供一种,我冒险探索它是否确实存在。

TL; DR显然没有,但是Swift允许您实现一个。如果您只想看看它是如何完成的,则可以滚动到此答案的底部。


在对各种语言的功能进行(快速)搜索之后,我可以肯定地说没有一种语言将此操作符实现为严格的变异就地操作(如果找到一种,请进行纠正)。因此,接下来的事情就是看看是否有可以让您构建语言的语言。这将需要两件事:

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

由于不支持这两种要求之一或全部,许多语言将立即被排除在外。Java for One不允许运算符重载(或自定义运算符),此外,所有原始类型均按值传递。Go完全不支持操作员重载(黑客除外)。Rust只允许运算符重载自定义类型。您几乎可以在Scala中实现这一点,它使您可以使用非常有创意的命名函数,并且也可以省略括号,但是遗憾的是没有通过引用。Fortran非常接近,因为它允许自定义运算符,但特别禁止他们使用inout 参数(正常功能和子例程中允许使用)。


但是,至少有一种语言在所有必要的方框中打勾:Swift。虽然有些人已链接到即将发布的.toggle()成员函数,但您也可以编写自己的运算符,该运算符确实支持inout参数。瞧瞧:

prefix operator ^

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

var foo = true
print(foo)
// true

^foo

print(foo)
// false


3
是的,但是这个问题专门问的是运算符,而不是成员函数。
Lauri Piispanen

4
“ Rust也没有” => 是的
Boiethios

3
@Boiethios谢谢!为Rust编辑-尽管只允许重载运算符用于自定义类型,所以没有骰子。
Lauri Piispanen

3
@LauriPiispanen该规则比这更笼统,由于孤儿规则,FYI
Boiethios

143

切换布尔位

...这将使我a = !a 无需重复a ... 的表达

这种方法并不是真正的“变异翻转”运算符,但确实满足您的上述条件;表达式的右侧不涉及变量本身。

任何具有布尔XOR赋值的语言(例如^=)都可以a通过XOR赋值将变量的当前值翻转,例如true

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

正如@cmaster在下面的注释中指出的那样,以上假设a是类型bool,而不是例如整数或指针。如果a实际上还有其他东西(例如,某些东西未bool对“真值”或“虚假”值进行评估,并且用位表示形式分别不是0b10b0),则以上内容不成立。

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

在Java中,^^=已明确定义行为布尔和整数(15.22.2。布尔逻辑运算符&^|),其中无论是运营商的双方必须是布尔值,或双方必须是整数。这些类型之间没有静默转换。因此,如果a将其声明为整数,则不会产生静默故障,而是会给出编译错误。因此a ^= true;在Java中安全且定义明确。


迅速: toggle()

从Swift 4.2开始,以下改进方案已被接受并实施:

toggle()BoolSwift中的类型添加了本机函数。

toggle()

切换布尔变量的值。

宣言

mutating func toggle()

讨论区

使用此方法可将布尔值从切换truefalse或从切换falsetrue

var bools = [true, false]

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

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


3
评论不作进一步讨论;此对话已转移至聊天
塞缪尔·柳

44

在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

9
“重新定义运算符含义的基本法则” –我觉得您在夸大其词,但将新的含义重新分配给现有的运算符通常并不是根本的罪过。
康拉德·鲁道夫

5
@KonradRudolph当然,我的意思是,运算符应该就其通常的算术或逻辑含义而言具有逻辑意义。2个字符串的'operator +'是合乎逻辑的,因为串联(在我们的脑海中)类似于加法或累加。operator<<由于其最初在STL中的滥用,因此意味着“流出”。这自然不会意味着“在RHS上应用转换函数”,这实际上是我在此处重新设计的目的。
理查德·霍奇斯

6
对不起,我认为这不是一个很好的论点。首先,字符串串联具有与加法完全不同的语义(它甚至不是可交换的!)。其次,根据您的逻辑,<<是位移位运算符,而不是“流输出”运算符。重新定义的问题不是因为它与运算符的现有含义不一致,而是它在简单的函数调用上没有任何价值。
康拉德·鲁道夫

8
<<似乎是一个严重的过载。我会做的!invert= x;;)
Yakk-Adam Nevraumont

12
@ Yakk-AdamNevraumont大声笑,现在您已经让我开始:godbolt.org/z/KIkesp
Richard Hodges

37

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

向前

INVERT 进行逐位补码。

0= 用于逻辑(对/错)补码。


4
可以争论的是,在每种面向堆栈的语言中,一元not都是“切换”运算符:-)
Bergi,

2
当然,这是一个横向答案,因为OP显然正在考虑具有传统赋值语句的类C语言。我认为这样的横向答案很有用,即使只是表明读者可能没有想到的编程领域的一部分。(“在Horatio中,天上人间的编程语言比我们哲学中所梦想的要多。”)
Wayne Conrad

31

递减C99 bool会达到预期的效果,递增或递减bit某些微型微控制器方言支持的类型(根据我的观察,将位视为单个位宽的位域,因此所有偶数都将被截断为0,而所有奇数为1)。我不会特别推荐这种用法,部分原因是我不喜欢bool类型语义[IMHO,该类型应已指定a bool,除读取0或1之外的任何值时,其a 都可能在读取时表现为它持有一个未指定(不一定一致)的整数值;如果程序正在尝试存储一个未知的0或1的整数值,则应!!首先在其上使用]。


2
bool 直到C ++ 17为止, C ++都做同样的事情。
戴维斯·赫林

31

2
假设这是x86汇编,我认为这并不是op想到的。例如en.wikibooks.org/wiki/X86_Assembly/Logic ; here edx would be 0xFFFFFFFE because a bitwise NOT 0x00000001 = 0xFFFFFFFE
BurnsBA '18年

8
您可以改用所有位:NOT 0x00000000 = 0xFFFFFFFF
Adrian

5
好吧,是的,尽管我试图画出布尔运算符和按位运算符之间的区别。正如op在问题中所说的那样,假定该语言具有定义明确的布尔类型:“(因此,没有C风格的int a = TRUE)。”
BurnsBA

5
@BurnsBA:的确,汇编语言没有一组明确定义的真实/错误值。在代码高尔夫球上,已经讨论了很多关于使用各种语言为布尔型问题允许返回的值的问题。 由于asm没有if(x)构造,因此将0 / -1设为false / true是完全合理的,就像SIMD比较(felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html)一样。但是您说对了,如果您将其用于任何其他值,这将中断。
彼得·科德斯

4
鉴于PCMPEQW的结果为0 / -1,而SETcc的结果为0 / + 1,很难使用单个布尔值表示。
尤金·斯特尔

28

我假设您不会仅基于此来选择一种语言:-)在任何情况下,您都可以在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

2
你也想return b = !b吗?有人阅读foo = makenot(x) || y或只是简单的阅读,foo = makenot(bar)可能会认为这makenot是一个纯粹的功能,并认为没有副作用。在较大的表达式中掩埋副作用可能是不好的样式,而非无效返回类型的唯一好处就是可以启用它。
彼得·科德斯

2
@MatteoItalia建议 template<typename T> T& invert(T& t) { t = !t; return t; }bool如果在非bool对象上使用,则要进行模板化的模板将与该模板进行转换。IDK是好是坏。大概不错。
彼得·科德斯


21

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

2
尽管这确实是作者寻求的快捷方式,但是,您必须使用两个运算符来实现Flip()=Not。有趣的是,在调用的情况下SomeInstance.SomeBoolean.Flip即使它SomeBoolean是一个属性也可以工作,而等效的C#代码不会编译。
培根

14

在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或C ++中那样的“伪装”整数:

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

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.