首页 > 其他分享 >C语言再学习 -- 运算符与表达式

C语言再学习 -- 运算符与表达式

时间:2023-04-03 20:43:33浏览次数:43  
标签:右值 -- 左值 C语言 运算符 表达式 赋值


分三部分来讲

一、左值与右值

参看:左值与右值

首先我们需要理解左值和右值的定义:

左值指的是如果一个表达式可以引用到某一个对象,并且这个对象是一块内存空间且可以被检查和存储,那么这个表达式就可以做为一个左值。      

右值指的是引用了一个存储在某个内存地址里的数据。

从上面的两个定义可以看出,左值其实要引用一个对象,而一个对象在我们的程序中又肯定有一个名字或者可以通过一个名字访问到,所以左值又可以归纳为:左值表示程序中必须有一个特定的名字引用到这个值。而右值引用的是地址里的内容,所以相反右值又可以归纳为:右值表示程序中没有一个特定的名字引用到这个值除了用地址。

好这些都是从定义上理解左值右值,那么我们再用这些定义作为我们的理论基础来总结一下哪些是左值,哪些是右值:

左值:


Expression               

Lvalue                                   

x = 4

 x

*ptr = newvalue

*ptr

++a

++a

b[0] = 100

b[0]

const int m = 10

m

int & f()

The function call to f()

右值:

Expression   

Rvalue

100

100

a * b

The expression of a * b

a++

a++

int f()

The function call to f() that does notreturn reference


以上这些内容都可以用定义来解释为什么这些为左值,而那些为右值。但我要特殊解释一下为什么函数的调用只能作为右值除了这个函数返回的是引用。其实这个也非常好解释,因为如果一个函数返回的值是内建类型,那么这个返回值是没有办法通过一个名字或者表达式引用到的,同理如果一个函数返回的是一个对象,那么这个对象是一个临时的,也不可能用一个名字访问到。所以函数的调用通常只能作为右值,但如果一个函数返回引用,那么它的返回值就有意义了,因为它是另一个名字的别名,有名字了,所以它就变成了左值。

注意:左值能转化为右值,但反之不行。

好了,讲了这么多我觉得已经足够,但还要多讲一点,这点就是哪些操作符必需左值.

Operator                      

Requirement                                          

&

Operand must be an lvalue

++ --

Operand must be an lvalue.This applies 

to both prefix and posfix forms

=   +=   -=   *=   %=   

<<=   >>= &= ^= |=

Left operand munst be an lvalue


附:《C primer plus》中左值、右值的定义

“数据对象”是泛指数据存储区的术语,数据存储区能用于保存值。

“左值”指用于标识一个特定的数据对象的名字或表达式。即对象指实际的数据存储,而左值是用于识别或定位那个存储的标识符(或表达式)。

“右值”指可能赋给可修改左值的量。


二、运算符优先级

参看:C语言运算符优先级列表(超详细)

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--


2

-

负号运算符

-表达式

右到左

单目运算符

~

按位取反运算符

~表达式

++

自增运算符

++变量名/变量名++

--

自减运算符

--变量名/变量名--

*

取值运算符

*指针变量

&

取地址运算符

&变量名

!

逻辑非运算符

!表达式

(类型)

强制类型转换

(数据类型)表达式

--

sizeof

长度运算符

sizeof(表达式)

--


3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

%

余数(取模)

整型表达式%整型表达式

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

5

<<

左移

变量<<表达式

左到右

双目运算符

>>

右移

变量>>表达式


6

>

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

<

小于

表达式<表达式

<=

小于等于

表达式<=表达式

7

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式


8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符


13

?:

条件运算符

表达式1?

表达式2: 表达式3

右到左

三目运算符


14

=

赋值运算符

变量=表达式

右到左

--

/=

除后赋值

变量/=表达式

--

*=

乘后赋值

变量*=表达式

--

%=

取模后赋值

变量%=表达式

--

+=

加后赋值

变量+=表达式

--

-=

减后赋值

变量-=表达式

--

<<=

左移后赋值

变量<<=表达式

--

>>=

右移后赋值

变量>>=表达式

--

&=

按位与后赋值

变量&=表达式

--

^=

按位异或后赋值

变量^=表达式

--

|=

按位或后赋值

变量|=表达式

--


15

逗号运算符

表达式,表达式,…

左到右

--

说明:
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符


一些容易出错的优先级问题

优先级问题

表达式

被误认为的结果

正确的结果

.优先级高于*,->

*p.f

(*p).f

*(p.f)对p取f偏移,作为指针,

然后进行解除操作

==和!=高于位操作

(var&mask !=0)

(var&mask)!=0

Var & (mask !=0)

==和!=高于赋值

C=getchar() !=EOF

(c=getchar())!=EOF

C=(getchaor()!=EOF)

