如果C允许,为什么Java完全不允许数字条件,如if(5){…}?


33

我有两个小程序:

C

#include <stdio.h>
int main()
{
    if (5) {
        printf("true\n");
    }
    else {
        printf("false\n");
    }

    return 0;
}

爪哇

class type_system {
   public static void main(String args[]) {
       if (5) {
           System.out.println("true");
       }
       else {
           System.out.println("false");
       }
   }
}

报告错误消息:

type_system.java:4: error: incompatible types: int cannot be converted to boolean
       if (5) {
           ^
1 error

我的理解

到目前为止,我将这个示例理解为不同类型系统的演示。C的类型更弱,并且允许从int到boolean的转换而没有错误。Java具有更强的类型,并且会失败,因为不允许隐式对话。

因此,我的问题是:我在哪里误解了事情?

我不想要的

我的问题与不良的编码风格无关。我知道这很糟糕,但是我对为什么C允许它而Java不允许它感兴趣。因此,我对语言的类型系统特别是其强项感兴趣。


22
@toogley:您特别想了解Java中的类型系统是什么?类型系统不允许这样做,因为语言规范禁止这样做。 C允许它和Java并没有与两种语言都可以接受的一切有关的原因。类型系统的最终行为是结果的影响,而不是原因。
罗伯特·哈维

8
@toogley:您为什么认为您误解了某些东西?再次阅读课文,我不明白您的问题是什么。
布朗

26
实际上,这不是 C语言中弱类型的示例。在过去的(C89)中,C甚至没有布尔类型,因此所有“布尔”操作实际上都对int值进行操作。一个更合适的例子是if (pointer)
Rufflewind '17

5
我之所以投票,是因为似乎没有做太多的研究来试图理解这一点。
jpmc26

11
似乎原来提出的问题与编辑后的版本有所不同(这就是为什么某些评论和答案似乎在回答不同的问题)。以目前的形式,这里似乎没有问题。什么误会 Java的行为完全符合您的预期。
jamesdlin

Answers:


134

1. C和Java是不同的语言

它们行为不同的事实不足为奇。

2. C没有做任何从int到的转换bool

怎么可能 ç甚至没有一个真正的bool类型转换为到1999年。C创建于1970年代初期,早if在C只是对B 1的一系列修改的时候就已经是C的一部分。

if在过去的NOP30年中,不仅是C语言。它直接作用于数值。即使在将s 引入C 的十年后,C标准中的语言(PDF链接bool仍使用术语“不等于0”和“等于0”来指定if(p 148)和?:(p 100)的行为。”,而不是布尔项“ true”或“ false”或类似内容。

方便地...

3. ...数字恰好是处理器指令所执行的操作。

JZ并且JNZ是有关条件分支的基本x86汇编说明。的缩写是“ Ĵ UMP如果Ž ERO”和“ Ĵ UMP如果Ñ OT Ž ERO”。用于PDP-11,其中C源自的当量,是BEQ(“ 牧场如果EQ UAL‘)和BNE(’ 牧场如果Ñ OT È QUAL”)。

这些指令检查先前的操作是否导致零,然后相应地跳转(或不跳转)。

4. Java对安全性的重视程度超过C曾经2

并且,出于安全考虑,他们认为限制ifbooleans是值得的成本(同时实施限制和由此产生的机会成本)。


1. B甚至根本没有类型。汇编语言通常也不会。但是,B语言和汇编语言可以很好地处理分支。

2.丹尼斯·里奇Dennis Ritchie)的话描述计划成为B(强调我的)的B的修改:

...似乎必须使用一种键入方案来应对字符和字节寻址,并为即将到来的浮点硬件做准备。其他问题,特别是类型安全性和接口检查,似乎并不像后来那样重要


13
关于直接映射到处理器指令的C语言中的布尔表达式的要点。我以前没想过
罗伯特·哈维

17
我认为第4点具有误导性,因为这似乎暗示着C会对安全性有一定的关注... C基本上可以让您做任何您想做的事:C假设您知道自己在做什么,常常会导致灾难性的灾难性后果。
TemporalWolf

13
@TemporalWolf您声明好像让C做您想做的事情是不好的。我认为不同之处在于C是为程序员编写的,并且具有基本能力。Java是为代码猴子编写的,如果可以键入,则可以在其中编写程序。通常情况很糟,但是谁在乎呢?我永远不会忘记,当我被入门Java入门课程的一名老师当作CS辅修的一部分时,他指出使用递归计算Fibonnaci数可能会有用,因为它“易于编写”。这就是为什么我们拥有自己的软件。
DRF

25
@DRF我的一位教授C网络课程的教授说:“ C就像一盒手榴弹,所有的针脚都拔了出来。” 您拥有很多力量,但实际上可以保证您的脸上会炸弹。在大多数情况下,这是不值得的麻烦,而且使用高级语言将使您的工作效率更高。我是否已将Python转码为hacky C位,使速度提高了6个数量级?是的,是的,我有。但这是例外,而不是规则。我可以用Python完成一天的工作,而在C中要花两周的时间...而且我是一个不错的C程序员。
TemporalWolf

11
@DRF鉴于知名公司开发的主流软件中普遍存在缓冲区溢出漏洞利用,显然即使是具有基本能力的程序员也不能不放下脚步。
恢复莫妮卡

14

C 2011在线草案

6.8.4.1 if语句

约束

1 语句的控制表达式if应具有标量类型。

语义

2在两种形式中,如果表达式比较不等于0,则执行第一个子语句。在else形式中,如果表达式比较等于0,则执行第二个子语句。如果通过标签到达第一个子语句,则第二个子语句为未执行。

3 else如果语法允许,则将An 与词汇上最接近的前导相关联。

请注意,此子句仅指定控制表达式应具有标量类型(char/ short/ int/ long/ etc。),而不能特别是布尔类型。如果控制表达式的值非零,则执行分支。

比较一下

Java SE 8语言规范

14.9 if声明

if语句允许语句的条件执行两个语句的条件选择执行一个或另一个但不是两者。
    IfThenStatement:
        if(表达式语句

    IfThenElseStatement:
        if(表达式StatementNoShortIf else 语句

    IfThenElseStatementNoShortIf:
        if(表达式StatementNoShortIf else StatementNoShortIf
表达式必须有类型booleanBoolean,或发生编译时错误。

Java OTOH 特别要求if语句中的控制表达式具有布尔类型。

因此,这与弱类型与强类型无关,而与每种语言定义指定为有效控件表达式有关。

编辑

至于为什么语言在此特定方面有所不同,请注意以下几点:

  1. C源自B,后者是一种“无类型”语言-基本上,所有内容都是32至36位的字(取决于硬件),并且所有算术运算都是整数运算。C的类型系统一次被拧紧,这样...

  2. 直到1999年版本的C语言,C才具有独特的布尔类型。C完全遵循B约定,即使用零表示false和使用非零表示true

  3. Java将C的发布日期推迟了几十年,并且专门针对解决C和C ++的某些缺点而设计。毫无疑问,加强对if语句中的控制表达式的限制是其中的一部分。

  4. 没有理由期望任何两种编程语言都能以相同的方式进行操作。甚至与C和C ++密切相关的语言也以一些有趣的方式出现差异,例如,您可以拥有不是合法C ++程序的合法C程序,或者具有不同语义的合法C ++程序等。


太可惜,我不能将两个答案标记为“已接受”
。– Toogley

1
是的,这是对这个问题的很好重申。但问题是,为什么两种语言之间存在这种差异。您根本没有解决这个问题。
达伍德说恢复莫妮卡

@DawoodibnKareem:问题是为什么C允许Java 不允许从转换intboolean。答案是在C语言中没有这样的转换。语言是不同的,因为它们是不同的语言。
约翰·博德

是的,“这与弱打字与强打字无关”。只看自动装箱和自动拆箱。如果C有那些,它也将不允许任何标量类型。
Deduplicator

5

许多答案似乎都针对条件表达式中的嵌入式赋值表达式。(虽然这是一种已知的潜在陷阱,但在这种情况下,它不是Java错误消息的来源。)

可能是因为OP没有发布实际的错误消息,并且^插入符号直接指向=赋值运算符的。

但是,编译器指向的是,=因为是由运算符产生条件看到的表达式的最终值(以及最终类型)。

它抱怨测试具有以下类型错误的非布尔值:

错误:类型不兼容:int无法转换为布尔值

测试整数虽然有时很方便,但被认为是Java设计人员选择避免的潜在陷阱。毕竟,Java具有真正的布尔数据类型,而C 没有(不是布尔类型)

这也适用于C通过if (p) ...和测试null / non-null的测试指针if (!p) ...,Java同样不允许这样做,而是要求显式比较运算符获取所需的布尔值。


1
C 确实具有布尔类型。但这比if声明要新。
MSalters

3
@MSalters C的bool仍然是整数,因此您可以这样做bool b = ...; int v = 5 + b;。这与不能用于算术的具有完整布尔类型的语言不同。
Jules

C的布尔值只是一个很小的整数。“ true”和“ false”可以很容易地用宏或其他任何东西定义。
奥斯卡·斯科格

4
@Jules:您只是指出C具有弱类型安全性。布尔值类型转换为整数值并不意味着它整数类型。按照您的逻辑,整数类型将是浮点类型,因为int v = 5; float f = 2.0 + v;在C中是合法的
。– MSalters

1
@MSalters实际上_Bool标准定义的标准无符号整数类型之一(请参见6.2.5 / 6)。
罗斯兰

2

不兼容的类型:int不能转换为布尔值

我对C为何允许它而Java不允许的兴趣。因此,我对语言的类型系统特别是其强项感兴趣。

您的问题分为两部分:

为什么Java不转换intboolean

归结为Java旨在尽可能地明确。它的类型系统非常静态,非常“在您面前”。在Java中,使用其他语言自动进行类型转换的事情并非如此。您也必须写int a=(int)0.5。转换floatint会丢失信息;与转换intboolean,因此容易出错。而且,他们将不得不指定很多组合。当然,这些事情似乎很明显,但它们的意图是要谨慎。

哦,与其他语言相比,Java 的规范非常精确,因为字节码不仅仅是内部实现细节。他们将必须精确地指定所有交互。巨大的事业。

为什么不if接受其他类型boolean呢?

if完全可以定义为允许除以外的其他类型boolean。它可能有一个定义,说明以下内容是等效的:

  • true
  • int != 0
  • String.length>0
  • 任何其他非null(而不是Boolean带有value的false)对象引用。
  • 甚至:任何其他非对象引用,null并且其方法Object.check_if(我是为此而发明的)返回true

他们没有;并没有真正的需要,他们想让它尽可能健壮,静态,透明,易于阅读等。没有隐式功能。此外,我敢肯定,实现必须非常复杂,我必须在所有可能的情况下测试每个值,因此性能可能也发挥了很小的作用(Java在当时的计算机上曾经很流行;记得在那里在最初的发行版中没有JIT编译器,至少在我当时使用的计算机上没有)。

更深层次的原因

一个更深层次的原因很可能是Java具有其原始类型的事实,因此其类型系统在对象和原始之间被撕裂。也许,如果他们避免了这些事情,事情将会以另一种方式发生。使用上一节中给出的规则,它们将必须明确定义每个单个基元的真实性(因为基元不共享超类,并且没有null为基元定义良好的)。很快,这将变成一场噩梦。

外表

好吧,最后,也许这只是语言设计师的偏爱。每种语言似乎都以自己的方式旋转...

例如,Ruby没有原始类型。一切,实际上是一切,都是对象。他们很容易确保每个对象都有特定的方法。

Ruby确实会在您可以抛出的所有类型的对象上寻找真实性。有趣的是,它仍然没有boolean类型(因为它没有基元),而且它没有Boolean类两种。如果您询问值true具有什么类(可轻松提供true.class),则得到TrueClass。该类实际上具有方法,即booleans(| & ^ ==)的4个运算符。在这里,if当且仅当其值为falsenilnullRuby的)时,才将其值视为false 。其他一切都是真的。所以,0或者""都是如此。

对于他们来说,创建一个Object#truthy?可以在任何类上实现并返回个人真实性的方法将是微不足道的。例如,String#truthy?对于非空字符串,可能已经实现为true或诸如此类。即使在大多数部门中Ruby是Java的对立面(使用mixin进行动态鸭子输入,重新打开类等等),他们也没有这样做。

对于习惯于$value <> 0 || length($value)>0 || defined($value)真实性的Perl程序员来说,这可能令人惊讶。等等。

输入带有约定的SQL,无论null在什么表达式内,表达式都会自动使其变为false。这样(null==null) = false。在Ruby中,(nil==nil) = true。快乐时光。


实际上,((int)3) * ((float)2.5)它在Java中定义非常明确(是7.5f)。
圣保罗Ebermann

没错,@PaŭloEbermann,我已删除了该示例。
AnoE

哇,在此...希望得到投票者和投票赞成删除答案的人的评论。
AnoE

实际上,从int到转换float通常也会丢失信息。Java是否也禁止这种隐式转换?
罗斯兰

@Ruslan否(长时间不变,→翻倍)–我想这是因为潜在的信息丢失仅发生在最不重要的位置,并且仅在认为不那么重要的情况下发生(相当大的整数值)。
圣保罗Ebermann

1

除了其他好的答案,我想谈一谈语言之间的一致性。

当我们想到数学上纯的if语句时,我们知道条件可以是true或false,没有其他值。每种主要的编程语言都遵循这一数学理想;如果为if语句赋予布尔值true / false,则可以一直看到一致,直观的行为。

到目前为止,一切都很好。这是Java实现的,也是Java实现的。

其他语言尝试为非布尔值带来便利。例如:

  • 假设n是一个整数。现在定义if (n)为的简写if (n != 0)
  • 假设x是一个浮点数。现在定义if (x)为的简写if (x != 0 && !isNaN(x))
  • 假设p是一个指针类型。现在定义if (p)为的简写if (p != null)
  • 假设s是字符串类型。现在定义if (s)if (s != null && s != "")
  • 假设a是数组类型。现在定义if (a)if (a != null && a.length > 0)

从表面上看,提供速记if-tests的想法似乎很不错……直到您遇到设计和意见上的差异:

  • if (0)在C,Python,JavaScript中被视为false;但在Ruby中被视为true。
  • if ([]) 在Python中被视为false,在JavaScript中为true。

每种语言都有以一种或另一种方式对待表达的良好理由。(例如,Ruby中唯一虚假的值是falsenil,因此0是真实的。)

Java采用了显式设计,以强制您向if语句提供布尔值。如果您急忙将代码从C / Ruby / Python转换为Java,则不能保留任何松散的if-test;您需要用Java显式地写出条件。花一点时间停下来思考,可以避免草率的错误。


1
你知道那x != 0x != 0 && !isNaN(x)吗?另外,它通常s != null用于指针,但对于非指针则适用。
Deduplicator

@Deduplicator的哪种语言是相同的?
圣保罗Ebermann

1
使用IEEE754,NaN≠0。NaN≠任何值。因此,如果(x)将执行,并且(!x)也将执行...
gnasher729

0

嗯,C中的每个标量类型,指针,布尔值(自C99起),数字(是否为浮点数)和枚举(由于直接映射至数字)都具有“自然”的falsey值,因此对于任何条件表达式都足够好。

Java也具有这些功能(即使Java指针被称为引用并且受到严格限制),但是Java 5.0中引入了自动装箱,这给人们带来了难以接受的困惑。此外,Java程序员也劝说键入更多内容的内在价值。

引发辩论的一个错误是是否将条件表达式限制为布尔类型,这是编写旨在进行比较的作业的错字,而根本没有通过限制条件表达式类型来解决,但通过禁止使用裸赋值表达式的为它的价值。

任何现代的C或C ++编译器都可以轻松地处理此问题,如果出现此类可疑结构,则会给出警告或错误。
对于恰好是预期的情况,添加括号会有所帮助。

总而言之,限制为布尔值(以及Java中的框式等效项)似乎是由于选择=赋值编译错误而造成的一类错字的尝试失败。


我想说,使用==布尔操作数(第一个是变量)(然后可以通过键入=)可以if比其他类型少得多,因此这只是一次半失败的尝试。
圣保罗Ebermann

0.0-> false和0.0-> true以外的任何值对我来说都不是“自然的”。
gnasher729

@PaŭloEbermann:部分结果带有不幸的副作用,尽管有一种简单的方法可以实现目标而没有副作用,但这显然是我书中的失败。
Deduplicator

您错过了其他原因。怎么样的truthy值""(Object) ""0.0-0.0NaN,空数组和Boolean.FALSE?尤其是最后一个很有趣,因为它是非空指针(true),可以将其取消显示为false。+++我也讨厌写作if (o != null),但是每天都节省一些字符,让我花半天时间调试我的“聪明”表达,这没什么好说的。就是说,我很乐意看到Java的一些中间方法:更慷慨的规则,但一点也不歧义。
maaartinus

@maaartinus:就像我说的那样,Java 5.0中自动装箱的引入意味着,如果他们分配了一个与指针的空值相对应的真值,他们就会遇到很大的麻烦。但这是自动装箱的问题,除此之外没有其他问题。像C这样没有自动装箱的语言很高兴没有这种情况。
Deduplicator

-1

我的问题与不良的编码风格无关。我知道它的坏处,但是我对为什么C允许它而Java却不感兴趣。

Java的两个设计目标是:

  1. 让开发人员专注于业务问题。 使事情变得更简单且不易出错,例如垃圾回收,因此开发人员无需专注于内存泄漏。

  2. 在平台之间可移植,例如在具有任何CPU的任何计算机上运行。

由于错别字,使用赋值作为表达式会导致很多错误,因此在上述目标#1下,您无法尝试使用它。

另外,推断任何非零值= true和任何零值= false不一定是可移植的(是,不管您相信与否,某些系统将0视为true和1视为false),因此在上面的目标#2下,不允许隐式允许。当然,您仍然可以显式转换。


4
除非在Java 8中已添加,否则在数字类型和布尔值之间不会进行显式转换。要将数值转换为布尔值,您必须使用比较运算符;要将布尔值转换为数字值,通常使用三元运算符。
彼得·泰勒

您可能会因为这些错别字而拒绝使用分配的值。但是考虑到您在Java中看到== true和== false的频率,显然将控制表达式限制为(可选装箱)布尔类型并没有太大帮助。
Deduplicator

Java会尽量确保将“某些系统将0视为true,将1视为false”的可移植性,因为它是Java的零和Java的false。操作系统对它的想法真的无关紧要。
maaartinus

-1

例如,其他语言的作用:Swift需要一个支持“ BooleanType”协议的类型的表达式,这意味着它必须具有“ boolValue”方法。“布尔”类型显然支持该协议,您可以创建自己的类型来支持该协议。整数类型不支持此协议。

在较早版本的语言中,可选类型支持“ BooleanType”,因此您可以编写“ if x”而不是“ if x!= nil”。这使得使用“可选布尔”非常混乱。一个可选的布尔值nil,false或true,如果b为nil,则不执行“ if b”;如果b为true或false,则不执行“ if b”。不再允许这样做。

似乎有一个真正不喜欢打开视野的家伙...

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.