关于c#:Expression与Statement

Expression Versus Statement

我问的是关于C,但我认为在大多数其他语言中也是这样。

有人对表达和陈述有很好的定义吗?有什么区别?


表达式:计算为一个值的东西。例子:1+2/x语句:执行某项操作的一行代码。示例:Goto 100

在最早的通用编程语言(如Fortran)中,这种区别非常明显。在Fortran中,语句是一个执行单元,这是您所做的。它之所以没有被称为"线",是因为有时它跨越了多条线。一个表达本身不能做任何事…你必须把它赋给一个变量。

1
1 + 2 / X

在Fortran中是一个错误,因为它不做任何事情。你必须对这个表达做些什么:

1
X = 1 + 2 / X

Fortran没有我们今天所知道的语法,这一思想和巴科斯-诺尔形式(bnf)一起被发明,作为algol-60定义的一部分。在这一点上,语义的区别("有值"和"做点什么")体现在语法中:一种短语是表达式,另一种是语句,解析器可以将它们区分开来。

后期语言的设计者模糊了区别:他们允许句法表达式做事情,并且允许有值的句法语句。最早仍然存在的流行语言例子是C。C的设计者意识到,如果允许你评估一个表达式并丢弃结果,就不会造成任何伤害。在C语言中,每一个句法表达式都可以通过在结尾附加一个分号而变成一个语句:

1
1 + 2 / x;

是一个完全合法的声明,即使绝对不会发生任何事情。同样,在C语言中,一个表达式可能有副作用,它可以改变某些东西。

1
1 + 2 / callfunc(12);

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

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

1
callfunc(x = 2);

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

表达式和语句的模糊出现在所有的C导数(c,c++,c*,和java)中,这些语句仍然有一些语句(如while),但它允许几乎任何表达式被用作语句(在C=唯一赋值、调用、增量和减量表达式中可以用作语句;参见Scott Wisniewski的答案)。

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

1
if (E) S1; else S2;

和表达形式

1
E ? E1 : E2

有时,人们希望复制不存在的内容:例如,在标准C中,只有一个语句可以声明一个新的局部变量,但是这种能力非常有用,以至于GNUCcompiler提供了一个GNU扩展,它允许表达式声明局部变量。

其他语言的设计者不喜欢这种重复,他们在早期就看到,如果表达式可以有副作用和值,那么语句和表达式之间的句法区别就不那么有用了—,因此他们摆脱了这种重复。haskell、icon、lisp和ml都是没有语法语句的语言,它们只有表达式。即使是类结构的循环和条件形式也被视为表达式,它们具有值,但不是非常有趣的值。


我想对上面乔尔的回答做一个小小的修正。

C不允许将所有表达式用作语句。特别是,只能将赋值、调用、递增和递减表达式用作语句。

例如,C编译器将以下代码标记为语法错误:

1±2;


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

注意,在C语言中,"="实际上是一个运算符,它有两个功能:

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

这是ANSIC语法的摘录。你可以看到C没有很多不同的陈述…程序中的大多数语句都是表达式语句,即结尾带有分号的表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
statement
    : labeled_statement
    | compound_statement
    | expression_statement
    | selection_statement
    | iteration_statement
    | jump_statement
    ;

expression_statement
    : ';'
    | expression ';'
    ;

网址:http://www.lysator.liu.se/c/ansi-c-grammar-y.html


表达式返回值,而语句则不返回值。

例如:

1
2
1 + 2 * 4 * foo.bar()     //Expression
foo.voidFunc(1);          //Statement

这两者之间的最大区别是可以将表达式链接在一起,而不能将语句链接在一起。


您可以在维基百科上找到它,但是表达式被计算为某个值,而语句没有计算值。

因此,表达式可以在语句中使用,但不能反过来使用。

请注意,有些语言(如Lisp,我相信Ruby和其他许多语言)不能区分语句和表达式…在这种语言中,一切都是一个表达式,并且可以与其他表达式链接。


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

命令式语言(FORTRAN,C,Java,…)强调语句构造程序,并将表达式作为一种事后考虑。功能语言强调表达。纯函数语言具有如此强大的表达式,以致于语句无法完全消除。


简单地说:表达式的计算结果是一个值,而语句则不是。


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

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

