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语言再学习 -- 运算符与表达式相关推荐
- C语言再学习 -- 详解C++/C 面试题 2
(经典)C语言测试:想成为嵌入式程序员应知道的0x10个基本问题. 参看:嵌入式程序员面试问题集锦 1.用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define ...
- C语言再学习 -- 再论数组和指针
之前有总结指针数组,但是现在看来总结的太简单了.好多重要的知识点都是一带而过的.本想在后面添加后来想想算了,还是再写一篇文章来详细介绍数组和指针这对冤家吧. 之前总结的,参看:C语言再学习 -- 数组 ...
- C语言再学习 -- C 预处理器
gcc/cc xxx.c 可以编译链接C源程序生成一个可执行文件 a.out 整个过程中可以划分为以下的4步流程: (1)预处理/预编译: 主要用于包含头文件的扩展,以及执行宏替换等 //加上 -E ...
- C语言再学习 -- 关键字sizeof与strlen
sizeof 一.简单介绍 sizeof 是 C 语言的一种单目操作符,如 C 语言的其他操作符++.--等.它并不是函数.C 规定 sizeof 返回 sieze_t 类型的值.这是一个无符号整数 ...
- C语言再学习--关键字
如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/53021879 C语言一共有32个关键字,如下表所示: 关键字 说明 auto ...
- C语言再学习 -- 详解C++/C 面试题 1
参看:<高质量C++ C编程指南>.林锐 对这篇文章记忆犹新,因为之前找工作面试的时候,遇到过一家公司就是用的这套面试题.现在就结合考查的知识点和我总结完 C 语言再学习后的深入理解,来详 ...
- C语言再学习 -- 关键字const
const 关键字其实我们并不陌生,之前有讲过const修饰数组和指针.现在来详细介绍这个关键字. 参看:[C/C++和指针]著名的<const的思考> 一.const 介绍 1.cons ...
- C语言再学习 -- 存储类型关键字
定义: 是对声明的实现或者实例化.连接器(linker)需要它(定义)来引用内存实体.与上面的声明相应的定义如下:参看:C语言再学习 -- 存储类.链接 C语言中有 5 个作为存储类说明符的关键字,分 ...
- C语言再学习 -- 关键字typedef
参看:C语言再学习 -- 结构和其他数据形式 参看:C语言再学习 -- 关键字struct(转) 参看:常见typedef 用法 参看:关于typedef的用法总结 一.typedef 介绍 type ...
最新文章
- Installation error: INSTALL_FAILED_VERSION_DOWNGRADE
- PHP语言 -- 数组
- 并发控制:(三)MVCC 多版本并发控制
- 详解C++移动语义std::move()
- Linux下Weblogic 11g R1安装和配置
- 关于wpf,datagrid,双向数据绑定用法解决方案
- nlp 财务提取_RPA,智慧财务时代的“珍妮纺织机”来了?
- web developer tips (45):如何改变动态数据文件夹的位置
- HDU1225 字符串
- php分析图片中水印的位置,图像处理技术之图片添加水印
- 精通python工资高吗-软件测试,如何工资过万?
- mysql binlog 恢复
- 阿里Java开发手册(2021最新终极版) 编程必备
- Mac 修改hosts
- POJ 1201 Intervals(差分约束)
- Docker的卸载与安装(阿里云)
- 苹果手机屏幕如何投射到win10?
- 常用的社会信息公开查询
- MTK平台apnspn的配置
- vi编辑器上下左右出现ABCD无法正常使用
热门文章
- vaniglia 源码学习 (六)
- 读书笔记《单核工作法》1
- Tensorflow 错误总结:NameError: name 'core' is not defined
- Ubuntu 开机出现 grub rescue 终端模式修复方法
- 关于Linux的缓存内存 Cache Memory详解
- 小结两种在Python中导入C语言扩展库的方法
- c语言stdio中null的值,C/C++编程笔记:C语言NULL值和数字 0 值区别及NULL详解
- 【Word】如何把代码优美的插入word
- [云炬创业学笔记]第一章创业是什么测试12
- ustc小道消息20211227