《C语言解惑》一1.3 逻辑操作符和增量操作符

谜题1.3 逻辑操作符和增量操作符

请问,下面这个程序的输出是什么?

define PRINT(int) printf("%d\n",int)

main()
{
  int x, y, z;

  x = 2; y = 1; z = 0;
  x = x && y || z; PRINT(x);                 (1.3.1)
  PRINT( x || ! y && z );                   (1.3.2)

  x = y = 1;
  z = x ++ - 1; PRINT(x); PRINT(z);             (1.3.3)
  z += - x ++ + ++ y; PRINT(x); PRINT(z);          (1.3.4)
  z = x / ++ x; PRINT(z);  (1.3.5)
}

输出:

1                             (1.3.1)
1                             (1.3.2)
2                             (1.3.3)
0
3                             (1.3.4)
0
?                             (1.3.5)

解惑1.3 逻辑操作符和增量操作符

1.3.1

初始值:x = 2, y=1, z=0

x = x&&y || z  按照操作符的优先级和关联规则进行绑定。

x = (x&&y) || z

x = ((x&&y) || z)

(x = ((x&&y) || z))

(x = ((TRUE&&TRUE) || z))   逻辑操作符的求值顺序是从左到右。逻辑操作符的操作数如果是0,则解释为FALSE;如果是非零值,则解释为TRUE。

(x = (TRUE || z))   只有两个操作数都是TRUE,逻辑与(&&)的结果才会是TRUE;其他情况的求值结果都将是FALSE。

(x = (TRUE ||任意值))  只要有一个操作数是TRUE,那么不管另一个操作数是什么,逻辑或(||)的结果都将是TRUE。因此,我们不必再对这个表达式继续求值了。

(x = TRUE)

(x = 1)

1

再谈define:这个程序里的define语句与前一个程序里的define语句有所不同:这个程序里的“PRINT”是一个带参数的宏。在遇到带参数的宏时,C语言的预处理器将分两步进行替换:先把宏定义里的形式参数替换为实际参数,再把宏调用替换为宏定义体。
在这个程序里,“PRINT”宏有一个形式参数int。“PRINT(x)”是使用实际参数“x”进行的“PRINT”调用。在扩展“PRINT”调用的时候,C语言预处理器会先把宏定义里的所有“int”替换为“x”,再把宏调用“PRINT(x)”替换为结果字符串“printf``("%d\n", x)”。请注意,形式参数int并不是匹配单词“printf”里的“int”字符的。这是因为宏定义里的形式参数是标识符——具体到这个例子,形式参数int只对标识符int进行匹配和替换。

1.3.2

初始值:x = 1, y=1, z=0

x || ! y&&z

x || (! y)&&z  对操作符和操作数进行绑定。

x || ((! y)&&z)

(x || ((! y)&&z))

(TRUE || ((! y)&&z))  按从左到右的顺序求值。

(TRUE ||任意值)

TRUE,即1

1.3.3

初始值:x = 1,y=1

z = x ++ - 1

z = (x ++) – 1  对操作符和操作数进行绑定。

z = ((x ++) - 1)

(z = ((x ++) - 1))

(z = (1 - 1)),此时x=2  出现在操作数右边的“++”操作符是所谓的“后”递增操作符:先在表达式里用该操作数完成有关的计算,再对它进行递增。

(z = 0)

0

1.3.4

初始值:x = 2, y=1, z=0

z += - x ++ + ++ y

z += - (x ++) + (++ y)   一元操作符的关联是从右到左,因而“++”操作符将先于一元操作符“-”得到绑定。(事实上,如果先去绑定“-”操作符的话,这个表达式将无法成立。这是因为“++”和“--”操作符都要求以一个变量(更准确地说,以一个“左值”[1])作为其操作数。“x”是一个左值,但“-x”不是。)

z += (- (x ++)) + (++ y)

z += ((- (x ++)) + (++ y))

(z += ((- (x ++)) + (++ y)))

(z += ((-2) + 2)),此时x=3, y=2

(z += 0)

(z = 0 + 0)

(z = 0)

0

按照从内到外的顺序依次求值。

关于记号:计算机程序的源代码文本是由一系列记号构成的,而编译一个程序的第一项工作就是把那些记号一个一个地分解开。这在绝大多数场合都没有什么困难,但有些字符序列可能会让人感到困惑。比如说,如果上面这个例子里的表达式有一部分根本没有空格——就像下面这样:

x+++++y

那么,这个没有空格的表达式与有空格的表达式还会是等价的吗?

为避免产生二义性,如果一个字符串能够解释为多个操作符,C语言编译器将按照“构成操作符的字符个数越多越好”的原则来作出选择。根据这一原则,“x+++++y”解释为:

x++ ++ + y

而这已经不是一个合法的表达式了。

1.3.5

初始值:x = 3,z=0

z = x / ++ x

z = x / (++ x)

z = (x / (++ x))

(z = (x / (++ x)))

如果你还像以前那样按照从内到外的顺序对这个表达式进行求值,即先对x进行递增,然后作为除数、用x作被除数去进行除法计算。问题是:作为被除数的x到底是几?是3还是4?换个问法,被除数到底是递增前的x值,还是递增后的x值?请注意,C语言并没有对这种“副作用”作出明确的规定,而是由C编译器的编写者决定的[2]。这个例子的教训是:如果你无法断定会不会产生副作用,那么就尽量不要写这样的表达式。