面向语句的语言要求所有过程都是语句列表。面向表达式的语言,可能是所有功能语言,都是表达式列表,或者在Lisp的情况下,是一个表示表达式列表的长s表达式。

尽管两种类型都可以组合,但只要类型匹配,大多数表达式都可以任意组合。每种类型的语句都有自己的方式组成其他语句,如果它们都能做到的话.forEach和if语句要么需要一个语句语句,要么所有的从属语句都在一个语句块中,一个接一个地执行,除非子语句允许使用自己的子语句。

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

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


语句是表达式的特殊情况,具有void类型。语言以不同的方式处理语句的倾向往往会引起问题,如果恰当地加以概括,情况会更好。

例如,在C中,我们有非常有用的Func重载的泛型委托集。但是我们也必须有一个对应的Action集,并且为了处理这种不幸的分叉,必须不断地重复通用的高阶编程。

简单示例-在调用另一个函数之前检查引用是否为空的函数:

1
2
3
4
5
TResult IfNotNull<TValue, TResult>(TValue value, Func<TValue, TResult> func)
                  where TValue : class
{
    return (value == null) ? default(TValue) : func(value);
}

编译器能否处理TResult成为void的可能性?对。它所要做的就是要求返回后面跟着一个类型为void的表达式。default(void)的结果为void型,传入的func需要为Func型(相当于Action型)。

许多其他的答案意味着你不能像用表达式那样链接语句,但是我不确定这个想法是从哪里来的。我们可以把语句后出现的;看作一个二进制中缀运算符,取void类型的两个表达式,并将它们组合成void类型的单个表达式。


关于基于表达式的语言:

最重要的是:一切都返回一个值

大括号和大括号用于分隔代码块和表达式没有区别,因为所有内容都是表达式。但是,这并不能阻止词法范围:例如,可以为包含其定义的表达式以及其中包含的所有语句定义局部变量。

在基于表达式的语言中,所有内容都返回一个值。一开始这可能有点奇怪——(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()TwiddleYourThumbs()将返回一些普通的值,如OK、DONE或SUCCESS。
  • 当在一个较大的表达式中计算多个未链接的表达式时,在该较大表达式中计算的最后一个对象的值将成为该较大表达式的值。以(FOR i = 1 TO 10 DO (print i))为例,for循环的值为"10",它使(print i)表达式被计算10次,每次返回i作为字符串。最后一次通过返回10,我们的最终答案

它通常需要稍微改变一下心态,才能充分利用基于表达式的语言,因为事实上,一切都是一种表达式,因此可以"内联"许多东西。

举个简单的例子:

1
2
3
4
 FOR i = 1 to (IF MyString =="Hello, World!" THEN 10 ELSE 5) DO
 (
    LotsOfCode
 )

是基于非表达式的

1
2
3
4
5
IF MyString =="Hello, World!" THEN TempVar = 10 ELSE TempVar = 5
FOR i = 1 TO TempVar DO
(    
    LotsOfCode  
)

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

当然,这会导致疯狂。作为一个名为maxscript的基于表达式的脚本语言的业余项目的一部分,我设法想出了这个怪兽行

1
2
3
4
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
)

语句->按顺序执行的说明
表达式->返回值的计算

语句基本上类似于算法中的步骤或指令,语句执行的结果是指令指针的实现(所谓的汇编程序)。

表达式并不意味着一眼就能看到执行顺序,它们的目的是计算并返回一个值。在命令式编程语言中,表达式的计算有一个顺序,但这仅仅是因为命令式模型,而不是它们的本质。

陈述示例:

1
2
3
4
for
goto
return
if

(所有这些都意味着执行行(语句)提前到另一行)

表达式示例:

1
2+2

(这并不意味着执行的想法,而是评价的想法)


陈述是语法上完整的句子。表达式不是。例如

1
x = 5

这是一个完整的句子。代码

1
(x + 5)/9.0

这不是一个完整的句子。声明

1
2
3
while k < 10:
    print k
    k += 1

是一个完整的句子。请注意,循环头不是;"while k<10,"是一个从属子句。


语句,

语句是一个程序构建块,从中构建所有C程序。语句可以声明局部变量或常量、调用方法、创建对象或为变量、属性或字段赋值。

