单元表达式 第4单元表达式 本单元教学目标 介绍C++的表达式和表达式语句。 学习要求 熟练掌握C艹+的各种表达式,特别是赋值表达式及其他有副作用的表达式 授课内容 在任何高级程序设计语言中,表达式都是最基本的的组成部分。形式化的表达式定义比 较复杂,需要用到递归定义的概念。简单说来,表达式是由运算符将运算对象(如常数,变 量和函数等)连接起来的具有合法语义的式子。在C艹中,由于运算符比较丰富(达数十种之 多),加之引入了赋值等有副作用的运算符,因而可以构成灵活多样的表达式。这些表达式的 应用一方面可以使程序编写得短小简洁,另一方面还可以完成某些在其他高级程序设计语 言中较难实现的运算功能 学习C++的表达式时应注意以下几个方面 (1)运算符的正确书写方法。C++的许多运算符与通常在数学公式中所见到的符号有很 大差别,例如:整除求余(%),相等(==),逻辑运算与(&&)等等。 今、(2)运算符的确切含义和功能。C艹语言中有一些比较特殊的运算符,有些运算符还有 谓“副作用”,这些都给我们的学习带来了一定的困难 (3)运算符与运算对象的关系。C+的运算符可以分为单目运算符(仅对一个运算对象 进行操作)、双目运算符(需要2个运算对象)甚至还有复合表达式,其中的两个运算符对三 个或者更多个运算对象进行操作 (4)运算符具有优先级和结合方向。如果一个运算对象的两边有不同的运算符,首先执行 优先级别较高的运算。如果一个运算对象两边的运算符级别相同,则应按由左向右的方向顺 序处理。各运算符的优先顺序可以参看表4-3:“运算符的优先级别和结合方向”。如果编程 序时对运算符的优先顺序没有把握,可以通过使用括号来明确其运算顺序 41算术运算符和算术表达式 C++的算术运算符有 +(加),-(减),*(乘),/(除,%(整除求余)
第 4 单元 表达式 - 66 - 第 4 单元 表达式 本单元教学目标 介绍C++的表达式和表达式语句。 学习要求 熟练掌握C++的各种表达式, 特别是赋值表达式及其他有副作用的表达式。 授课内容 在任何高级程序设计语言中, 表达式都是最基本的的组成部分。形式化的表达式定义比 较复杂, 需要用到递归定义的概念。简单说来, 表达式是由运算符将运算对象 (如常数, 变 量和函数等) 连接起来的具有合法语义的式子。在C++中, 由于运算符比较丰富(达数十种之 多), 加之引入了赋值等有副作用的运算符, 因而可以构成灵活多样的表达式。这些表达式的 应用一方面可以使程序编写得短小简洁, 另一方面还可以完成某些在其他高级程序设计语 言中较难实现的运算功能。 学习C++的表达式时应注意以下几个方面: (1) 运算符的正确书写方法。C++的许多运算符与通常在数学公式中所见到的符号有很 大差别, 例如: 整除求余(%), 相等(= =), 逻辑运算与(&&)等等。 (2) 运算符的确切含义和功能。C++语言中有一些比较特殊的运算符, 有些运算符还有 所谓“副作用”, 这些都给我们的学习带来了一定的困难。 (3) 运算符与运算对象的关系。C++的运算符可以分为单目运算符 (仅对一个运算对象 进行操作)、双目运算符(需要 2 个运算对象), 甚至还有复合表达式, 其中的两个运算符对三 个或者更多个运算对象进行操作。 (4)运算符具有优先级和结合方向。如果一个运算对象的两边有不同的运算符, 首先执行 优先级别较高的运算。如果一个运算对象两边的运算符级别相同, 则应按由左向右的方向顺 序处理。各运算符的优先顺序可以参看表 4-3:“运算符的优先级别和结合方向”。如果编程 序时对运算符的优先顺序没有把握, 可以通过使用括号来明确其运算顺序。 4.1 算术运算符和算术表达式 C++的算术运算符有: + (加), − (减), * (乘), / (除), % (整除求余)
第4单元表达式 其中“/”为除法运算符。如果除数和被除数均为整型数据,则结果也是整数。例如,5/3的 结果为1。“%”为整除求余运算符。“%”运算符两侧均应为整型数据,其运算结果为两个 运算对象作除法运算的余数。例如5%3的结果为2。 在C++中,不允许两个算术运算符紧挨在一起,也不能象在数学运算式中那样,任意省 略乘号,以及用中圆点“·”代替乘号等。如果遇到这些情况,应该使用括号将连续的算术 运算符隔开,或者在适当的位置上加上乘法运算符。例如 应写 (x+y)(x-y)应写成(x+y)*(x-y) 42逻辑运算符和逻辑表达式 C++中有6种比较运算符 (大于), z & x*y0 && !isgreat(z) 的运算顺序为 计算x*y /算术运算优先于比较运算 计算x*y>z /比较运算优先于逻辑运算 计算x*yz&&x*y<100 /逻辑与运算优先于逻辑或运算 计算-x /单目运算优先于双目运算 计算-x*y /算术运算优先于比较运算 1实际上,逻辑表达式的值为整数类型,0表示逻辑值“假”,任何其他非0值都表示逻辑值“真”。在 Visual c艹+中,使用类型助记符bol和逻辑值true, false可使程序易于理解。这些助记符也可写成BOOL TRUE和 FALSE
第 4 单元 表达式 - 67 - 其中“/”为除法运算符。如果除数和被除数均为整型数据, 则结果也是整数。例如, 5/3 的 结果为 1。“%”为整除求余运算符。“%”运算符两侧均应为整型数据, 其运算结果为两个 运算对象作除法运算的余数。例如 5%3 的结果为 2。 在C++中, 不允许两个算术运算符紧挨在一起, 也不能象在数学运算式中那样, 任意省 略乘号, 以及用中圆点“· ”代替乘号等。如果遇到这些情况, 应该使用括号将连续的算术 运算符隔开, 或者在适当的位置上加上乘法运算符。例如 x*−y 应写成 x*(−y) (x+y)(x−y) 应写成 (x+y)*(x−y) 4.2 逻辑运算符和逻辑表达式 C++中有 6 种比较运算符: > (大于), = (大于等于), z && x*y0 && !isgreat(z) 的运算顺序为: 计算 x*y // 算术运算优先于比较运算 计算 x*y>z // 比较运算优先于逻辑运算 计算 x*yz && x*y<100 // 逻辑与运算优先于逻辑或运算 计算 −x // 单目运算优先于双目运算 计算 −x*y // 算术运算优先于比较运算 1 实际上,逻辑表达式的值为整数类型,0 表示逻辑值“假”, 任何其他非 0 值都表示逻辑值“真”。 在 Visual C++中,使用类型助记符 bool 和逻辑值 true, false 可使程序易于理解。这些助记符也可写成 BOOL, TRUE 和 FALSE
单元表达式 计算-x*y>0 /比较运算优先于逻辑运算 计算 great(z) /计算函数值优先于任何运算符 计算! Isgreat(z) /单目运算优先于双目运算 计算-x*y>0&&! Isgreat(z)//逻辑与运算优先于逻辑或运算 计算x*y》z&&x*y0&&! Isgreat(z)2 43赋值运算符和赋值表达式 C艹+将赋值作为一个运算符处理。赋值运算符为“=”,用于构造赋值表达式。赋值表 达式的格式为 其中Ⅴ表示变量,e表示一个表达式。赋值表达式的值等于赋值运算符右边的表达式的值。 其实,赋值表达式的价值主要体现在其副作用上,即赋值运算符可以改变作为运算对象的变 量V的值。赋值表达式的副作用就是将计算出来的表达式e的值存入变量V 和其他表达式一样,赋值表达式也可以作为更复杂的表达式的组成部分。例如 由于赋值运算符的优先级较低(仅比逗号运算符高,见自学部分)。并列的赋值运算符 之间的结合方向为从右向左,所以上述语句的执行顺序是:首先计算出表达式mn的值,然 后再处理表达式j=m*n,该表达式的值就是m*n的值,其副作用为将该值存入变量j。最后 处理表达式i=j=m*n,其值即第一个赋值运算符右面的整个表达式的值,因此也就是mn 的值(最后计算出的这一赋值表达式的值并没有使用),其副作用为将第一个赋值运算符右 面整个表达式的值存入变量i。因此,上述表达式语句的作用是将mn的值赋给变量i和j 整个运算过程如下(设m的值为2,n的值为3) 计算m*n的值 2*3等于6; 计算j=m*n的值:j=6的值等于6,其副作用为将6存入变量j; 计算i=j=m*n的值 6的值等于6,其副作用为将6存入变量 44自增运算符和自减运算符 C++中有两个很有特色的运算符:自增运算符“+”和自减运算符“-”。这两个运算 符也是C++程序中最常用的运算符,以致于它们几乎成为C++程序的象征 艹+”和“—”运算符都是单目运算符,且其运算对象只能是整型变量或指针变量。这 实际的运算顺序与这里介绍的略有区别,主要是因为C+语言在执行表达式时进行了优化工作。例如,对 于表达式x*y>z&&x*yz不成立(等于0),则无论第二个表达式 x*y<100成立或不成立,整个表达的值均为0不成立),因此就无需计算第二个表达式
第 4 单元 表达式 - 68 - 计算 −x*y>0 // 比较运算优先于逻辑运算 计算 isgreat(z) // 计算函数值优先于任何运算符 计算 !isgreat(z) // 单目运算优先于双目运算 计算 −x*y>0 && !isgreat(z) // 逻辑与运算优先于逻辑或运算 计算 x*y>z && x*y0 && !isgreat(z) 2 4.3 赋值运算符和赋值表达式 C++将赋值作为一个运算符处理。赋值运算符为“=”,用于构造赋值表达式。赋值表 达式的格式为: V = e 其中 V 表示变量, e 表示一个表达式。赋值表达式的值等于赋值运算符右边的表达式的值。 其实, 赋值表达式的价值主要体现在其副作用上, 即赋值运算符可以改变作为运算对象的变 量 V 的值。赋值表达式的副作用就是将计算出来的表达式 e 的值存入变量 V。 和其他表达式一样,赋值表达式也可以作为更复杂的表达式的组成部分。例如 i = j = m*n; 由于赋值运算符的优先级较低(仅比逗号运算符高, 见自学部分)。并列的赋值运算符 之间的结合方向为从右向左, 所以上述语句的执行顺序是: 首先计算出表达式 m*n 的值, 然 后再处理表达式 j = m*n, 该表达式的值就是 m*n 的值, 其副作用为将该值存入变量 j。最后, 处理表达式 i = j = m*n, 其值即第一个赋值运算符右面的整个表达式的值, 因此也就是 m*n 的值 (最后计算出的这一赋值表达式的值并没有使用), 其副作用为将第一个赋值运算符右 面整个表达式的值存入变量 i。因此,上述表达式语句的作用是将 m*n 的值赋给变量 i 和 j。 整个运算过程如下 (设 m 的值为 2,n 的值为 3): 计算 m*n 的值: 2*3 等于 6; 计算 j = m*n 的值 : j = 6 的值等于 6, 其副作用为将 6 存入变量 j; 计算 i = j = m*n 的值: i = 6 的值等于 6, 其副作用为将 6 存入变量 i。 4.4 自增运算符和自减运算符 C++中有两个很有特色的运算符: 自增运算符“++”和自减运算符“−−”。这两个运算 符也是C++程序中最常用的运算符, 以致于它们几乎成为C++程序的象征。 “++”和“−−”运算符都是单目运算符, 且其运算对象只能是整型变量或指针变量。这 2 实际的运算顺序与这里介绍的略有区别, 主要是因为C++语言在执行表达式时进行了优化工作。例如, 对 于表达式 x*y>z && x*yz 不成立(等于 0), 则无论第二个表达式 x*y<100 成立或不成立, 整个表达的值均为 0(不成立), 因此就无需计算第二个表达式
单元表达式 两个运算符既可以放在作为运算对象的变量之前,也可以放在变量之后。这四种表达式的值 分别为 ++的值和i的值相同 i—-的值和i的值相同 的值为i+1 i的值为i-1。 然而,“++”和“-”这两个运算符真正的价值在于它们和赋值运算符类似,在参加运 算的同时还改变了作为运算对象的变量的值。++和计+会使变量i的值增大1;类似地 和ⅰ-会使变量i的值减1。因此,考虑到副作用以后,“++”和“-”构成的4种表达式的含 义见表4-1(设i为一整型变量)。 表41自增运算符和自减运算符的用法 表达式 表达式的值 副作用 1+ i的值增大1 ++1 i+1 i的值增大1 i的值减少1 i的值减少1 ++”表达式和“-”表达式既可以单独使用,也可以出现于更复杂的表达式中。例如 /i增加 /i减少1 x=aray[++i];〃/将aray[i+1]的值赋给x,并使i增加1 sl[i++]=s2[j++];∥/将s2[j赋给sl[i],然后分别使i和j增加1 作为运算符来说“艹+”和“—-”的优先级较高高于所有算术运算符和逻辑运算符。但 在使用这两个运算符时要注意它们的运算对象只能是变量,不能是其他表达式。例如, (计+j)++就是一个错误的表达式。 引入含有“++”、“-”以及赋值运算符这类有副作用的表达式的目的在于简化程序的 编写。例如,表达式语句i=j=m*n,的作用和 J=m*n 完全一样,而表达式语句sl[计++]=s2[+,其实正是下列语句的简化表达方式 l[i] s2[j] i=i+1 例4字符串连接 算法:所谓字符串连接,就是将两个字符串合并成一个新的字符串。函数 strato 可以实现字符串连接的功能,其具体做法是将第二个字符串的内容复制到第一个字符串的
第 4 单元 表达式 - 69 - 两个运算符既可以放在作为运算对象的变量之前, 也可以放在变量之后。这四种表达式的值 分别为: i++ 的值和 i 的值相同; i−− 的值和 i 的值相同; ++i 的值为 i+1; −−i 的值为 i-1。 然而, “++”和“−−”这两个运算符真正的价值在于它们和赋值运算符类似, 在参加运 算的同时还改变了作为运算对象的变量的值。++i 和 i++会使变量 i 的值增大 1; 类似地, −−i 和 i−−会使变量 i 的值减 1。因此,考虑到副作用以后,“++”和“−−”构成的 4 种表达式的含 义见表 4-1(设 i 为一整型变量)。 表 4-1 自增运算符和自减运算符的用法 表达式 表达式的值 副作用 i++ ++i i-- --i i i+1 i i-1 i 的值增大 1 i 的值增大 1 i 的值减少 1 i 的值减少 1 ++”表达式和“−−”表达式既可以单独使用, 也可以出现于更复杂的表达式中。例如 i++; // i 增加 1 −−i; // i 减少 1 x = array[++i]; // 将 array[i+1]的值赋给 x, 并使 i 增加 1 s1[i++] = s2[j++]; // 将 s2[j]赋给 s1[i], 然后分别使 i 和 j 增加 1 作为运算符来说,“++”和“−−”的优先级较高,高于所有算术运算符和逻辑运算符。但 在使用这两个运算符时要注意它们的运算对象只能是变量, 不能是其他表达式。例如, (i+j)++就是一个错误的表达式。 引入含有“++”、“−−”以及赋值运算符这类有副作用的表达式的目的在于简化程序的 编写。例如, 表达式语句 i = j = m*n; 的作用和 j = m*n; i = j; 完全一样; 而表达式语句 s1[i++] = s2[j++]; 其实正是下列语句的简化表达方式: s1[i] = s2[j]; i = i+1; j = j+1; [例 4-1] 字符串连接。 算 法: 所谓字符串连接, 就是将两个字符串合并成一个新的字符串。函数 mstrcat() 可以实现字符串连接的功能, 其具体做法是将第二个字符串的内容复制到第一个字符串的
第4单元表达式 尾部去 程序 ∥ Example4-1:连接两个字符串 void mstrcat(char destin[, char source[) int 1 while(source [j]!=0) destin[i++]= source [j++] destin[i] =0 分析:函数 mistreat()将字符串 source的内容复制在字符串 destin的后面。显然,字 符型数组 destin的大小一定要能够容纳得下连接后的新字符串才行。在复制工作开始之前 首先要确定在 destin中的复制位置,这是通过库函数 strlen(完成的 自学内容 45其他具有副作用的运算符 除了“++”和“-”以外,在C++中还有其他一些有副作用的复合运算符,它们都是以 称为赋值运算符“=”为基础构成的。例如算术复合赋值运算符“+=”,“-=”,“*=”,“仁=” 以及“%=”;另外还有用位运算符和赋值运算符复合而成的复合赋值运算符(见4.8:“位运 算表达式”)。算术复合运算符的形式为 V+=e的含义为将表达式e的值加在变量V上; V-=e的含义为将表达式e的值从变量V中减去 V*=e的含义为将变量V与表达式e的乘积存入变量V中 V/=e的含义为将变量V和表达式e的商存入变量V中; V%=e的含义为将变量V和表达式e的余数存入变量V中。 C+引入了一些有副作用的表达式,一方面丰富了程序的表达方式,使得C++程序的 形式简洁、干练;生成的目标代码的效率也比较高。但另一方面这些表达式比较复杂,难于 理解和调试,有时还会因为不同的C艹+编译程序对计算顺序的规定不同而产生二义性的解 释。因此在编程时要慎重使用。为了确保实现自己所要求的计算顺序,可以通过加括号的方 法加以明确;甚至可以将由多个有副作用的表达式组成的复杂表达式语句分解成几个比较 简单的表达式语句处理
第 4 单元 表达式 - 70 - 尾部去。 程 序 // Example 4-1:连接两个字符串 void mstrcat(char destin[], char source[]) { int i = strlen(destin), j = 0; while(source[j]!=0) destin[i++] = source[j++]; destin[i] = 0; } 分 析: 函数 mstrcat()将字符串 source 的内容复制在字符串 destin 的后面。显然, 字 符型数组 destin 的大小一定要能够容纳得下连接后的新字符串才行。在复制工作开始之前, 首先要确定在 destin 中的复制位置, 这是通过库函数 strlen()完成的。 自学内容 4.5 其他具有副作用的运算符 除了“++”和“−−”以外, 在C++中还有其他一些有副作用的复合运算符, 它们都是以 称为赋值运算符“=”为基础构成的。例如算术复合赋值运算符“+=”,“−=”,“*=”,“/=” 以及“%=”; 另外还有用位运算符和赋值运算符复合而成的复合赋值运算符(见 4.8:“位运 算表达式”)。算术复合运算符的形式为: V += e 的含义为将表达式 e 的值加在变量 V 上; V −= e 的含义为将表达式 e 的值从变量 V 中减去; V *= e 的含义为将变量 V 与表达式 e 的乘积存入变量 V 中; V /= e 的含义为将变量 V 和表达式 e 的商存入变量 V 中; V %= e 的含义为将变量 V 和表达式 e 的余数存入变量 V 中。 C++引入了一些有副作用的表达式, 一方面丰富了程序的表达方式, 使得C++程序的 形式简洁、干练; 生成的目标代码的效率也比较高。但另一方面这些表达式比较复杂, 难于 理解和调试, 有时还会因为不同的C++编译程序对计算顺序的规定不同而产生二义性的解 释。因此在编程时要慎重使用。为了确保实现自己所要求的计算顺序, 可以通过加括号的方 法加以明确; 甚至可以将由多个有副作用的表达式组成的复杂表达式语句分解成几个比较 简单的表达式语句处理
单元表达式 46问号表达式和逗号表达式 C++中还提供了一种比较复杂的表达式,即问号表达式,又称条件表达式。问号表达式 使用两个运算符(?和:)对三个运算对象进行操作,格式为 ? 问号表达式的值是这样确定的:如果的值为非零值,则问号表达式的值就是 的值;如果的值等于0,则问号表达式的值为的值。利用问 号表达式可以简化某些选择结构的编程。例如,分支语句 等价于语句 >y ?x:y 例4-2编写一个求绝对值的函数。 程序 ∥/ Example4-2:求双精度类型量的绝对值 double dabs(double x) return x>0?x: -x 在C+中可以使用逗号(,)将几个表达式连接起来,构成逗号表达式。逗号表达式的 格式为 ,,, 在程序执行时,按从左到右的顺序执行组成逗号表达式的各表达式,而将最后一个表达 式(即表达式n)的值作为逗号表达式的值。 逗号表达式常用于简化程序的编写。例如,如下程序结构 if(x>y) t =x 可以利用逗号表达式简化为
第 4 单元 表达式 - 71 - 4.6 问号表达式和逗号表达式 C++中还提供了一种比较复杂的表达式, 即问号表达式, 又称条件表达式。问号表达式 使用两个运算符 ( ? 和 : ) 对三个运算对象进行操作, 格式为: ?: 问号表达式的值是这样确定的: 如果的值为非零值, 则问号表达式的值就是 的值; 如果的值等于 0, 则问号表达式的值为的值。利用问 号表达式可以简化某些选择结构的编程。例如, 分支语句 if(x>y) z = x; else z = y; 等价于语句 z = x>y ? x : y; [例 4-2] 编写一个求绝对值的函数。 程 序: // Example 4-2:求双精度类型量的绝对值 double dabs(double x) { return x>0?x:-x; } 在C++中可以使用逗号 ( , ) 将几个表达式连接起来,构成逗号表达式。逗号表达式的 格式为: , , ..., 在程序执行时, 按从左到右的顺序执行组成逗号表达式的各表达式, 而将最后一个表达 式 (即表达式 n) 的值作为逗号表达式的值。 逗号表达式常用于简化程序的编写。例如, 如下程序结构 if(x>y) { t = x; x = y; y = t; } 可以利用逗号表达式简化为: if(x>y)
单元表达式 47表达式中各运算符的运算顺序 大家知道,四则运算的运算顺序可以归纳为“先乘、除,后加、减”,也就是说乘、除 运算的优先级别比加减运算的优先级别要高。C语言中有几十种运算符,仅用一句“先乘 除,后加、减”是无法表示各种运算符之间的优先关系的,因此必须有更严格的确定各运算 符优先关系的规则。表43列出了各种运算符的优先级别和同级别运算符的运算顺序(结合 方向) 表4-3运算符的优先级别和结合方向 优先级别运算符 运算形式结合方 名称或含义 圆括号 自左至右数组下标 结构体成员 用指针访问结构体成员 负号和正号 ++X 或 自增运算和自减运算 逻辑非 自右至左按位取反 类型转换 由地址求内容 求变量的地址 求某类型变量的长度 除和求余 el+e2 自左至右加和减 el<<d2 自左至右左移和右移 自左至右关系运算(比较) =自左至右等手和不等于比纹一 el&e2 自左至右按位与 自左至右按位异或 左至右按位或 自左至右逻辑与(并且) 续表4-3 运算符 运算形式结合方向 称或含义 12 自左至右逻辑(或者) e1?e2. e3 自右至左条件运算
第 4 单元 表达式 - 72 - t = x, x = y, y = t; 4.7 表达式中各运算符的运算顺序 大家知道, 四则运算的运算顺序可以归纳为“先乘、除, 后加、减”, 也就是说乘、除 运算的优先级别比加减运算的优先级别要高。C语言中有几十种运算符, 仅用一句“先乘、 除, 后加、减”是无法表示各种运算符之间的优先关系的, 因此必须有更严格的确定各运算 符优先关系的规则。 表 4-3 列出了各种运算符的优先级别和同级别运算符的运算顺序(结合 方向)。 表 4-3 运算符的优先级别和结合方向 优先级别 运算符 运算形式 结合方向 名称或含义 1 () [] . -> (e) a[e] x.y p->x 自左至右 圆括号 数组下标 结构体成员 用指针访问结构体成员 2 - + ++ -- ! ~ (t) * & sizeof -e ++x 或 x++ !e ~e (t)e *p &x sizeof(t) 自右至左 负号和正号 自增运算和自减运算 逻辑非 按位取反 类型转换 由地址求内容 求变量的地址 求某类型变量的长度 3 * / % e1*e2 自左至右 乘、除和求余 4 + - e1+e2 自左至右 加和减 5 > e1 >= e1<e2 自左至右 关系运算(比较) 7 == != e1==e2 自左至右 等于和不等于比较 8 & e1&e2 自左至右 按位与 9 ^ e1^e2 自左至右 按位异或 10 | e1|e2 自左至右 按位或 11 && e1&&e2 自左至右 逻辑与(并且) 续表 4-3 优先级别 运算符 运算形式 结合方向 名称或含义 12 || e1||e2 自左至右 逻辑(或者) 13 ? : e1?e2:e3 自右至左 条件运算 14 = 赋值运算
4单元表达式 自右至左复合赋值运算 自左至右顺序求值运算 说明:运算形式一栏中各字母的含义如下:a-数组,e一表达式,p一指针,t一类型,xy-变量 由表4-3可以看出,运算优先级的数字越大,优先级别越低。优先级别最高的是括号,所 以如果我们要改变混合运算中的运算次序,或者对运算次序把握不准时,可以使用括号来明 确规定运算的顺序 运算符的结合方向是对级别相同的运算符而言的,说明了在几个并列的级别相同的运 算符中运算的次序。大部分运算符的结合方向都是“自左至右”,例如表达式x*y/3,运算 次序就是先计算x*y,然后将其结果除以3。也有些运算符的结合顺序与此相反,是“自右 至左”,例如赋值运算符。表达式i=j=0的计算顺序是首先将0赋给变量j,然后再将 表达式j=0的值(仍为0)赋给变量i 48类型不同的数据之间的混合算术运算 大多数运算符对运算对象的类型有严格的要求。例如,%运算符只能用于两个整型数据 的运算,所有的位运算符也只适用于整型数据。但是算术四则运算符适用于所有的整型(包 括char、int和long)、浮点型( float)和双精度型( double)数据,因此存在一个问题:不同 类型的数据的运算结果的类型怎样确定? C艹规定,不同类型的数据在参加运算之前会自动转换成相同的类型,然后再进行运 算。运算结果的类型也就是转换的类型。转换的规则为 (1)转换的原则是级别低的类型转换为级别高的类型。各类型按级别由低到高的顺序 i char, int, unsigned, long, unsigned long, float, double 因此,即使是两个char型的数据运算,也要先转换为int型,运算的结果也是int型;两 个foat型数据要先转换为 double型,然后参加运算,其结果为 double型。不同类型的数据 之间也是这样,如一个char类型的数据和一个int类型的数据运算,结果为int型;一个int 型的数据和一个foat型数据的运算结果的类型为 double型。 另外,C艹规定,有符号类型数据和无符号类型的数据进行混合运算,结果为无符号类 型。例如,int型数据和 unsigned类型数据的运算结果为 unsigned型 对于赋值运算来说,如果赋值运算符右边的表达式的类型与赋值运算符左边的变量的 类型不一致,则赋值时会首先将赋值运算符右边的表达式按赋值运算符左边的变量的类型 进行转换,然后将转换后的表达式的值赋给赋值运算左边的变量。整个赋值表达式的值及其 类型也是这个经过转换后的值及其类型。例如 float x X=i=3.1416
第 4 单元 表达式 - 73 - += -= *= /= %= >>= <<= &= ^= |= 自右至左 复合赋值运算 15 , e1,e2 自左至右 顺序求值运算 说明:运算形式一栏中各字母的含义如下:a ⎯ 数组,e ⎯ 表达式,p ⎯ 指针,t ⎯ 类型,x,y ⎯ 变量 由表 4-3 可以看出, 运算优先级的数字越大,优先级别越低。优先级别最高的是括号, 所 以如果我们要改变混合运算中的运算次序, 或者对运算次序把握不准时, 可以使用括号来明 确规定运算的顺序。 运算符的结合方向是对级别相同的运算符而言的, 说明了在几个并列的级别相同的运 算符中运算的次序。大部分运算符的结合方向都是“自左至右”, 例如表达式 x*y/3,运算 次序就是先计算 x*y, 然后将其结果除以 3。也有些运算符的结合顺序与此相反, 是“自右 至左”, 例如赋值运算符。表达式 i = j = 0 的计算顺序是首先将 0 赋给变量 j, 然后再将 表达式 j = 0 的值(仍为 0)赋给变量 i。 4.8 类型不同的数据之间的混合算术运算 大多数运算符对运算对象的类型有严格的要求。例如, %运算符只能用于两个整型数据 的运算, 所有的位运算符也只适用于整型数据。但是算术四则运算符适用于所有的整型 (包 括 char、int 和 long)、浮点型 (float) 和双精度型 (double) 数据, 因此存在一个问题: 不同 类型的数据的运算结果的类型怎样确定? C++规定, 不同类型的数据在参加运算之前会自动转换成相同的类型, 然后再进行运 算。运算结果的类型也就是转换的类型。转换的规则为: (1) 转换的原则是级别低的类型转换为级别高的类型。各类型按级别由低到高的顺序 为 char, int, unsigned, long, unsigned long, float, double。 因此, 即使是两个 char 型的数据运算,也要先转换为 int 型, 运算的结果也是 int 型; 两 个 float 型数据要先转换为 double 型, 然后参加运算,其结果为 double 型。不同类型的数据 之间也是这样, 如一个 char 类型的数据和一个 int 类型的数据运算,结果为 int 型; 一个 int 型的数据和一个 float 型数据的运算结果的类型为 double 型。 另外, C++规定, 有符号类型数据和无符号类型的数据进行混合运算, 结果为无符号类 型。例如, int 型数据和 unsigned 类型数据的运算结果为 unsigned 型。 对于赋值运算来说, 如果赋值运算符右边的表达式的类型与赋值运算符左边的变量的 类型不一致, 则赋值时会首先将赋值运算符右边的表达式按赋值运算符左边的变量的类型 进行转换, 然后将转换后的表达式的值赋给赋值运算左边的变量。整个赋值表达式的值及其 类型也是这个经过转换后的值及其类型。例如: float x; int i; x = i = 3.1416;
单元表达式 则变量i的值为3,并且赋值表达式i=3.1416的类型为int值也是3。因此尽管变量x的类 型为foat,但对其赋值的结果是x的值为30而不是3.1416。上述赋值表达式语句实际上完 全相当于 i=3.1416; 这两个赋值表达式语句的效果 (3)可以使用强制类型转换。在程序中使用强制类型转换操作符可以明确地控制类型转 换。强制类型转换操作符由一个放在括号中的类型名组成,置于表达式之前,其结果是表达 式的类型被转换为由强制类型转换操作符所标明的类型。例如,如果i的类型为int,表达式 (foat)i将i强制转换为foat类型 算术表达式的强制类型转换的最主要的用途是防止丢失整数除法结果中的小数部分。例 int il=100,i2=40; float f1 这段程序的结果是 float类型的变量f的内容被赋值为200,虽然10040求值应为25。 为什么?原因是表达式i/2包含了两个nt类型的变量,该表达式的类型当然也应该是int 类型。因此,它只能表示整数部分,结果中的小数部分就丢失了。虽然将i2的结果赋值 给了一个浮点类型的变量,但这已经太晚了:结果中的小数部分已经被丢掉了 为了防止这种误差,其中一个int类型的变量必须强制转换为foat类型 f1 =(float)il/i 在这种情况下,另一个变量被自动地被转换为foat类型,并且整个表达式的类型也是 float 结果的小数部分就会被保留 调试技术 49运行错误的判断与调试 通常所说的运行错误有两种,一种是逻辑错误,即程序的实际运行结果和我们对程序结 果的期望不符;另一种仍是程序设计上的错误,但是躲过了编译程序和连接程序的检査,通 常表现为突然死机、自行热启动或者输出信息混乱 相对于编译和连接错误来说,运行错误的查找和判断更为困难。编译和连接错误分别由 编译程序和连接程序检査,尽管有时它们报告的出错信息和错误的实际原因之间有一些差 距,但总还可以作为查错时的一种参考。而运行错误就不同了,很少或根本没有提示信息 只能靠程序员的经验来判断错误的性质和位置。下面我们简单地介绍一些常见运行错误的调
第 4 单元 表达式 - 74 - 则变量 i 的值为 3, 并且赋值表达式 i = 3.1416 的类型为 int, 值也是 3。因此尽管变量 x 的类 型为 float, 但对其赋值的结果是 x 的值为 3.0 而不是 3.1416。上述赋值表达式语句实际上完 全相当于 i = 3.1416; x = i; 这两个赋值表达式语句的效果。 (3) 可以使用强制类型转换。在程序中使用强制类型转换操作符可以明确地控制类型转 换。强制类型转换操作符由一个放在括号中的类型名组成, 置于表达式之前, 其结果是表达 式的类型被转换为由强制类型转换操作符所标明的类型。例如, 如果 i 的类型为 int, 表达式 (float)i 将 i 强制转换为 float 类型。 算术表达式的强制类型转换的最主要的用途是防止丢失整数除法结果中的小数部分。例 如 int i1 = 100, i2 = 40; float f1; f1 = i1/i2; 这段程序的结果是 float 类型的变量 f 的内容被赋值为 2.00, 虽然 100/40 求值应为 2.5。 为什么? 原因是表达式 i1/i2 包含了两个 int 类型的变量, 该表达式的类型当然也应该是 int 类型。因此, 它只能表示整数部分, 结果中的小数部分就丢失了。虽然将 i1/i2 的结果赋值 给了一个浮点类型的变量, 但这已经太晚了:结果中的小数部分已经被丢掉了。 为了防止这种误差, 其中一个 int 类型的变量必须强制转换为 float 类型: f1 = (float)i1/i2; 在这种情况下, 另一个变量被自动地被转换为 float 类型, 并且整个表达式的类型也是 float。 结果的小数部分就会被保留。 调试技术 4.9 运行错误的判断与调试 通常所说的运行错误有两种, 一种是逻辑错误, 即程序的实际运行结果和我们对程序结 果的期望不符; 另一种仍是程序设计上的错误, 但是躲过了编译程序和连接程序的检查, 通 常表现为突然死机、自行热启动或者输出信息混乱。 相对于编译和连接错误来说, 运行错误的查找和判断更为困难。编译和连接错误分别由 编译程序和连接程序检查, 尽管有时它们报告的出错信息和错误的实际原因之间有一些差 距, 但总还可以作为查错时的一种参考。而运行错误就不同了, 很少或根本没有提示信息, 只能靠程序员的经验来判断错误的性质和位置。下面我们简单地介绍一些常见运行错误的调
第4单元表达式 试方法 逻辑错误:一种逻辑错误是由于在设计程序的算法时考虑欠周引起的,例如对边界和极 端条件未作处理等。例如以下循环 while(count) count count-1 程序员的构思是进行 count次循环。但是,如果 count中原来的值为负数时,此循环就成了 个“死循环”而导致无法停机,显然是错误的。但是编译程序无法查出这类错误,只有到了 程序运行之后才有可能发现。再如,在利用海伦公式计算三角型面积时,首先应该确认给出 的三条边长确实可以构成一个三角形,否则计算结果是没有意义的;而在编写求解一般实系 数一元二次方程的程序时,必须在程序中设计处理复根情况的程序段,以免对负数求平方 根。通常在手算时不用事先考虑这些问题,可以在确实发生了问题以后再提出解决的办法。 但是程序是为计算机设计的,而计算机并没有自行应变的能力,程序员必须事先将一切可能 遇到的情况统统考虑周全,尤其是对于那些受用户委托设计或者作为商品出售的软件更是 如此。 另一种常见的逻辑错误是由于程序输入时的打字错误造成的,例如将判断条件中的 >=”误输入为“>”,将相等判断“—”误输入为赋值号“=”等。含有这类错误的程序 在运行时出现的现象多种多样,而且通常很难与错误的原因联系起来。 410基本调试手段 程序的基本调试手段有以下几种:标准数据检验、程序跟踪、边界检查和简化循环次数 等。下面我们分别介绍之 标准数据检验:在程序编译、连接通过以后,就进入了运行调试阶段。运行调试的第 步就是用若干组已知结果的标准数据对程序进行检验。标准数据的选择非常重要,一是要有 代表性,接近实际数据,二是要比较简洁,容易对其结果的正确性进行分析。另外,对重要的 临界数据也必须进行检验。 程序跟踪:对于比较复杂的大型程序来说,上述标准数据检验一次就完全通过的可能性 很小。通常程序中总是存在许多各种各样的错误(就好象出错是程序的基本特性,一个错误 也没有的程序反倒是罕见的意外)还需要对程序进行细致的调试工作 程序跟踪则是最重要的调试手段。程序跟踪的基本原理是让程序一句一句地执行,通过 观察和分析程序执行的过程中数据和程序执行流程的变化来查找错误。就 sual C++而言, 程序跟踪可以采用两种方法,一种是直接利用 Developer Studio中的分步执行、断点设置 变量内容显示等功能对程序进行跟踪,这种方法我们留在第6单元的编程与调试部分介绍, 另一种是传统的方法,通过在程序中直接设置断点、打印重要变量内容等来掌握程序的运行
第 4 单元 表达式 - 75 - 试方法。 逻辑错误: 一种逻辑错误是由于在设计程序的算法时考虑欠周引起的, 例如对边界和极 端条件未作处理等。例如以下循环: while(count) { … … count = count−1; } 程序员的构思是进行 count 次循环。但是, 如果 count 中原来的值为负数时, 此循环就成了一 个“死循环”而导致无法停机, 显然是错误的。但是编译程序无法查出这类错误, 只有到了 程序运行之后才有可能发现。再如, 在利用海伦公式计算三角型面积时, 首先应该确认给出 的三条边长确实可以构成一个三角形, 否则计算结果是没有意义的; 而在编写求解一般实系 数一元二次方程的程序时, 必须在程序中设计处理复根情况的程序段, 以免对负数求平方 根。通常在手算时不用事先考虑这些问题, 可以在确实发生了问题以后再提出解决的办法。 但是程序是为计算机设计的, 而计算机并没有自行应变的能力, 程序员必须事先将一切可能 遇到的情况统统考虑周全, 尤其是对于那些受用户委托设计或者作为商品出售的软件更是 如此。 另一种常见的逻辑错误是由于程序输入时的打字错误造成的, 例如将判断条件中的 “>=”误输入为“>”, 将相等判断“==”误输入为赋值号“=”等。含有这类错误的程序 在运行时出现的现象多种多样, 而且通常很难与错误的原因联系起来。 4.10 基本调试手段 程序的基本调试手段有以下几种: 标准数据检验、程序跟踪、边界检查和简化循环次数 等。下面我们分别介绍之。 标准数据检验: 在程序编译、连接通过以后, 就进入了运行调试阶段。运行调试的第一 步就是用若干组已知结果的标准数据对程序进行检验。标准数据的选择非常重要, 一是要有 代表性, 接近实际数据; 二是要比较简洁, 容易对其结果的正确性进行分析。另外, 对重要的 临界数据也必须进行检验。 程序跟踪: 对于比较复杂的大型程序来说, 上述标准数据检验一次就完全通过的可能性 很小。通常程序中总是存在许多各种各样的错误 (就好象出错是程序的基本特性, 一个错误 也没有的程序反倒是罕见的意外),还需要对程序进行细致的调试工作。 程序跟踪则是最重要的调试手段。程序跟踪的基本原理是让程序一句一句地执行, 通过 观察和分析程序执行的过程中数据和程序执行流程的变化来查找错误。就 Visual C++而言, 程序跟踪可以采用两种方法, 一种是直接利用 Developer Studio 中的分步执行、断点设置、 变量内容显示等功能对程序进行跟踪, 这种方法我们留在第 6 单元的编程与调试部分介绍; 另一种是传统的方法, 通过在程序中直接设置断点、打印重要变量内容等来掌握程序的运行