资讯动态

C语言Python:表达式的求值顺序(evaluation order)的差异

发布时间:2026/10/27 9:25:02 来源:尧图企业网站定制
相关阅读Pythonhttps://blog.csdn.net/weixin_45791458/category_12403403.html?spm1001.2014.3001.5482C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm1001.2014.3001.5482C语言中表达式的求值C语言针对表达式的计算设置了操作符的优先级和结合性这两个特性优先级用于解析不同优先级的符号结合性用于解析相同优先级的符号。但是这两个特性并不能完全确定表达式的求值顺序至少对于大多数类型的操作符这就给编译器留下了一定的优化的空间下面举例说明这一点。假设有如下所示的简单表达式。例1 1 2 3C语言编译器在语法分析时会构建一个语法分析树类似图1所示的二叉树结构。图1 语法分析树在图1中12整体作为一个子表达式成为了操作符的左操作数这是操作符的结合性导致的结果。这个语法分析树保证了3和12会在根节点前求值1和2会在的左子节点前求值。但是这并没有保证3和12的求值顺序也没有保证1和2的求值顺序具体地来说编译器可能选择先对3求值随后对12求值在对12求值时先对2求值再对1求值也可能选择先对12求值在对12求值时先对1求值再对2求值最后对3求值....还有其他情况。这看似对最终表达式结果并没有什么影响不管哪种求值顺序结果都是6但如果将简单的操作数换成函数调用则会出现不同的情况如下例所示。例2 func1()func2()func3() int func1() { printf(This is func1.\n); return 1; } int func2() { printf(This is func2.\n); return 2; } int func3() { printf(This is func3.\n); return 3; }在这个例子中三个函数的执行顺序是不确定的可能是func1、func2、func3可能是func2、func1、func3可能是func3、func1、func2可能是func3、func2、func1。这就导致了printf语句的执行也是不确定的。下面再看一个更复杂的例子。例3 1 2 * 3在这个例子中由于*的优先级大于 2*3整体作为子表达式会成为的右操作数。解析得到的语法分析树如下图2所示。图2 语法分析树在这个例子中2与3的乘法毫无疑问是会在与1的加法前进行的但是对1、2、3的求值顺序是不确定的可能是先对1求值随后对2*3求值在对2*3求值时先对2求值再对3求值可能是先对2*3求值在对2*3求值时先对3求值再对2求值最后对1求值...还有其他情况。在这个简单的例子里不同的求值顺序对结果没有影响但如果将1、2、3换成func1、func2、func3分析是类似的即三个函数的执行顺序是不确定的至少是不完全确定的。也有少部分操作符是有规定求值顺序的下面将对此进行说明。一些有规定求值顺序的操作符逻辑与、逻辑或运算符逻辑与()和逻辑或(||)它们的求值顺序是从左到右的即先计算左操作数的值后计算右操作数的值。对于操作符如果其左操作数求值结果为0则右侧的操作数不会被求值最终结果为0对于||操作符如果其左操作数求值结果为1则右侧的操作数不会被求值最终结果为1这也被称为短路逻辑如例4所示。例4 int a 0; int b 1 if (a func1()) { // 这里func1不会执行因为 a 为 0 } if (b func1()) { // 这里func1不会执行因为 b 为 1 }三元条件运算符在条件表达式中首先计算条件部分然后根据条件的值选择是求值 真 分支还是 假 分支如下所示。condition ? expr1 : expr2;如果condition为真则只求值expr1否则只求值expr2如例5所示。例5 int x 1; int a (x 0) ? func1) : func2()) // func2()不会执行因为(x 0)为真逗号运算符逗号运算符会从左到右的顺序求值最后表达式的值为最右边操作数的值如下所示。expr1, expr2;首先求值expr1然后求值expr2整个表达式的值为expr2的结果如例6所示。例6 int x func1(), func2(); // func2()一定在func1()后执行深入理解其实如果深入了解这些特殊情况会发现这些确定的求值顺序是由序列点所带来得序列点(sequence point)是程序执行过程中一个特定的时刻保证在该点之前的所有操作包括表达式求值及其副作用都已经完成并生效而在该点之后的操作才开始。这意味着在序列点之前发生的所有更改都必须在继续执行下一个操作之前完全确定。对于除了上面三者的其他操作符一个表达式的序列点一般仅存于表达式末尾的分号;而对于逻辑与、逻辑或运算符或||处存在序列点对于三元条件运算符?处存在序列点对于逗号运算符每个逗号处都是序列点。这使得一些本来看起来像是出现了未定义行为的表达式是确定的如例7所示。例7 // 未定义行为 int a 1 int b a a ; // b值不确定只保证了a的自增在表达式完成遇到;时发生 // 逻辑与、逻辑或运算符 int c 0 int d c || c; // d值必然为真因为序列点保证了求值c时自增已经发生 // 三元条件运算符 int e 1 ; int f e ? e : 0; // e值必然为2因为序列点保证了求值e时自增已经发生 // 逗号运算符 int g 5; int h (g, g); // z值必然是6因为序列点保证了求值逗号右边的g前自增已经发生即使这样最好也不要使用这个特性因为这样的代码是很容易使人产生疑惑和不解的。Python中表达式的求值Python中规定了表达式的求值顺序是从左到右的。就拿上面的例1举例12子表达式一定在3之前求值而1一定在2之前求值。用数据结构的语言来说Python保证了在一个语法分析树中表达式的求值是后序遍历的即先求值左子节点后求值右子节点最后根据操作符求值整个表达式。下面来看一个例子。例8 func1 func2 * (func3 - func4)根据运算符的优先级这个表达式被解析为图3所示的语法分析树。图3 语法分析树根据后序遍历的定义这四个函数的执行顺序为func1、func2、func3、func4。详细说就是的左操作数func1一定会在右操作数func2 * (func3 - func4)前求值*的左操作数func2一定会在右操作数(func3 - func4)前求值-的左操作数func3一定会在右操作数func4前求值。不止是针对表达式的求值在Python中表达式列表的求值顺序也是确定的。表达式列表的定义如下所示即为多个由,分隔的表达式在函数调用、多变量赋值、函数返回中都有运用。expression_list :: expression (, expression)* [,]在如下例的函数调用中求值的顺序是func1、func2、func3、func4、func5其中func1也可以被求值前提函数func1的返回值是一个函数名。例9 func1(func2, func3, func4, func5)在如下例的多变量赋值中求值的顺序是func1、func2、func3。a, b, c func1(), func2(), func3()在如下例的函数返回中求值的顺序是func1、func2、func3.return func1(), func2(), func3()实际上后两种情况下表达式列表在被求值后会变成一个包含各表达式求值结果元组在多变量赋值操作中元组内的各个元素被赋值给对应的目标变量在函数返回中return语句返回一个元组。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