算术运算符高于位移运算法

Mask << 4+3

(Mask <<4)+3

Mask << (4+3)

逗号运算符在所有运算符中

优先级最低

i=1,2

I=(1,2)

(i=1),2 结果为i=2





三、扩展总结

1、赋值运算符:=

赋值运算符的动作是从右到左,且不能将一个值赋给一个常量。赋值,从C的角度来看,主要目的是对表达式来求值。

2、除法运算符:/

7/4 = 1;

7./4 = 1.75;

7./4. = 1.75;

11 / 5 = 2; 11 / -5 = -2; -11 / -5 = 2; -11 / 5 = -2;

在C中,整数除法结果的小数部分都被丢弃,没有把整数除法运算的结果四舍五入到最近的整数。

当对整数与浮点数进行混合运算时,结果是浮点数。

3、取余运算符:%

取余运算符用于整数运算,该运算符计算出用它右边的整数去除它左边的整数得到的余数。

注意:不要对浮点数使用该运算符,那将是无效的。

负数取余规则:

如果第一个操作数为负数,那么得到的余数也为负数;如果第一个操作数为正数,那么得到的余数也为正数。

11 % 5 = 1;11 % -5 = 1;-11 % 5 = -1;-11 % -5 = -1;

4、自增和自减:++和--

后缀: a++;  a--;  使用a的值之后改变a;

前缀: ++a;  --a;  使用a的值之前改变a;


++a = 10;

++a 的结果是a值的拷贝,并不是变量本身,你无法向一个值进行赋值。赋值运算的左操作数必须是左值。

后续补充几个例题。。。


四、布尔值

通过包含stdbool.h头文件,可以用bool代替关键字_BOOL表示这种类型,并用标识符true和false代替1和0.


五、逻辑运算符

与(&&)、或(||)、非(!)

或(||)和与(&&)都具有短路特征,如果前一个逻辑表达式的结果可以决定整个表达式结果则计算机会忽略后面的逻辑表达式。

短路特性具体为:   
表达式一 && 表达式二 如果表达式一为假,就不会进行表达式二的计算
表达式一 || 表达式二 如果表达式一为真,就不会进行表达式二的计算 


六、三目表达式

三目操作符可以在两个不同的计算机规则中选择一个
三目操作符的格式如下
布尔值 ? 公式一: 公式二            
//布尔值需要有定义的  ret=(num >=0) ? num : 0-num;
如果布尔值为真则采用公式一计算结果
如果布尔值为假则采用公式二计算结果


七、== 和 =不同

符号 = 作为赋值运算,符号 ==作为比较。注意,用于测试两个表达式是否相等的操作符是==,如果无用了=操作符,虽然它也是合法的表达式,但其结果几乎肯定和你的本意不一样,它将执行赋值操作而不是比较操作。

比如下例,该语句本意似乎是要检查 x 是否等于 y

if (x = y)
    break;


而实际上是将 y 的值赋给了 x,然后检查该值是否为零。

还需注意:任何非零值都是真.比如,while (-1) == while (true)

再看下面的例子:

while (c = ' ' || c == 't' || c =='\n')
    c = getc (f);

本例中循环语句的本意是跳过文件中的空格符、制表符和换行符。由于程序员在比较字符 ' ' 和变量 c 时,误将比较运算符 == 写成了赋值运算符 = 。因为赋值运算符 = 的优先级要低于逻辑运算符 || ,因此实际上是将以下表达式的值赋给了 c

' ' || c == 't' || c == '\n'

因为 ' ' 不等于零 (' ' 的ASCII 码为 32),那么无论变量 c 此前为何值,上述表达式求值的结果是 1,因此循环将一直进行下去直到整个文件结束。

另一方面,如果把赋值运算符写成比较运算符,同样会造成混淆:

if ((filedesc == open(argv[i] , 0)) < 0)
    error ();

在本例中,如果函数open执行成功,将返回 0 或者正数;而如果函数 open 执行失败,将返回 -1.上面这段代码的本意是将函数 open的返回值存储在变量 filedesc 之中,然后通过比较变量 filedesc 是否小于 0 来检查函数 open 是否执行成功。但是,此处的 == 本应是 = 。而按照上面代码中的写法,实际进行的操作是比较函数 open 的返回值与变量 filedesc ,然后检查比较的结果是否小于 0.因此比价运算符 == 的结果只可能是 0 或 1.永远不可能小于 0,所以函数 error ( )将没有机会被调用。如果代码执行,似乎一些正常,除了变量 filedesc 的值不再是函数 open 的返回值。


八、在表达式中使用无符号数

库函数 strlen 的原型如下:

size_t strlen (char const *string);

注意:strlen 返回一个类型为 size_t 的值。这个类型是在头文件 stddef.h 中定义的,它是一个无符号整数类型。在表达式中使用无符号数可能导致不可预料的结果。例如下面的表达式:

