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

Com*_*hip 140 language-agnostic bitwise-operators negation

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

a += 3;   // for a = a + 3
a *= 3;   // for a = a * 3;
a <<= 3;  // for a = a << 3;
Run Code Online (Sandbox Code Playgroud)

但是当我想切换一个布尔表达式时,我总会发现自己写的东西就像

a = !a;
Run Code Online (Sandbox Code Playgroud)

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

this.dataSource.trackedObject.currentValue.booleanFlag =
    !this.dataSource.trackedObject.currentValue.booleanFlag;
Run Code Online (Sandbox Code Playgroud)

(是的,德米特定律,我知道).

所以我想知道,是否有任何语言都使用一元布尔切换运算符,这将允许我缩写a = !a而不重复表达式a,例如

!=a;  
// or
a!!;
Run Code Online (Sandbox Code Playgroud)

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

如果你能找到一个文档化的源代码,我也有兴趣了解一下,例如C++设计者是否考虑在bool成为内置类型时添加类似的运算符,如果是这样,为什么他们决定反对它.


(注意:我知道有些人认为作业不应该使用 =,++并且+=不是有用的操作员,而是设计缺陷;让我们假设我对他们感到高兴,并专注于为什么他们不会扩展到bool).

dfr*_*fri 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
Run Code Online (Sandbox Code Playgroud)

正如@cmaster在下面的评论中所指出的,上面假设a是类型的bool,而不是例如整数或指针.如果a实际上是其他东西(例如,某些非bool评估为"真实"或"虚假"的值,而某些表示不是0b1或者0b0分别表示),则上述内容不成立.

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

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


迅速: toggle()

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

toggle()BoolSwift中的类型添加了一个本机函数.

toggle()

切换布尔变量的值.

宣言

mutating func toggle()
Run Code Online (Sandbox Code Playgroud)

讨论

使用此方法可从切换一个布尔值truefalsefalsetrue.

var bools = [true, false]

bools[0].toggle() // bools == [false, false]
Run Code Online (Sandbox Code Playgroud)

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

  • 评论不适用于扩展讨论; 这个对话已经[移动到聊天](https://chat.stackoverflow.com/rooms/178389/discussion-on-answer-by-dfri-does-any-language-have-a-unary-boolean-toggle-歌剧). (3认同)

Ric*_*ges 43

在c ++中,可以提交重新定义运算符含义的Cardinal Sin.考虑到这一点,以及一点点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;
}
Run Code Online (Sandbox Code Playgroud)

预期产量:

6
0
1
0
1
Run Code Online (Sandbox Code Playgroud)

  • @ Yakk-AdamNevraumont LOL,现在你让我开始了:https://godbolt.org/z/KIkesp (12认同)
  • "重新定义运营商意义的红衣主教罪" - 我觉得你夸大了但是,对于现有的运营商而言,重新分配新意义真的不是一个重大的罪过. (9认同)
  • `<<`似乎是一个糟糕的超负荷.我做`!invert = x;`;) (8认同)
  • 对不起,我认为这不是一个好的论点.首先,字符串连接具有*完全*与添加不同的语义(它甚至不可交换!).其次,通过你的逻辑`<<`是一个位移运算符,而不是一个"流出"运算符.重新定义的问题不在于它与运算符的现有含义不一致,而是在简单的函数调用中没有增加任何值. (6认同)
  • @KonradRudolph我的意思当然是操作员应该对其通常的算术或逻辑意义有逻辑意义.2个字符串的'operator +`是合乎逻辑的,因为连接在我们的脑海中类似于添加或累积.由于STL中的原始滥用,`operator <<'已经意味着'流出'.它自然不会意味着"在RHS上应用转换函数",这实质上就是我在这里重新设定的. (5认同)
  • TBH这种语法只会增加一些全局定义的函数`template <typename T> T&invert(T&t){t =!t; 返回; }` (2认同)

DrS*_*don 37

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

向前

INVERT 对于按位补码.

0= 对于逻辑(真/假)补充.

  • 可辩论的是,在每种面向堆栈的语言中,一元"不"是一个"切换"运算符:-) (4认同)
  • 当然,这是一个侧面的答案,因为OP显然正在考虑具有传统赋值语句的类C语言.我认为像这样的侧面答案具有价值,如果只是为了展示读者可能没有想到的编程世界的一部分.("天堂和地球上有更多的编程语言,Horatio,而不是我们哲学中的dreampt.") (2认同)

sup*_*cat 31

bool减小C99 会产生预期的效果,因为会增加或减少bit某些微型微控制器方言所支持的类型(从我观察到的那样,将位视为单位宽的位域,因此所有偶数都被截断为0并且所有奇数到1).我不会特别推荐这样的用法,部分是因为我不是bool类型语义的忠实粉丝[恕我直言,该类型应该指定一个bool除了0或1以外的任何值存储可能在读取时表现得好像它包含一个Unspecified(不一定一致)的整数值; 如果一个程序试图存储一个不知道为0或1的整数值,它应该!!首先使用它].

  • C++用`bool`做了同样的事情[直到C++ 17](http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0002r1.html). (2认同)

Adr*_*ian 31

汇编语言

NOT eax
Run Code Online (Sandbox Code Playgroud)

