C#中!和~的区别

25

当我第一次学习编写程序时,我使用的是C语言。(非常基本的命令行应用程序) 在这两种语言中,您通常会像这样使用!运算符:

    if(!true){
       //false..
}

我想在c#中进行一些位掩码操作,我想知道'~'运算符的作用。

现在我有点困惑了,因为在我的理解中,!和~应该具有相同的功能。

在c#中这样可以:

int i = ~0xffffffff; // i = 0
bool j = !true; // j = false

这不行:(但在c中它确实有效,并且做了我预期的事情)

int i = !0xffffffff; // i = 0

那么“~”和“!”之间的区别是什么,为什么要将它们分开使用?


假设 true == 0x00000001,当你运行 ~0x01!0x01 时,差异应该更加明显。 - ssube
3个回答

43

C#决定完全将整数操作与布尔操作分离。例如,您不能这样做:if(x & 4),而必须这样做:if((x & 4) != 0)以明确地从整数转换为布尔值。

这符合使用C及其前身超过40年的经验,人们通常会犯错,例如对两个具有真值true的值进行 & 操作,由于它们都是非零值,但没有任何非零位重叠,因此得到 false

C和C++都在其历史的晚期引入了bool类型,以添加更明确的区别,即表示数字或位模式的整数和我们只关心真值的值,但必须与旧代码兼容。 C#可以更加明确。

考虑到这一点,C#的!~与C中完全相同,只是某些东西不再有意义:

在C中,!表示取反,将0(false)转换为1(true),将所有非零值(true)转换为0(false)。在C#中,这只适用于bool,而不适用于int

在C中,~产生一的补数;它产生一个值,其中每个1位都变为0,每个0位都变为1(例如,0xFFFFFFFF变为0,0xF0F0F0F0变为0x0F0F0F0F等)。在C#中,这适用于int,但不适用于bool

如果要执行类似于!someInteger的操作,请在C#中执行someInteger == 0

编辑:

值得注意的是,由于运算符被分为“按位”('&', '|', '~')和“布尔”('&&','||'和'!'),有时可能会产生一些混淆。但这种区分并不完全正确。
最后三个运算符确实只在布尔上下文中有意义,在C#中,由于布尔和整数值之间有更严格的分离,它们不再适用于整数。
'~'确实没有在布尔上下文中使用的意义('~x'其中'x'为真将产生一个仍然为真的'x',4294967295次中有4294967294次),因此在C#中它不再适用于bool类型。
'&'和'|'保留了布尔用法。如果'A()'和'B()'各自返回bool,那么A() && B()仅在A()为false时才调用B()(即,“短路”),而A() & B()则总是先调用两个方法,然后进行布尔运算。这往往很少见,因为:
  1. 大多数情况下调用B()只是浪费时间,并且短路可以带来从巨大的性能提升(如果B()很昂贵)到没有任何影响但我们也没有损失任何东西的效果,所以应该养成这个习惯。(但是请考虑,如果B()非常便宜,则总是调用它的成本可能比分支还要便宜,特别是如果预测错误,请参见下面的注释。)

  2. 有时候&&是强制性的,例如x != null && x.Length != 0,不短路将在第二个参数上抛出异常。

  3. 如果确保两个方法都被调用非常重要,则最好在单独的语句中进行编码,以向其他开发人员(或稍后返回时自己)清晰地表明这一点。

  4. 但如果我们要讨论具有布尔值和整数参数的运算符之间的区别,我们应该包括对 |& 的布尔使用,因为它们确实会出现(有时是由于错字!),如果人们错误地将"位运算符"和"布尔运算符"分开,并忘记有两个符号被同时用作这两种运算符,那么它们可能会引起混淆。

1
其实,在 C 语言中,(!0) 的正常值为 0x01。这在 C# 中也是正确的(你可以通过不安全操作从 bool* 复制到 int* 来测试这一点)。因此,在用作布尔值但不是布尔值的值上使用 ~ 进行一补数(或 - 进行二补数)可能会产生令人困惑的结果,因此也允许对布尔值执行 ++,但不允许执行 --。在 VB6 和更早期版本中,真正的布尔值确实是 0xFFFF,原因与您的想法接近,但这仍然可能会导致混淆…… - Jon Hanna
1
例如,虽然现在布尔值和整数的操作更加一致了(NOT TRUE的结果为FALSE,没有任何不同的操作方式,因为0xFFFF为0x0),但仍然存在这样一个问题,即4为true而2也为true,但4 AND 2为false。重要的是,在C中,!1!0xFFFFFF!42都会得到0,与``不相对应。 - Jon Hanna
@BastiM 哎呀,我误解了来自www.dotnetpearls.com/bool的这段文字。我以为读到的是如果是true就是0xFF,但实际上只要不是0x00就是true: CLI布尔类型在内存中占用1个字节。所有零的位模式表示false的值。任何一位设置的位模式(类似于非零整数)表示true的值。Miller&Ragsdale,第479页。 - Marc Trittibach
1
是的,任何非零都被视为“true”,但是产生“true”的操作的结果(例如!false)始终为1。事实上,我发现强制其他非零值充当true是不好的;在C#和大多数.NET语言中实际上是不可能的,而且有些代码假定唯一可能的值是10。上周在我编写一些CIL时,我遇到了一个有趣的bug,其中我采用了C风格的任何非零即为真的方法,我的NUnit测试说“Expected:True But was:True”。严格来说,这个bug存在于object.Equals中,但我改变了它,因为其他代码可以调用它。 - Jon Hanna
2
好的回答Jon。我想再补充一点。在某些罕见情况下,A()&B()可能比A() && B()性能更高。 &&等同于if-then,这不仅使代码变大,还引入了条件分支,这意味着处理器必须进行分支预测,这意味着它可能会选择错误,这意味着处理器可能会做出其他糟糕的优化决策。如果B()非常便宜,那么有时不必要地执行它的成本比添加额外的条件分支的成本要 - Eric Lippert
显示剩余8条评论

16

7
一些解释可以使这个答案变得更好。 - Steve

2

~按位取反运算符是一元运算符,它只包含一个操作数。与其他按位运算符不同,按位取反运算符不使用与布尔运算符相似的符号。要实现一的补码,波浪线字符(~)位于要修改的值左侧。

byte valueToComplement = 187;                  // 10111011  
byte complement = (byte) ~valueToComplement;   // Result = 68

! - 是一个布尔反转符号,可以是 true 或 false。


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