C Programming: A Modern Approach

第4章 表达式

2020-02-23  本文已影响0人  橡树人

英文原版:P53

一个表达式就是一个用于展示如何计算一个值的公式。
C语言与众不同的特征之一就是强调表达式,而不是语句。
最简单的表达式就是变量和常量。
一个变量表示一个可随着程序运行变化的值。
一个常量表示一个随着程序运行不会变化的值。

更复杂的表达式可应用运算符到操作数上,注意操作数本身也是表达式。比如a + (b*c),就是将运算符加号+应用到操作数a(b*c)上,其中a(b*c)自己本身就是一个表达式。

构建表达式的基本工具就是运算符,C运算符的种类非常丰富。开始阶段,C语言提供了在大部分编程语言中都可找到的基本运算符:

C语言不会止步于此,C语言还提供了许多其他的运算符。有如此多的运算符,以致于要在本书的前20章里逐步介绍。掌握如此多的运算符虽然很繁琐,但是对于精通C语言却是必需的

4.1节介绍算术运算符,解释运算符的优先级和关联性质,这些对包含多个运算符的表达式很重要。
4.2节介绍赋值运算符。
4.3节介绍自增和自减运算符。
4.4节描述如何求表达式的值。
4.5节介绍表达式语句,这是一个不寻常的特征,允许任何表达式充当一个语句。

章节介绍逻辑:

4.1 算术运算符

算术运算符是许多编程语言(包括C语言)的基石。
算术运算符包括加减乘除四种运算。

加法和乘法运算符是双目运算符,因为它们需要两个操作数。

取余运算符%

单目运算符需要1个操作数;
双目运算符需要2个操作数;

C语言的通用规则

有哪些算术运算符?

取余运算符%

规则:

除法运算符/

规则:

剩余算术运算符:加减乘

规则:

运算符优先级和关联性

当一个表达式包含有多于1个的运算符时,比如i+j * k,是表示”先执行i+j,然后把结果乘以k“,还是表示”先j乘以k,后将结果和i相加?

方法一:添加括号,比如(i+j)*k或者i+(j*k)

方法二:不使用括号,使用运算符的优先级规则来消除潜在的歧义;

运算符的优先级

算术运算符的优先级法则

最高:单目(+、-)、双目(*、/)

最低:双目(+、-)

当一个表达式里包含至少两个同优先级的运算符时,该如何解释表达式?

利用运算符的关联性;

如果一个运算符从左向右进行分组,则该运算符是左关联。

双目算术操作符(*、/、%、+、-)都是左关联的。

i-j-k等价于(i-j)-k
i*j/k等价于(i*j)/k

如果一个运算符从右向左进行分组,则该运算符是右关联。

单目算术运算符都是右关联的

-+i等价于-(+i)

运算符和关联性规则在许多语言中都很重要,在C语言中尤其重要。

但是C语言有如此多的运算符(将近50个),以致于几乎没有程序员去记忆这些优先级和关联性法则。反而,程序员通常都是当有疑问时查运算符表,或者使用大量的括号。

程序示例:计算UPC的校验数

多年以来,在美国和加拿大的许多零售店里售卖的商品的制造商会在每个产品上放入一个条形码。

众所周知,条形码就是UPC,可确认产品和制造商的身份。

每个条形码都是一个有12个数字的数,常被打印在条形码的下面。

认识条形码

第1个数字表示物品的类型

0或7表示大部分物品;

2表示必须称重的物品;

3表示药品和保健品;

5表示购物券;

第一个5连数用于确认制造商的身份。

第2个5连数用于确认商品的身份。

最后一个数字是一个校验位,用于帮助确认前11位是不是有错误。

这里使用如下方法来计算校验数字:

源文件upc.c

/* Computes a Univeral Product Code check digit */

#include <stdio.h>