时间: 2024-12-06 06:55:27

《C语言解惑》一1.3 逻辑操作符和增量操作符的相关文章

《C语言解惑》—— 2.9 输出值的操作符

2.9 输出值的操作符 在例2.11中,既然&a[i]表示数组a的各个元素的地址,这些地址存储相应数组元素的值,就应该能直接将这些值输出.[例2.13]使用"*"操作符输出数组内容. #include <stdio.h> void main ( ) { int a[3]={1,2,3}; int i=0; //输出数组地址 for(i=0;i<3;i++) printf("0x%p ",&a[i]); printf("\n

《C语言解惑》一导读

前言 C语言解惑 C语言并不大--如果以参考手册的篇幅作为衡量标准的话,C语言甚至可以归为一种"小"语言.不过,这种"小"并不意味着C语言的功能不够强大,而是说明了C语言里的限制性规则比较少.C语言本身的设计非常简洁精妙,这一点相信C语言的使用者早已有所体会. 不过,C语言的这种精妙对C语言的初学者来说,似乎是故作神秘.因为限制较少,C语言可以写成内容丰富的表达式,这可能会被初学者认为是输出错误.C语言的紧凑性允许以简洁凝炼的方式实现常见的编程任务. 学用C语言的过

《C语言解惑》一第1章 操作符1.1 基本算术操作符

第1章 操作符 C语言解惑 C语言程序由语句构成,而语句由表达式构成,表达式又由操作符和操作数构成.C语言中的操作符非常丰富--本书的附录B所给出的操作符汇总表就是最好的证据.正是因为这种丰富性,为操作符确定操作数的规则就成为了我们理解C语言表达式的核心和关键.那些规则--即所谓的"优先级"和"关联性"--汇总在本书附录A的操作符优先级表里.请使用该表格来解答本章中的谜题. 谜题1.1 基本算术操作符 请问,下面这个程序的输出是什么? main() { int x;

《C语言解惑》一1.5 关系操作符和条件操作符

谜题1.5 关系操作符和条件操作符 请问,下面这个程序的输出是什么? define PRINT(int) printf(#int " = %d\n",int) main() { int x=1, y=1, z=1; x += y += z; PRINT( x < y ? y : x ); (1.5.1) PRINT( x < y ? x ++ : y ++ ); PRINT(x); PRINT(y); (1.5.2) PRINT( z += x < y ? x ++

C++中点操作符和箭头操作符的使用详解_C 语言

区别 C++中对于类来说,对于其中的成员,用点操作符.来获得, 而对于一个指向类对象的指针来说,则用箭头操作符->调用该指针所指向对象的成员. 当类定义->重载操作符后,则既可以用箭头操作符,也可以用点操作符. 重载->操作符 重载箭头操作符必须定义为类成员函数.没有显式形参(而且是类成员,唯一隐式形参是this).->的右操作数不是表达式,而是对应类成员的一个标识符,由编译器处理获取成员工作. 重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象.

C# 操作符之三元操作符浅析_C#教程

C# 操作符之三元操作符"?:"是什么呢? C# 操作符之三元操作符"?:"有时也称为条件操作符. 对条件表达式b?x:y,先计算条件b,然后进行判断. 如果b的值为true,计算x的值,运算结果为x的值:否则,计算y,运算结果为y的值. 一个条件表达式从不会又计算x,也计算y.条件操作符是向右关联的,也就是说,从左向右分组计算. C# 操作符之三元操作符"?:"操作实例: 表达式a?b:c?d:e将按a?b:(C?d:e)形式执行. ?:的第二

《C语言解惑》一1.4 二进制位操作符

谜题1.4 二进制位操作符 请问,下面这个程序的输出是什么? define PRINT(int) printf(#int " = %d\n", int) main() { int x, y, z; x = 03; y = 02; z = 01; PRINT( x | Y & z ); (1.4.1) PRINT( x | y & ~ z ); (1.4.2) PRINT( x ^ y & ~ z ); (1.4.3) PRINT( x & y &

《C语言解惑》一1.6 操作符的优先级和求值顺序

谜题1.6 操作符的优先级和求值顺序 请问,下面这个程序的输出是什么? define PRINT3(x,y,z) \ printf(#x "=%d\t" #y "=%d\t" #z "=%d\n",x,y,z) main() { int x, y, z; x = y = z = 1; ++x || ++y && ++z; PRINT3(x,y,z); (1.6.1) x = y = z = 1; ++x && ++

《C语言解惑》—— 导读

前 言 C语言编程仍然是编程工作者必备的技能.目前有四类典型的学习C语言的教材:第一类是以讲授语法为主线,即流行的教科书方式,所涉及的例题均以正确的程序为主:第二类是以案例教学为主的教材,摆脱了语法的部分约束:第三类是以讲解编程技术为主的经验之作,主要针对已有编程基础的读者:第四类是针对编程容易产生错误的专题,对比正确与错误的程序以提高编程能力,涉及的内容比较专业.这些教材各有千秋,其共同的目的都是想教会读者如何编写正确.规范的程序.我们也曾在两部教材的每一章中尝试增加一节错误分析的内容,以期让