目录
三目运算符(条件操作符)的细节
一个很重要的内容,上个星期发现的,三元运算符。
exp1 ? exp2 : exp3
看一下问题是怎么发现的,我是在写Java的时候发现的(现在时间2022/04/24 15:47)。
Java代码
于是我就去看一下C语言是不是这样
C语言也是这样的情况,是让最终结果转换为64位的无符号类型。
于是我就想继续探索下去
看来最终的结果是根据问好后面的两个结果来判断的。不信可以看下面的例子
主要想讲的是Java,Java中三目运算符是想看全部的整体类型,double的优先优先级比int高,所以结果一定是double类型的。
注意:移位操作符和位操作符需要了解原码、补码、反码,如果不了解可以访问我上一篇文章。
一、操作符分类:
1.算数操作符
2.移位操作符
3.位操作符
4.赋值操作符
5.单目操作符
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号表达式
10.下标引用、函数调用和结构成员
二、算数操作符:
1.除了%操作符以外,其他操作符可以用于整型和和实型的运算(整型、实型见上一篇文章开头)。
2.对于/操作符如果两个操作数都为整型,与数学数学计算的除法相同,如果有一个操作数是实型,则结果一定是小数。
3.%操作符的两个操作数必须是整型,返回的是整除后的余数。
三、移位操作符:
1.左移操作符
操作数1 << 操作数2
操作数1:被执行的数
<< :左移操作符
操作数2:要执行多少位
移位规则:左边丢弃,右边补0
例如:
注意:a的不会发生变化,只是让a这个数变化赋给b而自身没有改变,除非a = a << 1,这样是对a进行操作,a自己的值发生了变化。
2.右移操作符
操作数1 << 操作数2
操作数1:被执行的数
<< :左移操作符
操作数2:要执行多少位
右移运算分为两种:逻辑移位、算术移位
逻辑移位:左边用0填充,右边丢弃。
算术移位:左边用原来的符号位填充,右边丢弃。
例如:
由图三、图四可看出在VS2019的环境下是算术移位,可以在评论区说出你们的环境是那种运算方式。
警告⚠:
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
为了满足你们好奇心我就试一试
没错,我也不知道为什么。
再来一个
还是这样,所以说不要尝试了,谁没事知道有错还总是犯错啊。
四、位操作符:
按位与:
两个数的二进制位相同位都是1时为1,否则为0。
按位或:
两个数的二进制位相同为只要有1就为1,否则为0.
按位异或:
两个数的二进制位相同位相同为0,相反为1(这里认为0与1相反)。
五、赋值操作符:
感觉这个没什么好讲的,感觉只有两点:连续赋值和复合赋值。
连续赋值:
例如:
这里我突然想到一个常见的错误:
这个应该有人中过招把,让我来细细拆解。
如何正确表达?
这算是个小插曲吧,突然想到的,不认识&&的就看后面我的讲解,文章会讲到这个操作符。
复合赋值符:
我只讲一个,其他的是一样的使用。
六、单目操作符:
1.单目操作符介绍:
!:让真变为假,让假变为真。
-、+:感觉没什么讲的。
&:这里不是按位与,不要搞混了。
*:这里不是乘法运算的*,不要搞混了。
sizeof:
括号里面是看的类型,不看里面的值,而且括号里面不进行运算(重点)。
而且sizeof的返回类型为unsign int 这个也应该注意,尤其是参与运算时,例如
-1与sizeof(a)比较时要发生整型提升(不了解可以参考我的上一篇文章)。-1会提升为无符号类型
~:连同符号位,1变为0,0变为1。
原码、反码、补码不熟悉一定要看我上一篇文章。其他看不懂的话,只用看原码、反码、补码就行。其他的可能需要让这篇文章看完才行。
2.sizeof和数组:
数组如果一个也不初始化,则全部都是随机数。再讲一个细节:全局变量没有初始化默认为0(假设int类型)
七、关系操作符:
比较简单,感觉只有一点要讲的,一种不细心错误:
如果这样编译器也不报错,怎么解决这个问题呢?
其实不不难。
这样据很好的解决这个问题了,编译器直接报错。但是,为什么报错呢?其实很简单=的左边必须是变量,常量是不行的。这里有人可能有要问了我给它一个常变量行不行?马上满足你们的要求。
八、逻辑操作符:
&&、||一般用于if语句:
这两个操作符的操作数都是两个,&&两个操作数都为真(非0)结果才为真,||两个操作数只要有一个为真(非0)则结果为真。
但是,&&的优先级比||的高。即:先执行&&再执行||。
&&的运算细节是如果左操作数为假,则&&右侧的操作数不执行。
||的运算细节是如果左操作数为真,则||右侧的操作数不执行。
九、条件操作符:
exp1 ? exp2 : exp3
如果表达式1结果为真执行表达式2,否则执行表达式3。
十、逗号表达式:
exp1, exp2, exp2, exp2,...expN
最终的结果是expN的值。
十一、下标引用、函数调用和结构成员:
1.[] 小标引用操作符
操作数有两个:一个数组名、一个索引值(就是[]里面的数)。
2.() 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
上面的()操作符有三个操作数:Max、a、b。
上面()操作符的操作数有一个test
3.访问一个结构体的成员
.实例:
->实例:
十二、表达式求值:
表达式求值的顺序一部分是由操作符的优先级和结合性决定。但是这两个结合并不能解决所有问题。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
255变为char类型是发生截断,因为打印格式是%d所以要进行整型提升,从最左边的符号位进行提升,左边每提升的都是符号位的数字(如果是负数则左边都补1,如果是正数或者无符号数则补0)。
重新回顾sizeof实例中的if(-1 < sizeof(int))。
应该是编译阶段编译器发现了不对。编译器不允许明着让整型提升,真是对应了隐式类型转换这个性质了。
不要害怕报错,我经常报错。所有的高手在自己敲代码时都有报错过,我们只是比他们走的慢了点,艰难了点而已。我们要做到快速找到报错我原因,以防以后不出现这样的错误。打了一波鸡血。
sizeof的返回类型为size_t,我们看一下size_t是什么东西。鼠标放在size_t的上面,左键点击,右键点击转到定义。
没错,就是让unsigned int 这个类型用 size_t表示了。意思就是sizeof()的返回类型是unsigned int,根据向上转型的规则(这个好像忘记讲了)
来一道题:
#include<stdio.h>
//实例1
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
再来道题:
#include<stdio.h>
//实例2
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:
但是算术转换要合理,要不然会有一些潜在的问题。
好吧我没思考就实验了:
至于为什么是这个数就看我的上一篇文章最后的实型再内存存储。那么怎么才能表达我想要表达的意思呢?
这两种方法都可以证明高精度向低精度转换精度会丢失。
3.操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级:
操作符 | 描述 | 结合性 | 是否控制求值顺序 |
() | 聚组 | N/A | 否 |
() | 函数调用 | L/R | 否 |
[] | 下标引用 | L/R | 否 |
. | 访问结构成员 | L/R | 否 |
-> | 访问结构指针成员 | L/R | 否 |
++ | 后缀自增 | L/R | 否 |
-- | 后缀自减 | L/R | 否 |
! | 逻辑反 | R/L | 否 |
~ | 按位取反 | R/L | 否 |
+ | 单目,表示正值 | R/L | 否 |
- | 单目,表示负值 | R/L | 否 |
++ | 前缀自增 | R/L | 否 |
-- | 前缀自减 | R/L | 否 |
* | 间接访问 | R/L | 否 |
& | 取地址 | R/L | 否 |
sizeof | 取其长度,以字节表示 | R/L | 否 |
(类型) | 类型转换 | R/L | 否 |
* | 乘法 | L/R | 否 |
/ | 除法 | L/R | 否 |
% | 整数取余 | L/R | 否 |
+ | 加法 | L/R | 否 |
- | 减法 | L/R | 否 |
<< | 左移位 | L/R | 否 |
>> | 右移位 | L/R | 否 |
> | 大于 | L/R | 否 |
>= | 大于等于 | L/R | 否 |
< | 小于 | L/R | 否 |
<= | 小于等于 | L/R | 否 |
== | 等于 | L/R | 否 |
!= | 不等于 | L/R | 否 |
& | 位与 | L/R | 否 |
^ | 位异或 | L/R | 否 |
| | 位或 | L/R | 否 |
&& | 逻辑与 | L/R | 是 |
|| | 逻辑或 | L/R | 是 |
?: | 条件操作符 | N/A | 是 |
= | 赋值 | R/L | 否 |
+= | 以…加 | R/L | 否 |
-= | 以…减 | R/L | 否 |
*= | 以…乘 | R/L | 否 |
/= | 以…除 | R/L | 否 |
%= | 以…取模 | R/L | 否 |
<<= | 以…左移 | R/L | 否 |
>>= | 以…右移 | R/L | 否 |
&= | 以…与 | R/L | 否 |
^= | 以…异或 | R/L | 否 |
|= | 以…或 | R/L | 否 |
, | 逗号 | L/R | 是 |
有了优先级和结合性运算是一定唯一的吗?
实例一:
没错我就是自己想的,所以尽量越难越好😀。
你们是不是以为是这样?
看看结果吧~
没错,编译器都不知道怎么运算了,也不能这么说,是不是按照我们这样想的运算。所以表达式结果不是简单的用优先级和结合性就可以计算结果,当然这是困难的表达式,一般谁会这样出题,如果以后运算表达式害怕那就用(),减少不必要的麻烦。
再举一个例子:
直接公布结果吧,不同编译器有不同的结果,VS2019编译器的结果是i = 4。linux gcc编译器结果是-63,可以@出你们的结果。
最后要说的话:
制作不易,这篇文章大概耗时8个小时(两次连续3小时),我非常希望有小伙伴找到并纠正我的错误(我暂时还没有发现)。