表达式与语句

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

有人对表达式语句有好的定义吗?它们的区别是什么?

76840 次浏览

表达式可以求值得到值,而语句不返回值(它们的类型是无效)。

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

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

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

语句还可以包括表达式,而表达式实际上不包括任何语句。不过,lambda表达式是一个例外,它表示一个函数,因此可以包括函数可以包含的任何东西,除非语言只允许有限的lambdas,比如Python的单表达式lambdas。

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

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

例子:

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

两者之间的重要之处在于,您可以将表达式链接在一起,而语句则不能被链接。

很简单:表达式的值是一个值,而语句不是。

你可以在维基百科上找到这个,但是表达式被求值为某个值,而语句没有求值。

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

请注意,一些语言(如Lisp,我相信Ruby,以及许多其他语言)并不区分语句和表达式……在这样的语言中,所有东西都是一个表达式,并且可以与其他表达式连接。

表达式:一个值的值。例如:1 + 2 / x < br > 语句:执行某些操作的代码行。例如:转到100

在最早的通用编程语言(如FORTRAN)中,这种区别是非常明显的。在FORTRAN中,一条语句是一个执行单元,是你所做的一件事。它不被称为“线”的唯一原因是因为有时它跨越多条线。一个表达式本身什么都做不了……你必须把它赋值给一个变量。

1 + 2 / X

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

X = 1 + 2 / X

FORTRAN并没有我们今天所知道的语法,这个想法和巴克斯-诺尔表单(BNF)一起被发明出来,作为algolo -60定义的一部分。在这一点上,语义的区别(“有一个值”和“做某事”)被供奉在语法中:一种短语是表达式,另一种是语句,解析器可以区分它们。

后来语言的设计者模糊了这种区别:他们允许语法表达式做事情,他们允许有值的语法语句。 现存最早的流行语言例子是C语言。C语言的设计者意识到,如果允许你求一个表达式的值,然后放弃结果,那也没有什么害处。在C语言中,每个语法表达式都可以通过在后面加一个分号变成语句:

1 + 2 / x;

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

1 + 2 / callfunc(12);

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

一旦您允许任何表达式作为语句,您也可以在表达式中允许赋值操作符(=)。这就是为什么C允许你做

callfunc(x = 2);

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

