表达与陈述

425 c# language-features expression

我问的是关于c#,但我认为它在大多数其他语言中都是一样的.

有没有人对表达式语句有一个很好的定义,有什么区别?

Joe*_*sky 515

表达式:评估值的东西.示例:1 + 2/x
语句:执行某些操作的代码行.示例:GOTO 100

在最早的通用编程语言中,如FORTRAN,区别非常清晰.在FORTRAN中,声明是一个执行单元,你做了一件事.它不被称为"线"的唯一原因是因为有时它跨越多条线.表达式本身无法做任何事情......你必须将它分配给变量.

1 + 2 / X
Run Code Online (Sandbox Code Playgroud)

是FORTRAN中的错误,因为它不执行任何操作.你必须对那个表达做一些事情:

X = 1 + 2 / X
Run Code Online (Sandbox Code Playgroud)

FORTRAN没有我们今天所知的语法 - 这个想法与Backus-Naur Form(BNF)一起被发明,作为Algol-60定义的一部分.在那一点上,语义上的区别("有一个值"与"做某事")在语法中得到了体现:一种短语是表达式,另一种是语句,解析器可以区分它们.

后来语言的设计者模糊了这种区别:他们允许语法表达式做事,并且他们允许具有值的句法语句.仍然存在的最早的流行语言示例是C.C的设计者意识到,如果允许您评估表达式并丢弃结果,则不会造成任何伤害.在C语言中,每个语法表达式都可以通过在末尾添加分号来形成语句:

1 + 2 / x;
Run Code Online (Sandbox Code Playgroud)

虽然绝对不会发生任何事情,但这是完全合法的陈述.类似地,在C中,表达式可能有副作用 - 它可以改变某些东西.

1 + 2 / callfunc(12);
Run Code Online (Sandbox Code Playgroud)

因为callfunc可能只是做一些有用的事情

一旦允许任何表达式成为语句,您也可以在表达式中允许赋值运算符(=).这就是为什么C让你做的事情

callfunc(x = 2);
Run Code Online (Sandbox Code Playgroud)

这将计算表达式x = 2(将值2赋值给x),然后将该值(2)传递给函数callfunc.