请参阅https://www.tutorialspoint.com/assembly_programming/assembly_logical_instructions.htm

  • 您可以改为使用所有位:NOT 0x00000000 = 0xFFFFFFFF (8认同)
  • 嗯,是的,虽然我试图绘制布尔运算符和按位运算符之间的区别.正如op在问题中所说,假设语言有一个明确定义的布尔类型:"(所以没有C风格的int a = TRUE)." (5认同)
  • @BurnsBA:实际上,汇编语言没有一套明确定义的真值/假值.在代码高尔夫上,已经讨论了很多关于哪些值允许返回各种语言的布尔问题.[由于asm没有`if(x)`结构](https://codegolf.meta.stackexchange.com/questions/2190/interpretation-of-truthy-falsey#comment9441_2194),允许0是完全合理的/ -1为false/true,与SIMD比较(http://felixcloutier.com/x86/PCMPEQB:PCMPEQW:PCMPEQD.html).但是如果你在任何其他价值上使用它,你就会失败. (5认同)
  • 如果PCMPEQW导致0/-1,则很难有单个布尔表示,但SETcc导致0/+ 1. (4认同)
  • 我不认为这是op的意思,假设这是x86程序集.例如https://en.wikibooks.org/wiki/X86_Assembly/Logic`; 这里edx将是0xFFFFFFFE,因为按位NOT 0x00000001 = 0xFFFFFFFE` (2认同)

pax*_*blo 28

我假设你不会选择一种完全基于此的语言:-)无论如何,你可以用C++这样做:

inline void makenot(bool &b) { b = !b; }
Run Code Online (Sandbox Code Playgroud)

请参阅以下完整程序,例如:

#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);
}
Run Code Online (Sandbox Code Playgroud)

正如预期的那样输出:

false
true
false
Run Code Online (Sandbox Code Playgroud)

  • 你想要'返回b =!b`吗?有人读'foo = makenot(x)|| y`或只是一个简单的`foo = makenot(bar)`可能会认为`makenot`是一个纯函数,并假设没有副作用.在较大的表达式中隐藏副作用可能是不好的样式,非void返回类型的唯一好处就是启用它. (2认同)
  • [@MatteoItalia建议](/sf/ask/3634897661/#comment90846989_51928360)`template <typename T> T&invert(T&t) {t =!t; 返回; 如果在非`bool`对象上使用,被模板化的`将转换为/ bool`.IDK,如果这是好的或坏的.大概好. (2认同)

Way*_*rad 22

PostScript是一种串联的,面向堆栈的语言,如Forth,它具有一元切换,而不是.在操作切换堆栈顶部的值.例如,

true    % push true onto the stack
not     % invert the top of stack
        % the top of stack is now false
Run Code Online (Sandbox Code Playgroud)

请参见" PostScript语言参考手册"(pdf).458.


Reg*_*lue 21

Visual Basic.Net通过扩展方法支持此功能.

像这样定义扩展方法:

<Extension>
Public Sub Flip(ByRef someBool As Boolean)
    someBool = Not someBool
End Sub
Run Code Online (Sandbox Code Playgroud)

然后像这样调用它:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip
Run Code Online (Sandbox Code Playgroud)

所以,你原来的例子看起来像:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip
Run Code Online (Sandbox Code Playgroud)

  • 虽然这确实起到了作者寻求的速记的作用,当然,你必须使用两个运算符来实现`Flip()`:`=`和`Not`.有趣的是,在调用`SomeInstance.SomeBoolean.Flip`的情况下,即使`SomeBoolean`是一个属性,它仍然有效,而等效的C#代码不会编译. (2认同)

Lau*_*nen 14

从纯粹的理论角度来看,这个问题确实很有意思.抛开一元,变异的布尔切换操作符是否有用,或者为什么许多语言选择不提供一个,我冒险尝试查看它是否确实存在.

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


在(快速)搜索各种语言的功能之后,我可以放心地说,没有任何语言将此运算符实现为严格的变异就地操作(如果找到的话,请纠正我).接下来的事情就是看看是否有可以让你构建一种语言的语言.这需要两件事:

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

由于不支持这些要求中的任何一个或两个,将立即排除许多语言.Java for one不允许运算符重载(或自定义运算符),此外,所有基本类型都按值传递.Go不支持运算符重载(除了hacks).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
Run Code Online (Sandbox Code Playgroud)

  • 该语言将提供:https://github.com/apple/swift-evolution/blob/master/proposals/0199-bool-toggle.md (6认同)
  • "Rust也没有"=> [是的确如此](https://doc.rust-lang.org/std/ops/) (4认同)
  • 是的,但问题特别要求_operator_,而不是成员函数. (3认同)
  • @Boiethios谢谢!为Rust编辑 - 只允许重载运算符用于自定义类型,所以没有骰子. (3认同)
  • @LauriPiispanen规则比这更普遍,并且由于[orphan rule](https://www.quora.com/What-is-Rusts-orphan-rule-and-why-does-it-exist),仅供参考 (3认同)

Fre*_*ios 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);
}
Run Code Online (Sandbox Code Playgroud)

您也可以^= 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);
}
Run Code Online (Sandbox Code Playgroud)


And*_*yko 6

在Python中

Python支持这样的功能,如果变量与运算符具有bool类型(True或False)exclusive or (^=):

a = False
a ^= True
print(a)  # --> True
a ^= True
print(a)  # --> False
Run Code Online (Sandbox Code Playgroud)