第四章 表达式《C语言程序设计现代方法(第2版)》读书笔记

简介: 第四章 表达式《C语言程序设计现代方法(第2版)》读书笔记

第四章 表达式🚀


  1.  算术运算符,包括加、减、乘和除。
  2.  关系运算符进行诸如“i0大”这样的比较运算。
  3.  逻辑运算符实现诸如“i0大并且i10小”这样的关系运算。


4.1 算术运算符🚀


image.png


一元运算符 + 什么都不做;实际上,经典 C中甚至不存在这种运算符。它主要用于强调某数值常量是正的。

除%运算符以外,表4-1中的二元运算符既允许操作数是整数也允许操作数是浮点数,两者混合也是可以的。 当把int 型操作数和 float 型操作数混合在一起时,运算结果是 float型的。因此,9+2.5f 的值为 11.5 ,而 6.7f/2 的值为 3.35 。




运算符/和运算符%需要特别注意以下几点。


1.运算符 / 可能产生意外的结果。 当两个操作数都是整数时 ,运算符 / 会丢掉分数部分来“截

取”结果。因此, 1 / 2 的结果是 0 而不是 0.5 。

2.运算符%要求操作数是整数。 如果两个操作数中有一个不是整数,程序将无法编译通过。

3. 把零用作/或%的右操作数会导致未定义的行为 ( 4.4 节)。

4. 当运算符/和运算符%用于负操作数时,其结果难以确定。 根据 C89 标准,如果两个

操作数中有一个为负数,那么除法的结果既可以向上取整也可以向下取整。(例如, -9/7

的结果既可以是- 1 也可以是- 2 。)在 C89 中,如果 i 或者 j 是负数, i%j 的符号与具体实现

有关。(例如, -9%7 的值可能是- 2 或者 5 。) 但是在C99中,除法的结果总是向零截取的(因此-9/7的结果是-1),i%j的值的符号与i的相同(因此-9%7的值是-2)。


                                               “由实现定义”的行为  


       术语 由实现定义 ( implementation-defined )出现频率很高,因此值得花些时间讨论一下。 C 标准故意对C语言的部分内容未加指定,并认为其细节可以由“实现”来具体定义。所谓实现是指程序在特定的平台上编译、链接和执行所需要的软件。 因此,根据实现的不同,程序的行为可能会稍有差异。C89中运算符 / 和运算符 % 对负操作数的行为就是一个由实现定义行为的例子。

       留下语言的一部分内容未加指定看起来可能有点奇怪,甚至很危险,但这正反映了C 语言的基本理念。 C语言的目标之一是高效,这常常意味着要与硬件行为相匹配。- 9 除以 7 时,有些CPU产生的结果是- 1 ,有些为- 2 。 C89 标准简单地反映了这一现实。

       最好避免编写依赖于由实现定义的行为的程序。如果不可能做到,起码要仔细查阅手册—— C标准要求在文档中说明由实现定义的行为。


运算符的优先级和结合性


image.png


4.2 赋值运算符🚀


4.2.1 简单赋值🚀

表达式v = e的赋值效果是求出表达式e的值,并把此值复制给v

如果ve的类型不同,那么赋值运算发生时会把e的值转化为v的类型:


image.png


注意:


注意由于存在类型转换,串在一起的赋值运算的最终结果可能不是预期的结果:

int i;

float f;

f = i = 33.3f;

首先把数值 33 赋值给变量 i ,然后把 33.0 (而不是预期的 33.3 )赋值给变量 f 。


4.2.2 左值🚀


image.png


4.3 自增运算符和自减运算符🚀


++i意味着“立即自增i”,而i++则意味着“现在先用i的原始值,稍后 再自增i”。这个“稍后”有多久呢? C语言标准没有给出精确的时间,但是可以放心地假 设i将在下一条语句执行前进行自增。


需要记住的是,后缀 ++ 和后缀 -- 比一元的正号、负号优先级高,而且都是左结合的。前缀

++ 和前缀 -- 与一元的正号、负号优先级相同,而且都是右结合的。


4.4 表达式求值

(最高优先级为1,最低优先级为5),最后一列显示了每种运算符的结合性。


image.png


      根据C 标准,类似 c = (b = a + 2) – (a = 1); 和 j = i * i++; 这样的语句都会导致“未定义的行为”(undefined behavior ),这跟 4.1节中讲的由实现定义的行为是不同的。当程序中出现未定义的行为时,后果是不可预料的。不同的编译器给出的编译结果可能是不同的,但这还不是唯一可能发生的事情:首先程序可能无法通过编译,就算通过了编译也可能无法运行,就

算可以运行也有可能崩溃、不稳定或者产生无意义的结果。换句话说,应该像躲避瘟疫一样避免未定义的行为。


4.5 表达式语句🚀

C语言有一条不同寻常的规则,那就是任何表达式都可以用作语句。换句话说,不论表达

式是什么类型,计算什么结果,我们都可以通过在后面添加分号的方式将其转换成语句。例如,可以把表达式++i转换成语句

++i;

键盘上的误操作很容易造成“什么也不做”的表达式语句。例如,本想输入

i = j;

但是却错误地输入

i + j;

(因为 = 和 + 两个字符通常在键盘的同一个键上,所以这种错误发生的频率可能会超出

想象。)某些编译器 可能会检查出无意义的表达式语句 ,会显示类似“ statement with no

effect ”的警告。


问与答🚀

问:我注意到C语言没有指数运算符。如何求一个数的幂呢?