这种表达式和语句的模糊出现在所有的C语言衍生物(C、c++、c#和Java)中,它们仍然有一些语句(如while),但允许几乎任何表达式用作语句(在c#中,只有赋值、调用、自增和自减表达式可以用作语句;见Scott Wisniewski的回答是)。

拥有两个“语法类别”(这是语句和表达式这类东西的专业名称)可能会导致重复工作。例如,C语言中有两种形式的条件句,一种是语句形式

if (E) S1; else S2;

以及表达式形式

E ? E1 : E2
有时人们想要复制不存在的:例如,在标准C中,只有语句可以声明一个新的局部变量—但是这个能力足够有用,可以使用 GNU C编译器提供了一个GNU扩展,允许表达式声明局部变量

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

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

注意,在C语言中,“=”实际上是一个运算符,它做两件事:

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

下面是一段ANSI C语法的摘录。你可以看到C语言没有很多不同种类的语句……程序中的大多数语句都是表达式语句,即结尾带有分号的表达式。

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

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


最重要的是:所有内容都返回一个值


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


在基于表达式的语言中,所有内容都返回一个值。这一开始可能有点奇怪——(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,我们的最终答案

通常需要稍微改变一下心态,才能最大限度地利用基于表达式的语言,因为所有东西都是表达式,这使得“内联”很多东西成为可能

举个简单的例子:

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

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

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

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

当然,这可能导致疯狂。作为基于表达式的脚本语言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
)

语句是表达式的一种特殊情况,类型为void。语言区别对待语句的倾向经常会导致问题,如果恰当地概括它们会更好。

例如,在c#中,我们有非常有用的Func<T1, T2, T3, TResult>重载泛型委托集。但是我们也必须有一个相应的Action<T1, T2, T3>集,并且通用的高阶编程必须不断地重复来处理这个不幸的分歧。

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

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

编译器能处理TResultvoid的可能性吗?是的。它所要做的就是要求return后跟一个类型为void的表达式。default(void)的结果将是void类型,并且传递的func将需要是Func<TValue, void>形式(这将等效于Action<TValue>)。

其他一些答案暗示你不能像连接表达式那样连接语句,但我不确定这个想法是从哪里来的。我们可以把出现在语句后面的;看作一个二进制中缀操作符,它取两个void类型的表达式,并将它们组合成一个void类型的表达式。

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

x = 5

读起来是“x得到5”。这是一个完整的句子。的代码

(x + 5)/9.0

结果是,x + 5都除以9.0这不是一个完整的句子。该声明

while k < 10:
print k
k += 1

是一个完整的句子。注意,循环头不是;而k <10、”是从句。

最准确地说,语句必须有“side-effect"(即是必要的),表达式必须有一个价值类型(即不是底类型)。

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


Void不是底部类型(它不是所有可能类型的子类型)。它存在于没有一个完全健全的类型系统. c语言中。这听起来可能有点势利,但完整性比如方差注释对于编写可扩展软件是至关重要的。

让我们看看维基百科对这件事是怎么说的。

https://en.wikipedia.org/wiki/Statement_(computer_science)

在计算机编程中,语句是必要的编程语言中最小的独立元素,它表示要执行的一些行动

许多语言(例如C语言)区分语句和定义,语句只包含可执行代码和声明标识符的定义,而表达式只计算值。

语句->按顺序执行的指令
表达式->返回值

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

表达式乍一看并不意味着和执行顺序,它们的目的是求值并返回值。在命令式编程语言中,表达式的求值是有顺序的,但这只是命令式模型的原因,而不是它们的本质。

语句示例:

for
goto
return
if

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

表达式示例:

2+2

(这并不是指执行,而是指评估)

语句

语句是构造所有c#程序的过程构建块。语句可以声明局部变量或常量,调用方法,创建对象,或为变量、属性或字段赋值。

由花括号括起来的一系列语句构成了一个代码块。方法体就是代码块的一个例子。

bool IsPositive(int number)
{
if (number > 0)
{
return true;
}
else
{
return false;
}
}

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

Expression

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

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

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

我更喜欢statement在形式逻辑意义上的意义。它改变了计算中一个或多个变量的状态,从而能够对它们的值做出真或假的声明。

我想,当新的术语或词汇被引入,现有的词汇被“重新定义”,或者用户对他们所描述的现有的、已建立的或“适当的”术语一无所知时,在计算世界和科学中总会出现困惑

下面是我找到的一个最简单的答案。

原文由Anders Kaseorg回答

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

可以使用操作符将表达式“水平”组合成更大的表达式,而语句只能通过一个接一个地写入或使用块结构来“垂直”组合。

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

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

我对这里的答案都不太满意。我查看了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

我们可以看到c++中被认为是语句的其他概念。

  • 表达式语句s是自解释的(语句可以由0或更多的表达式组成,仔细阅读语法,这很棘手)
  • 例如,case是一个标记语句
  • __abc3是if if/elsecase
  • __abc3是whiledo...whilefor (...)
  • __abc4是breakcontinuereturn(可以返回表达式),goto
  • 说明语句是声明的集合
  • try块是表示try/catch块的语句
  • 在语法中可能还有更多

以下是表达部分的节选:

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

为了改进和验证我之前的回答,编程语言术语的定义应该从计算机科学类型理论中解释。

表达式具有除Bottom类型以外的其他类型,即它有一个值。语句具有Unit或Bottom类型。

由此可见,语句只有在产生副作用时才能在程序中起作用,因为它要么不能返回值,要么只返回Unit类型的值,而Unit类型的值要么不可赋值(在一些语言中,如C的void),要么可以存储以用于语句的延迟求值(如Scala)。

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

这些概念的事实基础是:

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

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

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

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

          (BTW,关于C的材料,我想补充一下[引文],因为我不记得DMR是否有这样的意见。似乎不是,否则就没有理由在C语言的设计中保留功能重复:特别是逗号操作符和语句。)

          (以下基本原理并不是对最初问题的直接回应,但我觉得有必要澄清这里已经回答过的一些问题。)

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

            在通常的设计中,语句不能保证比表达式有更多的语义能力。
              许多语言已经成功地放弃了语句的概念,以获得干净、整洁和一致的整体设计。
              • 在这样的语言中,表达式可以做旧式语句能做的所有事情:在表达式求值时删除未使用的结果,要么显式地不指定结果(例如在RnRS Scheme中),要么具有普通表达式求值不能产生的特殊值(作为单元类型的值)。
              • 表达式求值的词法顺序规则可以被显式的序列控制操作符(例如Scheme中的begin)或一元结构的语法糖所取代。
              • 其他类型的“语句”的词法顺序规则可以派生为语法扩展(例如,使用卫生宏),以获得类似的语法功能。(它实际上可以做更多的事。)
              • 李< / ul > < / >
              • 相反,语句不能有这样的常规规则,因为它们不是在求值上构成的:只是没有这样的“子语句求值”的共同概念。(即使有,我怀疑除了从已有的表达式求值规则复制和粘贴之外,还能有更多的东西。)
                • 通常,保存语句的语言也有表达计算的表达式,并且有一个顶级子类别的语句保存为该子类别的表达式求值。例如,c++有所谓的表达式语句作为子类别,并使用discarded-value表达式求值规则来指定在这种上下文中全表达式求值的一般情况。一些语言(如c#)选择细化上下文以简化用例,但它使规范更加臃肿。
                • 李< / ul > < / > 李< / ul > < / > 对于编程语言的使用者来说,语句的意义可能会使他们更加困惑。
                  • 语言中表达式和语句规则的分离需要更多的努力来学习一门语言。
                  • 简单的词序解释隐藏了更重要的概念:表达式求值。(这可能是所有问题中最有问题的。)
                    • 即使语句中完整表达式的求值也受到词汇顺序的约束,子表达式则没有(必要)。除了与语句相关联的任何规则之外,用户最终应该学会这一点。(考虑如何让一个新手明白++i + ++i在c中是没有意义的。)
                    • 一些语言,如Java和c#,进一步限制子表达式的求值顺序,允许不考虑求值规则。这可能会更成问题。
                      • 对于已经了解表达式求值思想的用户来说,这似乎过于详细了。它还鼓励用户社区遵循语言设计的模糊思维模型。
                      • 它使语言规范更加膨胀。
                      • 在引入更复杂的原语之前,在计算中忽略了不确定性的表达,这对优化是有害的。
                      • 李< / ul > < / > 一些语言,如c++(特别是c++ 17),指定了更微妙的求值规则上下文,作为上述问题的折衷。
                        • 它使语言规范膨胀了很多。
                        • 这完全违背了对普通用户的简单性。
                        • 李< / ul > < / > 李< / ul > < / > 李< / ul > < / >

                        为什么是语句?不管怎样,历史已经一团糟了。似乎大多数语言设计者都没有仔细选择。

                        更糟糕的是,它甚至让一些类型系统爱好者(他们对PL历史不够熟悉)产生了一些误解,认为类型系统必须与操作语义上更基本的规则设计有重要关系。

                        严肃地说,基于类型的推理在许多情况下并不是那么糟糕,但在这个特殊情况下尤其没有建设性。即使是专家也会把事情搞砸。

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

在面向语句的编程语言中,代码块被定义为语句列表。换句话说,语句是可以放入代码块而不会导致语法错误的一段语法。

维基百科对statement这个词的定义类似

在计算机编程中,语句是命令式编程语言的语法单位,它表示要执行的某些操作。用这种语言编写的程序由一个或多个语句的序列组成

注意后一种说法。(尽管在这种情况下,“一个程序”在技术上是错误的,因为C和Java都拒绝一个不包含任何语句的程序。)

维基百科对表达式的定义是

编程语言中的表达式是一个语法实体,可以对其求值以确定其值

然而,这是错误的,因为在Kotlin中,throw new Exception("")是一个表达式,但在求值时,它只是抛出一个异常,从不返回任何值。

在静态类型编程语言中,每个表达式都有一个类型。然而,这个定义在动态类型编程语言中不起作用。

就我个人而言,我将表达式定义为一段语法,它可以由运算符或函数调用组成,以产生更大的表达式。这其实和维基百科对表达式的解释很相似:

它是一个或多个常量、变量、函数和操作符的组合,编程语言解释(根据其特定的优先级和关联规则)并计算产生(在有状态环境中为“返回”)另一个值

但是,问题是在C编程语言中,给定一个函数执行如下内容:

void executeSomething(void){
return;
}

executeSomething()是表达式还是语句?根据我的定义,它是一个语句,因为根据微软C引用语法的定义,

不能以任何方式使用具有void类型的表达式的(不存在的)值,也不能将void表达式(通过隐式或显式转换)转换为除void以外的任何类型

但同一页明确指出,这样的语法是一个表达式。

声明是一个不返回任何东西的代码块,它只是一个独立的执行单元。例如,

if(a>=0)
printf("Hello Humen,I'm a statement");

另一方面,表达式返回或计算一个新值。例如:

 if(a>=0)
return a+10;//This is an expression because it evalutes an new value;

 a=10+y;//This is also an expression because it returns a new value.

表达式 .

一段可以求值的语法。换句话说,表达式是表达式元素的累积,如字面量、名称、属性访问、操作符或函数调用,它们都返回一个值。与许多其他语言相比,并不是所有的语言结构都是表达式。还有一些语句不能用作表达式,比如while。赋值也是语句,而不是表达式。

语句

语句是套件(代码“块”)的一部分。语句可以是表达式,也可以是带有关键字的几个结构之一,如if、while或for。