由大括号包围的一系列语句构成一个代码块。方法体是代码块的一个例子。

1
2
3
4
5
6
7
8
9
10
11
bool IsPositive(int number)
{
    if (number > 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

C中的语句通常包含表达式。C中的表达式是包含文字值、简单名称或运算符及其操作数的代码片段。

表达式,

表达式是可以计算为单个值、对象、方法或命名空间的代码片段。两种最简单的表达式类型是文本和简单名称。文字是一个没有名称的常量值。

1
2
int i = 5;
string s ="Hello World";

I和S都是识别局部变量的简单名称。在表达式中使用这些变量时,将检索变量的值并将其用于表达式。


我更喜欢这个词的形式逻辑意义上的statement的意思。它可以改变计算中一个或多个变量的状态,从而可以对其值做出判断是真是假。

我想,在计算机界和科学界,总的来说,当引入新的术语或词时,现有的词是"重新调整用途的",或用户对现有的、已建立的或"适当的"术语一无所知时,他们所描述的是什么


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

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

1
2
3
4
5
6
7
8
9
statement:
        labeled-statement
        expression-statement <-- can be zero or more expressions
        compound-statement
        selection-statement
        iteration-statement
        jump-statement
        declaration-statement
        try-block

我们可以看到其他概念,这些概念被认为是C++语言中的语句。

  • 表达式语句是自我解释的(一个语句可以由零个或多个表达式组成,仔细阅读语法,这很棘手)
  • 例如,case是一个带标签的语句
  • 选择语句为ifif/elsecase
  • 迭代语句有whiledo...whilefor (...)
  • 跳转语句有breakcontinuereturn(can return expression)、goto
  • 声明语句是一组声明
  • Try块是表示try/catch块的语句
  • 可能还有更多的语法问题

这是一个摘录,显示了表达式部分:

1
2
3
4
5
6
7
expression:
        assignment-expression
        expression"," assignment-expression
assignment-expression:
        conditional-expression
        logical-or-expression assignment-operator initializer-clause
        throw-expression
  • 表达式是或包含通常的赋值
  • 条件表达式(听起来有误导性)是指运算符(+-*/&|&&||…)的用法。
  • 抛出表达式-呃?throw子句也是一种表达。

这是我找到的最简单的答案之一。

最初由安德斯·卡塞奥回答

语句是执行某些操作的完整代码行,而表达式是代码中计算值的任何部分。

表达式可以使用运算符"水平"组合成较大的表达式,而语句只能通过逐个写入或使用块构造"垂直"组合。

每个表达式都可以用作语句(其作用是计算表达式并忽略结果值),但大多数语句不能用作表达式。

http://www.quora.com/python-programming-language-1/whats-the-difference-between-a-statement-and-an-expression-in-python


为了改进和验证我之前的答案,程序设计语言术语的定义应在适用时从计算机科学类型理论中解释。

表达式的类型不是底部类型,即它有一个值。语句具有Unit或Bottom类型。

由此可知,语句只有在产生副作用时才能在程序中产生任何效果,因为它要么不能返回值,要么只返回不可赋值(在某些语言中,如C's void)的单元类型的值,要么(如scala)的单元类型的值可以存储以延迟对语句的评估。

显然,@pragma/*comment*/没有类型,因此与陈述有区别。因此,唯一没有副作用的语句类型是非操作。非操作仅用作将来副作用的占位符。由于声明而采取的任何其他行动都是副作用。同样,编译器提示(例如@pragma不是语句,因为它没有类型。


最准确地说,语句必须具有"副作用"(即命令式),表达式必须具有值类型(即非底部类型)。

语句的类型是单位类型,但是由于停止定理,单位是虚构的,所以我们可以说是底部类型。

Void并不是最底层的类型(它不是所有可能类型的子类型)。它存在于没有完全健全的系统的语言中。这听起来像是势利的声明,但完整性(如差异注释)对于编写可扩展软件至关重要。

让我们看看维基百科对这件事有什么看法。

https://en.wikipedia.org/wiki/statement_uu(计算机科学)

In computer programming a statement is the smallest standalone element of an imperative programming language that expresses some action to be carried out.

Many languages (e.g. C) make a distinction between statements and definitions, with a statement only containing executable code and a definition declaring an identifier, while an expression evaluates to a value only.