答:通过重复乘法运算的方法可以进行整数的较低的整数次幂运算( i * i * i  i的立方运算)。如果想计算  整数次幂,可以调用pow函数( 23.3 节)。(abs()绝对值)


问:我想把%运算符用于浮点数,但程序无法通过编译。该怎么办?p.37

答:%运算符要求操作数是整数,可以试试fmod函数( 23.3节)。


C99 出现的时候,大多数 CPU 都对除法的结果向零取整,所以这也被写入这一标准作为唯一允许的结果。


问:如果C语言有左值,那它也有右值吗?(p.41)


答:是的,当然。左值是可以出现在赋值 左侧 的表达式,而右值则是可以出现在赋值 右侧 的表达式。因此,右值可以是变量、常量或更加复杂的表达式。本书和C 标准一样,采用“表达式”这一术语来代替“右值”。(左值->空间 右值->值)


*问:前面提到:如果v有副作用,那么v += e 不等价于 v = v + e。可以解释一下吗?(p.41)


答: 计算v += e只会求一次v的值,而计算v = v + e则会求两次v的值 。在后一种情况下,对 v 求值可能引起的任何副作用也都会出现两次。在下面的例子中,i 只自增一次:

a[i++] += 2;

如果用 = 代替 += ,语句变成:

a[i++] = a[i++] + 2;

*

i 的值在别处被修改和使用了,因此上述语句的结果是未定义的。 i 的值可能会自增两次,但我们无法确定到底会发生什么。



问: C 语言为什么提供 ++ 和 -- 运算符?它们是比其他的自增、自减方法执行得快,还是仅仅更便捷? (p.42 )

对于现代编译器而言,使用 ++ 和 -- 不会使编译后的程序变得更短小或更快,继续普及这些运算符主要是由于它们的简洁和便利。



问:++--是否可以处理float型变量?

答:可以。自增和自减运算也可以用于浮点数,但实际应用中极少采用自增和自减运算符处理float型变量。


*问:在使用后缀形式的++或--时,何时执行自增或自减操作?(p.42)


答:这是一个非常好的问题,也是一个非常难回答的问题。 C语言标准引入了“顺序点”的概念,并且指出“应该在前一个顺序点和下一个顺序点之间对存储的操作数的值进行更新”。在C语言中有多种不同类型的顺序点,表达式语句的末尾是其中一种。在表达式语句的末尾,该语句中的所有自增和自减操作都必须执行完毕,否则不能执行下一条语句。

在后面章节中会遇到的一些运算符(逻辑与 、逻辑 或 、条件和逗号)对顺序点也有影响。函数调用也是如此:在函数调用执行之前,所有的实际参数必须全部计算出来。如果实际参数恰巧是含有++ 或 -- 运算符的表达式,那么必须在调用前进行自增或自减操作。



问:丢掉表达式语句的值意味着什么?(p.45)


答:根据定义,一个表达式表示一个值。例如,如果 i 的值为 5 ,那么计算 i + 1 产生的值为 6 。在末尾添加分号,把i+1 变成语句:

i + 1;

执行这条语句时,我们计算出了i + 1的值;但是我们没有保存此值(也没有以某种方式使用这个值),因此这个值就丢失了。



问:但是类似i = 1;这样的语句会如何呢?我没发现有什么东西被丢掉了。


答:不要忘记在 C 语言中 = 是一种运算符,它可以像其他任何运算符一样产生值。赋值语句

i = 1;

把1赋值给i。整个表达式的值是1,这个值被丢掉了。编写语句的首要目的是改变i的值,因此丢掉表达式的值不算什么大的损失。



最后的最后,创作不易,希望读者三连支持💖

赠人玫瑰,手有余香💖

相关文章
|
1月前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
22 3
|
2月前
|
程序员 C语言
【C语言基础考研向】06运算符与表达式
本文介绍了C语言中的运算符分类、算术运算符及表达式、关系运算符与表达式以及运算符优先级等内容。首先概述了13种运算符类型,接着详细说明了算术运算符的优先级与使用规则,以及关系运算符和表达式的真假值表示,并给出了C语言运算符优先级表。最后附有课后习题帮助巩固理解。
104 10
|
2月前
|
C语言
C语言程序设计核心详解 第四章&&第五章 选择结构程序设计&&循环结构程序设计
本章节介绍了C语言中的选择结构,包括关系表达式、逻辑表达式及其运算符的优先级,并通过示例详细解释了 `if` 语句的不同形式和 `switch` 语句的使用方法。此外,还概述了循环结构,包括 `while`、`do-while` 和 `for` 循环,并解释了 `break` 和 `continue` 控制语句的功能。最后,提供了两道例题以加深理解。
|
2月前
|
存储 C语言
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
|
2月前
|
存储 C语言
C语言程序设计核心详解 第七章 函数和预编译命令
本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第六章 数组_一维数组_二维数组_字符数组详解
本章介绍了C语言中的数组概念及应用。数组是一种存储同一类型数据的线性结构,通过下标访问元素。一维数组定义需指定长度,如`int a[10]`,并遵循命名规则。数组元素初始化可使用 `{}`,多余初值补0,少则随机。二维数组扩展了维度,定义形式为`int a[3][4]`,按行优先顺序存储。字符数组用于存储字符串,初始化时需添加结束符`\0`。此外,介绍了字符串处理函数,如`strcat()`、`strcpy()`、`strcmp()` 和 `strlen()`,用于拼接、复制、比较和计算字符串长度。
|
2月前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
22 6