【Cprimerplus_01】左值、右值和运算符的一些注意事项

本文介绍了C语言中的左值、右值和运算符的相关概念,包括数据对象、左值(可修改的左值)、右值的定义,并详细讲解了赋值运算符、符号运算符、除法运算符、求模运算符的用法及注意事项。此外,还探讨了递增运算符的两种模式和优先级与求值顺序的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、几个术语:数据对象、左值、右值和运算符

​ 在C语言中,=并不意味着”相等“,而是一个赋值运算符。比如bmw = 2002;把值2002赋给变量bmw。也就是说,=号左侧是一个变量名,右侧是赋给该变量的值。在C语言中,类似2002 = bmw;这样的句子没有意义(实际上是无效的)。因为这种情况下,2002被称作 右值(rvalue),只能是字面常量,不能给常量赋值,常量本身就是它的值。

​ 因此,在编写代码时要记住,=号的左侧必须是一个变量名。实际上,赋值运算符的左侧必须引用一个存储位置。

​ 概括地说,C使用**可修改的左值(modifiable lvalue)**标记那些可赋值实体。”可修改的左值“这个概念可能不太好懂,我们再来看一些定义。

1.数据对象

​ 赋值表达式的目的是把值存储到内存位置上。 用于存储值的数据存储区域统称数据对象(data object)。C标准只有提到这个概念时才会用到对象这个术语。

​ 使用变量名是标识对象的一种方法。

2.左值

**左值(lvalue)**是C语言的术语,用以标识特定数据对象的名称或表达式。因此,对象指的是实际的数据存储,而左值是用于标识特定或定位存储位置的的标签

​ 对于早期的C语言,提到左值意味着:

  1. 它可以指定一个对象,可以引用内存地址中的值;
  2. 它可以用在赋值运算符的的左侧,左值中的l来源于left。

​ 但是后来,标准中新增了const限定符,用const创建的变量不可修改。因此,const标识符满足上面的第1项,但是不满足第2项。一方面,C继续把标识对象的表达式定义为左值,一方面某些左值却不能放在赋值运算符的左侧。

​ 为此,C新增了一个术语可修改的左值(modifiable lvalue),用于标识可修改的对象。所以,赋值运算符的左侧应该是可修改的左值。当前标准建议,使用术语**对象定位值(object locator value)**更好。

3.右值

​ **右值(rvalue)**指的是能赋值给可修改左值的量,且本身不是左值。

​ 右值可以是常量或者其他可求值的表达式(比如,函数调用)。

​ 实际上,当前在描述这一概念的时候使用的是表达式的值(value of an expression),而不是右值。

const int TWO = 2;中TWO是不可改变的左值,它只能用于赋值运算符的右侧。在该例中,TWO被初始化为2,这里的=表示初始化而不是赋值,因此并未违反规则。

ex = a*(why + zee);表达式(why+zee)是右值,该表达式不能表示特定的内存位置,而且也不能给它赋值。它只是程序计算的一个临时值,在计算完毕后就会被丢弃。

二、一些运算符的注意事项:
1 赋值运算符

​ C的赋值运算符有些与众不同,许多其他语言都会回避三重赋值,但是C中完全没问题,赋值的顺序是从右往左。

2 符号运算符:+ 和 -

​ 减号可用于标明或改变一个值的代数符号。

​ C90标准新增了一元+运算符,它不会改变运算对象的值或符号,只能这样使用:dozen = +12;,编译器不会报错,但是在以前,这样做是不允许的。

3 除法运算符 /

​ C使用符号/来表示除除法。/左侧的值是被除数,右侧的值是除数。

在C语言中,整数除法的结果的小数部分被丢弃,这一过程被称为截断(truncation)

​ C99标准之前,C语言给语言的实现者留有一些空间,让它们来决定如何进行负数的整数除法。一种方法是,舍入过程采用小于或者等于浮点数的最大整数。当然,对于3.8而言,处理后的3符合这一描述。可以是对于-3.8会怎样?该方法建议四舍五入为-4,因为-4小于-3.8。

​ 但是,另一种舍入方式是直接丢弃小数部分。这种方法被称为 ”趋零截断“,即把-3.8换成-3。在C99之前,不同的实现采用不同的方法但是C99规定使用趋零截断。所以,应把-3.8换成-3。

4 求模运算符 %

求模运算符(modulus operator)给出其1左侧整数除以右侧整数的余数(reminder)

求模运算只能用于整数,不能用于浮点数

​ 负数求模如何进行?C99规定“趋零截断“之前,该问题的处理方法很多。但自从有了这条规则之后,如果第1个运算对象是负数,那么求模的结果为负数;如果第1个运算对象是整数,那么求模的结果也是正数

​ 如果当前系统不支持C99标准,会显示不同的结果。实际上,标准规定:无论何种情况,只要a和b都是整数值,便可通过 a − ( a / b ) ∗ b a-(a/b)*b a(a/b)b​来计算a%b。

5 递增运算符 ++

​ **递增运算符(increment operator)**执行简单的任务,将其运算对象增加1。

​ 该运算符以两种方式出现,两种模式的区别在于递增行为发生的时间不同:

​ 第1种方式,++出现在其作用的变量前面,这是前缀模式,前缀是在使用值之前递增;

​ 第2种方式,++出现在其作用的变量后面,这是后缀模式,后缀是在使用了值之后,再进行递增。

​ 我们可以把变量的递增过程放进循环语句的循环条件之中。这样会使得程序更加简洁,更重要的是,它把控制循环的两个过程集中在一个地方。该循环的主要过程是判断是否继续循环,次要过程是改变待测试的元素。把循环测试和更新循环放在一处,就不会忘记更新循环。

​ 但是,把两个操作合并在一个表达式中,降低了代码的可读性,让代码难以理解。而且,还容易产生计数错误。

5 优先级和求值顺序
1.共享运算对象的情况

​ 运算符优先级为表达式中的求值顺序提供重要的依据,但是并没有规定所有的顺序。C给语言的实现者留出选择的余地。

​ 考虑这样的语句:

y = 6 * 12 + 5 * 12;

当运算符共享一个运算对象时,优先级决定了求值顺序。例如上面的例子中,先进行两个乘法运算,在进行加法运算。但是,优先级并未规定到底先进行哪一个乘法。C把主动权留给语言的实现者,根据不同的硬件来决定先计算哪一个。可能在一种硬件上采用某种方案效率更高,而在另一种硬件上采用另一种方案效率更高。无论采用哪种方案,表达式都会化简成 72+100,所以并不影响最终效果。

​ 但是,读者可能会根据乘法从左往右的结合律,认为应该先执行+运算符左边的乘法。结合律只适用于共享同一运算对象的运算符。例如,在表达式 12 / 3 * 2 中,/和*的运算符优先级相同,共享运算对象3。因此,从左往右的结合律在这种情况起作用。

2.关于递增和递减运算符

​ 递增和递减运算符都有很高的结合优先级,只有圆括号的优先级比它们高。 因此,x*y++表示的是 x * y++, 而不是 (x * y)++。不过后者无效,因为递增和递减运算符只能影响一个变量(或者,更普遍地说,只能影响一个可修改的左值),而组合 x * y本身不是可修改的左值。

​ 如果n++是表达式的一部分,可将其视为“先使用n,再递增”;而++n则表示“先递增n,再使用”。

​ **不要自作聪明,一次使用太多递增/递减运算符。**遵循以下规则:

  • 如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符;
  • 如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值