第4章 表达式

英文原版:P53

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

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

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

  • 算术运算符,包括加法、减法、乘法、除法等;
  • 用于执行比较的关系运算符,比如"i is greater than 0";
  • 用于构建条件的逻辑运算符,比如"i is greater than 0 and i is less than 10";

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

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

章节介绍逻辑:

  • 有哪些运算符?
  • 运算符都有哪些规则?
  • 运算符的优先级和关联性

4.1 算术运算符

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

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

取余运算符%

  • 双目运算符;
  • 两个操作数必须是整数;

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

C语言的通用规则

  • C语言允许使用括号来分组表达式;

有哪些算术运算符?

  • 单目运算符:+-

  • 双目运算符:+-*/

取余运算符%

规则:

  • 要求两个操作数必须是整数,如果有一个不是整数,则编译不通过;
  • 右操作是0时的结果是不确定的;

除法运算符/

规则:

  • 右操作是0时的结果是不确定的;
  • 当除法运算符/的两个操作数都是整数时,除法运算符/会以丢掉小数部分的形式截断结果,比如1/2的结果是0,而不是0.5;

剩余算术运算符:加减乘

规则:

  • 两个操作数可以是整数,也可以是浮点数,或者两者混合;
  • intfloat混合时,结果是float,比如9+2.5f的结果是11.5,6.7f/2的结果是3.35;

运算符优先级和关联性

当一个表达式包含有多于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位是不是有错误。

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

  • 将第1个、第3个、第5个、第9个、第11个数加起来;
  • 将第2个、第4个、第6个、第8个、第10个数加起来;
  • 将第一个和乘以3加到第2个和上;
  • 将总数减去1;
  • 计算将调整后的和除以10的余数;
  • 从9中减去余数;

源文件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

  • 形式:v=e
  • 效果:计算出表达式e的值,将该值拷贝到v中。
    其中右操作数e可以是一个常数、变量或者更复杂的表达式;
    左操作数v必须是lvalue,不能是常数;
    当右操作数v和左操作数e的类型不同时,需要将e的值需要转换成v的类型。
  • 赋值运算符=是右关联的;
  • C语言中的赋值运算符有副作用,会修改左操作数的值;

例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,--就是将操作数减1
  • 使用++运算符和--运算符要小心,因为使用++运算符和--运算符有两种形式:
    • 前缀运算符,比如++i--i;
    • 后缀运算符,比如i++i--
  • ++运算符和--运算符是有副作用的,即他们会修改操作数的值
  • ++运算符和--运算符的优先级比单目正号运算符+和负号-高。
  • ++运算符和--运算符都是左关联的。

例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)))))

子表达式的顺序

  • 运算符优先级和关联性允许我们将任何一个C表达式划分成多个子表达式,但是并不能决定表达式的值,因为表达式的值可能取决于子表达式的求值顺序。

  • C语言没有定义子表达式的求值顺序,除了包含逻辑运算符and、逻辑运算符or、条件运算符、逗号运算符等子表达式外。

  • 无论子表达式的求值顺序是怎样的,大部分表达式的结果都是相同的。

  • 当有子表达式会修改它的某个操作数时,表达式的结果就跟子表达式的求值顺序相关了。

  • 可通过使用赋值操作来代替子表达式中的赋值运算等;

例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语言中没有指数运算符。如何求一个数的幂次?

  • 如果是求一个整数的小正整数次幂,最好使用重复乘法,比如i*i*i
  • 如果是求一个非整数次数,最好调用pow函数;

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

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

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

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

  • 有;
  • lvalues是一个表达式,该表达式出现在赋值运算符的左边;
  • rvalues是一个表达式,该表达式出现在赋值运算符的右边;
  • 一个rvalues可以是变量、常数、或者更复杂的表达式;
  • 在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. 起作用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容

  • #1.基础1.1 基本概念1.2 优先级和结合律1.3 求值顺序 #2.算术运算符 #3.逻辑和关系运算符 #4....
    MrDecoder阅读 325评论 0 0
  • 粗略的把左值和右值理解成:左值是对象本身,右值是对象的值。操作左值就是用了对象本身,修改左值就是修改了对象本身的内...
    修司敦阅读 191评论 0 0
  • 4.1 基础 4.1.1 基本概念 重载运算符:用户自定义的用于类类型的运算对象的运算符。这个自定义过程为已存在的...
    咸鱼翻身ing阅读 136评论 0 0
  • 原始表达式:包括常量或直接量,关键字和变量
    没人能救你呀吼阅读 125评论 0 0
  • 教育观察NO.48‖ 教育,在于促使学生以自己的灵魂为导师,行驶在他自己的航路上。是不去泯灭他的内在热情,不去压抑...
    边根元阅读 205评论 0 0