表达式和语句的这种模糊发生在所有C衍生物(C,C++,C#和Java)中,它们仍然有一些语句(如while),但几乎允许任何表达式用作语句(仅在C#中赋值,调用,递增和递减表达式可用作语句;请参阅Scott Wisniewski的答案).

有两个"语法类别"(这是事物语句和表达式的技术名称)可能导致重复工作.例如,C有两种形式的条件,即语句形式

if (E) S1; else S2;
Run Code Online (Sandbox Code Playgroud)

和表达形式

E ? E1 : E2
Run Code Online (Sandbox Code Playgroud)

有时人们想要不存在的重复:例如,在标准C中,只有一个语句可以声明一个新的局部变量 - 但这种能力足够有用,GNU C编译器提供了一个GNU扩展,使表达式能够声明一个局部变量也是如此.

其他语言的设计者不喜欢这种重复,他们很早就看到如果表达式既有副作用也有值,那么语句和表达式之间的句法区别并不是那么有用 - 所以他们摆脱了它.Haskell,Icon,Lisp和ML都是没有语法语句的语言 - 它们只有表达式.即使是类结构化循环和条件形式也被认为是表达式,它们具有值 - 但不是非常有趣的.

  • 如果我在这里没有误解你,你似乎声称"(setf(第三个foo)'goose)"是一个表达式,而不是一个声明,因为它是Lisp,"没有语句",因为Lisp比C语言早了十多年,这是"在表达和语句之间模糊界限的最早流行语言." 可以向我解释一下这个细节吗? (9认同)
  • 如果我没有弄错,`callfunc(x = 2);`将`x`传递给`callfunc`,而不是`2`.如果`x`是一个浮点数,则调用`callfunc(float)`,而不是`callfunc(int)`.在C++中,如果将`x = y`传递给`func`,并且`func`接受引用并更改它,它会改变`x`,而不是`y`. (4认同)
  • @Curt Sampson,你有没有问过这个单独的问题? (2认同)

Mar*_*son 21

  • 表达式是任何产生值的东西:2 + 2
  • 语句是程序执行的基本"块"之一.

请注意,在C中,"="实际上是一个运算符,它执行两项操作:

  • 返回右手子表达式的值.
  • 将右侧子表达式的值复制到左侧的变量中.

这是ANSI C语法的摘录.您可以看到C没有很多不同类型的语句......程序中的大多数语句都是表达式语句,即最后带分号的表达式.

statement
    : labeled_statement
    | compound_statement
    | expression_statement
    | selection_statement
    | iteration_statement
    | jump_statement
    ;

expression_statement
    : ';'
    | expression ';'
    ;
Run Code Online (Sandbox Code Playgroud)

http://www.lysator.liu.se/c/ANSI-C-grammar-y.html

  • 关于语句是什么的错误逻辑.声明性程序也可以执行,但声明性程序没有语句.声明是并且确实["副作用"](http://stackoverflow.com/a/8357604/615784),即必要.比照 [我的回答](http://stackoverflow.com/a/8450398/615784). (2认同)

Pat*_*ick 14

表达式是返回值的东西,而语句则不是.

举些例子:

1 + 2 * 4 * foo.bar()     //Expression
foo.voidFunc(1);          //Statement
Run Code Online (Sandbox Code Playgroud)

两者之间的大问题是你可以将表达式链接在一起,而语句不能被链接.

  • 肯定的陈述可以链接.{stmt1; stmt2; stmt3;}是一个链,它本身也是一个(复合)语句. (6认同)
  • `foo.voidFunc(1);`是一个带有void值的表达式.`while`和`if`是陈述. (5认同)

Mik*_*one 8

你可以在维基百科上找到它,但是表达式被评估为某个值,而语句没有评估值.

因此,表达式可以用在语句中,但不能用于其他方式.

请注意,某些语言(例如Lisp,我相信Ruby和许多其他语言)不区分语句与表达式......在这些语言中,一切都是表达式,可以与其他表达式链接.


Con*_*nal 8

为了解释表达式与语句的可组合性(可链接性)的重要差异,我最喜欢的参考是John Backus的图灵奖论文,可以从冯·诺依曼风格中解放出编程吗?.

命令式语言(Fortran,C,Java,...)强调结构化程序的语句,并将表达式作为一种后思.功能语言强调表达. 函数式语言具有如此强大的表达式,而语句可以完全消除.


Ely*_*Ely 7

我对这里的任何答案都不满意。我查看了 C++ 的语法(ISO 2008)。然而,也许出于教学和编程的目的,答案可能足以区分这两个元素(尽管现实看起来更复杂)。

语句由零个或多个表达式组成,但也可以是其他语言概念。这是语法的扩展巴科斯诺尔形式(语句摘录):

statement:
        labeled-statement
        expression-statement <-- can be zero or more expressions
        compound-statement
        selection-statement
        iteration-statement
        jump-statement
        declaration-statement
        try-block
Run Code Online (Sandbox Code Playgroud)

我们可以看到 C++ 中被视为语句的其他概念。

  • 表达式语句s 是不言自明的(一个语句可以由零个或多个表达式组成,仔细阅读语法,这很棘手)
  • case例如是一个标记语句
  • 选择语句if if/elsecase
  • 迭代语句while, do...while,for (...)
  • 跳转语句break, continue, return(可以返回表达式),goto
  • 声明语句是声明的集合
  • try-block是表示try/catch块的语句
  • 语法上可能还有更多

这是显示表达式部分的摘录:

expression:
        assignment-expression
        expression "," assignment-expression
assignment-expression:
        conditional-expression
        logical-or-expression assignment-operator initializer-clause
        throw-expression
Run Code Online (Sandbox Code Playgroud)
  • 表达式通常是或包含赋值
  • 条件表达式(听起来有误导性)是指运算符的使用 ( +, -, *, /, &, |, &&, ||, ...)
  • 抛出表达式- 呃?该throw子句也是一个表达式


Mar*_*ade 5

可以计算表达式以获取值,而语句不返回值(它们的类型为void).

函数调用表达式当然也可以被认为是语句,但除非执行环境有一个特殊的内置变量来保存返回的值,否则无法检索它.

面向语句的语言要求所有过程都是语句列表.面向表达式的语言,可能是所有函数式语言,是表达式列表,或者在LISP的情况下,是表示表达式列表的一个长S表达式.

尽管两种类型都可以组合,但只要类型匹配,大多数表达式都可以任意组合.每种类型的语句都有自己的组合其他语句的方式,如果他们可以做到这一切.Foreach和if语句要求单个语句或所有从属语句一个接一个地进入语句块,除非子语句允许其自己的子语句.

语句还可以包含表达式,其中表达式实际上不包含任何语句.但是,一个例外是lambda表达式,它表示一个函数,因此可以包含函数可以包含的任何内容,除非该语言仅允许有限的lambdas,如Python的单表达式lambda.

在基于表达式的语言中,您只需要一个函数的单个表达式,因为所有控制结构都返回一个值(其中很多都返回NIL).由于函数中最后一次计算的表达式是返回值,因此不需要return语句.


Mat*_*kel 5

简单地说:表达式求值为值,语句则不求值.


小智 5

关于基于表达式的语言的一些事情:


最重要的是:一切都有回报


用于分隔代码块和表达式的大括号和大括号没有区别,因为一切都是表达式。但这并不妨碍词法作用域:例如,可以为包含其定义的表达式以及其中包含的所有语句定义局部变量。


在基于表达式的语言中,一切都会返回一个值。一开始这可能有点奇怪——(FOR i = 1 TO 10 DO (print i))返回什么?

一些简单的例子:

  • (1)回报1
  • (1 + 1)回报2
  • (1 == 1)回报TRUE
  • (1 == 2)回报FALSE
  • (IF 1 == 1 THEN 10 ELSE 5)回报10
  • (IF 1 == 2 THEN 10 ELSE 5)回报5

几个更复杂的例子:

  • 有些东西,例如某些函数调用,实际上并没有返回有意义的值(只会产生副作用的东西?)。调用 OpenADoor(), FlushTheToilet()orTwiddleYourThumbs()将返回某种普通值,例如 OK、Done 或 Success。
  • 当在一个较大的表达式中计算多个未链接的表达式时,大表达式中计算的最后一个值将成为该大表达式的值。以 为例(FOR i = 1 TO 10 DO (print i)),for 循环的值为“10”,它会导致表达式(print i)被计算 10 次,每次都将 i 作为字符串返回。最后一次返回10,我们最终的答案

通常需要稍微改变思维方式才能充分利用基于表达式的语言,因为一切都是表达式这一事实使得“内联”很多东西成为可能

举个简单的例子:

 FOR i = 1 to (IF MyString == "Hello, World!" THEN 10 ELSE 5) DO
 (
    LotsOfCode
 )
Run Code Online (Sandbox Code Playgroud)

是非基于表达式的完全有效的替代

IF MyString == "Hello, World!" THEN TempVar = 10 ELSE TempVar = 5 
FOR i = 1 TO TempVar DO
(    
    LotsOfCode  
)
Run Code Online (Sandbox Code Playgroud)

在某些情况下,基于表达式的代码允许的布局对我来说感觉更自然

当然,这可能会导致疯狂。作为基于表达式的脚本语言 MaxScript 的爱好项目的一部分,我设法想出了这个怪物系列

IF FindSectionStart "rigidifiers" != 0 THEN FOR i = 1 TO (local rigidifier_array = (FOR i = (local NodeStart = FindsectionStart "rigidifiers" + 1) TO (FindSectionEnd(NodeStart) - 1) collect full_array[i])).count DO
(
    LotsOfCode
)
Run Code Online (Sandbox Code Playgroud)


Fra*_*kHB 5

这些概念的事实上的基础是:

表达式:一个语法类别,其实例可以计算为一个值。

语句:一个语法类别,其实例可能涉及表达式的求值,并且求值的结果值(如果有)不保证可用。

除了几十年前 FORTRAN 的最初背景之外,公认答案中表达式和语句的定义显然都是错误的:

  • 表达式可以是未评估的操作数。价值永远不会从它们中产生。
    • 非严格求值中的子表达式绝对可以不求值。
      • 大多数类 C 语言都有所谓的短路求值规则,可以有条件地跳过某些子表达式求值,尽管有副作用,但不会改变最终结果。
    • C 和一些类似 C 的语言具有未计算操作数的概念,甚至可以在语言规范中规范地定义它。这样的构造用于明确地避免评估,因此可以静态地区分剩余的上下文信息(例如类型或对齐要求),而不改变程序翻译后的行为。
      • 例如,用作运算符操作数的表达式sizeof永远不会被求值。
  • 语句与行结构无关。它们可以做的不仅仅是表达式,具体取决于语言规范。
    • 现代Fortran作为旧FORTRAN的直接后代,有可执行语句不可执行语句的概念。
    • 类似地,C++ 将声明定义为翻译单元的顶级子类别。C++ 中的声明是一条语句。(这在 C 中不是这样。)还有类似于 Fortran 的可执行语句的表达式语句。
    • 为了与表达式进行比较,只有“可执行”语句才重要。但你不能忽视这样一个事实:语句已经被概括为在这种命令式语言中形成翻译单元的结构。因此,正如您所看到的,该类别的定义有很大差异。这些语言中(可能)唯一保留的共同属性是,语句预计按照词汇顺序进行解释(对于大多数用户来说,从左到右和从上到下)。

(顺便说一句,我想在有关 C 的材料的答案中添加[需要引用],因为我不记得 DMR 是否有这样的意见。似乎没有,否则应该没有理由在 C 的设计中保留功能重复: 值得注意的是,逗号运算符与语句。)

(以下理由不是对原始问题的直接回答,但我觉得有必要澄清一些已经回答过的问题。)

然而,我们是否需要通用编程语言中的特定类别的“语句”是值得怀疑的:

  • 不保证语句比通常设计中的表达式具有更多的语义功能。
    • 许多语言已经成功地放弃了语句的概念,以获得干净、整洁和一致的整体设计。
      • 在此类语言中,表达式可以执行旧式语句可以执行的所有操作:只需在计算表达式时删除未使用的结果,或者通过显式未指定结果(例如在 R n RS 方案中),或者具有特殊值(作为单位类型的值)无法从正常表达式求值中产生。
      • 表达式求值的词汇顺序规则可以用显式序列控制运算符(例如begin在Scheme 中)或一元结构的语法糖来代替。
      • 其他类型“语句”的词汇顺序规则可以作为句法扩展(例如使用卫生宏)导出,以获得类似的句法功能。(它实际上可以做更多事情。)
    • 相反,陈述不能有这样的常规规则,因为它们不构成评估:只是不存在“子陈述评估”这样的常见概念。(即使有的话,我怀疑除了从现有的表达式求值规则中复制和粘贴之外,还会有更多的东西。)
      • 通常,保留语句的语言也将具有表达计算的表达式,并且存在保留到该子类别的表达式评估的语句的顶级子类别。例如,C++具有所谓的表达式语句作为子类别,并使用丢弃值表达式求值规则来指定在这种上下文中完整表达式求值的一般情况。某些语言(例如 C#)选择细化上下文以简化用例,但它使规范更加膨胀。
  • 对于编程语言的用户来说,语句的意义可能会让他们更加困惑。
    • 语言中表达式和陈述规则的分离需要更多的努力来学习语言。
    • 朴素的词汇顺序解释隐藏了更重要的概念:表达式求值。(这可能是最有问题的。)
      • 即使语句中完整表达式的求值也受到词法顺序的约束,子表达式则不是(必然)。除了与语句相关的任何规则之外,用户最终应该了解这一点。++i + ++i(考虑一下如何让一个新手明白C语言中无意义的点。)
      • Java 和 C# 等一些语言进一步限制子表达式的求值顺序,以允许忽略求值规则。这可能会带来更多问题。
        • 对于已经了解表达式评估概念的用户来说,这似乎过于具体。它还鼓励用户社区遵循语言设计的模糊心理模型。
        • 它使语言规范更加膨胀。
        • 在引入更复杂的原语之前,由于缺少评估中不确定性的表达能力,这对优化是有害的。
      • 像 C++(特别是 C++17)这样的一些语言指定了更微妙的求值规则上下文,作为上述问题的折衷方案。
        • 它使语言规范变得非常臃肿。
        • 这完全违背了普通用户的简单性......

那么为什么要声明呢?无论如何,历史已经是一团糟了。似乎大多数语言设计者并没有仔细考虑他们的选择。

更糟糕的是,它甚至给一些类型系统爱好者(对 PL 历史不太熟悉)带来一些误解,认为类型系统必须与操作语义上更重要的规则设计有关。

说真的,在许多情况下,根据类型进行推理并没有那么糟糕,但在这种特殊情况下尤其没有建设性。即使是专家也可能把事情搞砸。

例如,有人强调良好类型的本质是反对传统处理无限延续的核心论点。虽然结论有些合理,关于组合函数的见解也不错(但对于本质来说仍然太天真),但这个论点并不合理,因为它完全忽略了实践中的“侧通道”方法,例如_Noreturn any_of_returnable_types(在 C11 中)进行编码Falsum严格来说,状态不可预测的抽象机器并不等同于“崩溃的计算机”。