#include <stdio.h>
#include <string.h>
int main (void)
{
	char ptr1[] = "beijing";
	char ptr2[] = "hello world";
	if (strlen (ptr1) - strlen (ptr2) >= 0)
	{
		printf ("1111111111\n");
	}
	printf ("2222222222\n");
	return 0;
}

strlen (ptr1) - strlen (ptr2) 为无符号类型,得不到想要的结果,应该为:

#include <stdio.h>
#include <string.h>
int main (void)
{
	char ptr1[] = "beijing";
	char ptr2[] = "hello world";
	if (strlen (ptr1) >= strlen (ptr2))
	{
		printf ("1111111111\n");
	}
	printf ("2222222222\n");
	return 0;
}


标签:右值,--,左值,C语言,运算符,表达式,赋值
From: https://blog.51cto.com/u_15979522/6167345

相关文章

  • Spring注解驱动原理及源码,深入理解Spring注解驱动
    文章目录一、Java注解入门大全二、Spring注解驱动编程发展历程1、注解驱动启蒙时代:SpringFramework1.x@Transactional@ManagedResource2、注解驱动过渡时代:SpringFramework2.x@Repository@Component3、注解驱动黄金时代:SpringFramework3.x4、注解驱动完善时代:SpringFramewo......
  • LG Ultra PC 15U480-KA56K黑苹果efi引导文件
    原文来源于黑果魏叔官网,转载需注明出处。(下载请直接百度黑果魏叔)硬件型号驱动情况主板LGUltraPC15U480-KA56K处理器Intel8thGenKabyLakeRefreshCorei5-8250U3.40GHz已驱动内存DDR4PC4-192002400MHz8GB已驱动硬盘KingstonSA400S37120GSATA32.5SSD已驱动显卡I......
  • Spring 类型转换详解,SpringBean创建时属性类型转换源码详解
    文章目录一、概述1、Spring类型转换的实现2、使用场景3、源码分析二、基于JavaBeans接口的类型转换1、代码实例2、Spring內建PropertyEditor扩展ByteArrayPropertyEditor3、自定义PropertyEditor扩展整合到springframework代码实例SpringPropertyEditor的设计缺陷三、Spr......
  • Mysql主从复制
    工作原理图:主从复制的原理:分为同步复制和异步复制,实际复制架构中大部分为异步复制。复制的基本过程如下:1).Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;2).Master接收到来自Slave的IO进程的请求后,通过负责复制的IO进程根据请......
  • Redis常见问题答疑
    数据类型一个数据类型都对应了很多种底层数据结构。以List为例,什么情况下是双向链表,反之又在什么情况下是压缩列表呢?还是说是并存状态?1、Hash和ZSet是数据量少采用压缩列表存储,数据量变大转为哈希表或跳表存储2、但List不是这样,是并存的状态,List是双向链表+压缩列表key过期......
  • Golang基本语法笔记
    数据类型整型取值范围varnint8n=100fmt.Println(n)//100没有问题//如果赋值为200则不行因为int8取值范围最大是127字符串 v1:='A'v2:="A" //单引号存储的是ASCII编码//A的ASCII值=65//B的ASCII值B=66//a的ASCII值a=97 fmt.Printf("v1的类型是%T,%d,值为%s\n"......
  • mysql数据库优化大全
    数据库优化sql语句优化索引优化加缓存读写分离分区分布式数据库(垂直切分)水平切分MyISAM和InnoDB的区别:1.InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;2......
  • 【第27天】SQL进阶-查询优化- performance_schema系列实战三:锁问题排查(表级锁)(SQL 小虚
    回城传送–》《32天SQL筑基》文章目录零、前言一、什么是表级锁二、什么时候适合加表级锁三、实战演练3.1数据准备(如果已有数据可跳过此操作)3.2开启第一个会话,执行显式加表级锁3.3开启第二个会话,对该表执行update更新3.4开启第三个会话,查询线程信息3.5分析3.6释放第一个会话......
  • Grafana 安装启用和钉钉报警
    Grafana钉钉报警的小卡片点击时无法跳转到Grafana的界面在Grafana的配置文件.ini里root_url='xxxx'配置上地址重启即可一、grafana安装与启用我这里使用的docker方式官方文档:https://grafana.com/docs/grafana/latest/installation/docker/1.数据接入、仪表盘配置展示、各指标......
  • MySQL实战45讲 笔记
    笔记不要小看一条update语句,在生产机上使用不当可能会导致业务停滞,甚至崩溃。当我们要执行update语句的时候,确保where条件中带上了索引列,并且在测试机确认该语句是否走的是索引扫描,防止因为扫描全表,而对表中的所有记录加上锁。我们可以打开MySQL里的sql_safe_updates参数......