int main(void)
{
    int d, i1, i2, i3, i4, i5, j1, j2, j3, j4, j5,
    first_sum, second_sum, total;

    printf("Enter the first (single) digit: ");
    scanf("%1d", &d);
    printf("Enter first group of five digits: ");
    scanf("%1d%1d%1d%1d%1d", &i1,&i2,&i3,&i4,&i5);
    printf("Enter second group of five digits: ");
    scanf("%1d%1d%1d%1d%1d", &j1,&j2,&j3,&j4,&j5);

    first_sum = d + i2 + i4 + j1 + j3 + j5;
    second_sum = i1 + i3 + i5 + j2 + j4;
    total = 3 * first_sum + second_sum;

    printf("Check digit: %d\n", (9 - (total - 1)%10));
}

4.2 赋值运算符=

在C语言中,赋值运算符=的作用就是保存表达式的值到某个变量,留待后续使用。

为了更新已经保存在变量中的值,C语言提供了各种复合赋值运算符。

简单的赋值v=e

例1 左右操作数类型不同

int i;
float f;

i = 72.99f;
f = 136;

这里i值是72,f的值是136.0。

int i;
float j;

f = i = 33.3f;

这里的i值是33,f的值是33.0。

例2 赋值运算符是右关联的
语句i = j = k = 0等价于i = (j = (k = 0))

不建议使用如下的形式的赋值,因为这会使代码很难读。

int i, j ,k;

i = 1;
k = 1 + (j = i);
printf(“%d %d %d”, i , j, k);

4.3 加1运算符++和减1运算符--

对单个变量最常见的两个操作就是增加1和减小1,比如:

i = i + 1;
j = j - 1;

复合赋值运算符允许将上述语句进行压缩:

i += 1;
j -= 1;

C语言允许使用++表示给变量加1,--表示给变量减1

++运算符和--运算符的效果

例1 前缀表达式++i求值

i = 1;
printf("i is %d\n", ++i);// i is 2
printf("i is %d\n", i);// i is 2

例2 后缀表达式i++求值

i = 1;
printf("i is %d\n", i++);// i is 1
printf("i is %d\n", i);// i is 2

例3 前缀表达式--i求值

i = 1;
printf("i is %d\n", --i);// i is 0
printf("i is %d\n", i);// i is 0

例4 后缀表达式i--求值

i = 1;
printf("i is %d\n", i--);// i is 1
printf("i is %d\n", i);// i is 0

4.4 表达式求值

例1 给表达式添加上合适的括号

a = b += c++ - d + -- e / -f

答:

(a = (b += (((c++) - d) + ((--e) / (-f)))))

子表达式的顺序

例1 赋值运算符导致的结果不确定

a = 5;
c = (b = a + 2) - (a = 1);
// 如果b = a + 2先执行,则c=6。
// 如果a=1先执行,则c=2;

例2 加1运算符导致的结果不确定

i = 2;
j = i * (i++);
//j的结果可能是4,也可能是6。

4.5 表达式语句

规则:任何一个表达式都可用作一条语句

推论:任何一个表达式,无论类型和计算结果,都可通过在结尾添加分号;转换成一条语句

例1 表达式转语句

++i//表达式
++i;//语句

Q&A

问题1 注意到C语言中没有指数运算符。如何求一个数的幂次?

问题2 如何求浮点数的余数?
答:尝试使用fmod函数

问题3 为什么当操作数有负数时,除法运算符/和取余运算符%非常复杂?

在C89和C99中,规则:保证(a/b)*b+a\%b=a恒成立

问题4 如果C语言有lvalues,则C语言有rvalues吗?

问题5 如果v有副作用,则v += e就不等价于v = v + e。这该怎么理解?

//v += e:v只求了一次值;
//v = v + e:v被求了两次值;
a[i++] += 2;
a[i++] = a[i++] + 2;

问题6 如何理解表达式的值被丢掉了?

i = 5;
i+1;//整个表达式的结果为6,但是由于没有保存该值,所以被丢掉了。

i = 1;//整个表达式的结果为1,被丢掉了。

练习

  1. 答:
    (a) 1 2
    (b) 0
    (c) 1
    (d) 0

  2. 一样

(a) 1
(b) -1
(c) -1
(d) 1

答:
(a) 3
(b) -3
(c) 3
(d) -3

  1. 这种方法的问题出在10-total%10会产生10,占用了两位数字,而校验位只有1位数字

  2. 起作用

上一篇下一篇

猜你喜欢

热点阅读