引:
操作符这部分内容呐,在小边眼里,是看似食之无味,弃之又非常非常可惜的一部分,而且其实是蛮有意思的。 这么讲是因为我个人认为,学校老师把它打散散布在零星各处又不够深入,而如果出题又可能“刁钻”,所以今天拿出来好好总结一下。
正文开始@边通书
一、操作符分类
二、算术操作符
+,-,* 都非常简单,唯一要注意的两点就是 乘 * 和 取模 %
除号 /
运行结果:
取模 %(整除之后求余数)
三、移位操作符
移位操作符移动的都是 内存中的 二进制位。 其实对于整数的二进制位有3种表示方法:原码,反码,补码,整数在内存中存储的都是补码。
呀,怕小伙伴忘记先说一下,最高位也就是符号位,正数为0,负数为1奥。
左移操作符<<(相对简单)
1.左移原理剖析
1.左移正数a:
运行结果: 2.左移负数c: 运行结果
2.右移原理剖析
右移操作符>> (其实也没夺复杂啦)
(1)右移正数a:
运行结果:
(2)右移负数a:
运行结果: 可见,vs2013采用的是算术右移(即补符号位),或者说大多数编译器都采用算术右移。 且,算术右移似乎更合适一些,你是负数,右移之后仍为负数。
3.注:
可爱同学写的胡乱代码,如下
四、位操作符
1.原理剖析
按位与&
按位或|
按位异或^
2.典例
那么这些位操作符到底有什么用呢?下面看:
典例1:计算某整数存储在内存二进制位中有多少1?
1.方法一: 代码:
#include<stdio.h>
int main()
{
int a = 15;
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
if (((a >> i) & 1) == 1)
{
count++;
}
}
printf("%d\n", count);
return 0;
}
运行结果,如我所愿:
3.变态面试题
一道变态的面试题:不创建临时变量(第三个变量),实现两个数交换。
我们最最简单也是最常用的办法: 方法一:加减法 运行结果: 此方法虽满足不创建临时变量的要求,但是有一定问题的: 如果a,b都很大且还在int范围之内,buta = a + b 很有可能溢出。依然不是最好的解决方案。
方法二:异或的方法
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d,b = %d\n", a, b);
return 0;
}
运行结果: 原理剖析: 异或这个操作符是很有意思的,它让我想起了集合论与图论里的对称差,那个小三角符号。 然而这种方法实际用的比较少:原因: 1.代码可读性不好,乍一看都不知道什么意思 2.只适用于整形 3.且执行效率低 优点是满足了面试官的需求哈哈哈哈。
五、赋值操作符
赋值操作符是一个很棒的操作符,可以让你得到一个之前不满意的值。 1.连续赋值 2.复合赋值符 a += 2; 即为a = a + 2; a >>= 1; 即为a = a>>1; a &= 1 即为a = a & 1 , 显然前者更加简洁。
类似的有这些:
+= ,-=,*=, /=,%=,<<=,>>=,&=,|=,^=
六、单目操作符
单目操作符:即为只有一个操作数的操作符
逻辑反!
一般使用场景如下:
正号+ 负号-
比较简单,不多讲
取地址& 解引用*
sizeof
1.作用剖析 与 strlen()简单对比 运行结果: 2.sizeof是操作符,不是函数 面试官问你:“小伙子,sizeof是不是函数?”,你说,“是!”,面试官“孩子,回家吧。”哈哈哈哈哈
下面用代码简要说明一下: 3.sizeof 内部的表达式是不参与真实运算的 看看以下代码,小伙伴们觉得结果是什么?
#include<stdio.h>
int main()
{
int a = 5;
short s = 10;
printf("%d\n", sizeof(s = a + 2));
printf("%d\n", s);
return 0;
}
运行结果: 原理剖析: 4.sizeof与数组 读以下代码,给出你的结果:
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
原理剖析: 运行结果: 32位平台:
按位取反~
二进制位全部取反,连符号位也要取反!!! 1.上代码感受一下 运行结果: 调试窗口,看看内存中~a的真实存在: 2.应用场景
++,- -
1.前置++ 运行结果:
2.后置++ 运行结果: 前置- -,后置- -,都同理,不赘述。 3.不建议研究过于复杂的相关代码 在vs下,运行结果为12; 然而在Linux平台下,gcc编译器算出结果为10, 那么这段代码本身就是错误的,就没有什么必要纠结,也不要写这样的代码。 至于为什么出现这种情况,后文会讲。
强制类型转换(类型)
七、关系操作符
两 同类型 的变量之间 比较大小,比较简单,不做赘述
<,>,<=,>=,==,!=
八、逻辑操作符
关注的是真假。
&&,||
1.逻辑与&& 360笔试题:逻辑操作符的短路效果
#include <stdio.h>
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0; }
程序运行结果为多少? 剖析:左为假,则右边不再计算 举一反三:若将a的初值赋为1呐? 运行结果:
2.逻辑或|| 完全类似的题目: 运行结果: 举一反三:若将a初值再改成0呐? 运行结果:
九、条件操作符
(exp1) ? (exp2) : (exp3)
十、逗号表达式
从左向右执行,整个表达式结果是最后一个表达式 的结果。 运行结果: 应用:
十一、下标引用,函数调用和结构成员
1.下标引用
进一步说明[ ] 是操作符: 运行结果:
2.函数调用
3.结构成员
上代码加强理解: 运行结果: 注:
十二、表达式求值
操作符可以说是为表达式而服务的,也影响着表达式的结果,主要体现在以下两方面: 1.表达式求值的顺序 ~ 操作符的优先级和结合性 2.类型转换 ~ 操作数在求值过程中可能需要转化为其他类型
1.隐式类型转换
隐式,即偷偷地,没法实在地看到。 这里要提到整形提升的概念及意义。
1.整形提升是什么?
C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。 整形提升规则,按照数据类型的符号位来提升。
2.整形提升的意义 小边从来不喜欢贴大段文字,但在这里觉得很有必要,有助于理解为什么要整形提升。
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
这可能也是寄存器读写很快的原因。
上代码感受: 看看你觉得结果是多少?
#include<stdio.h>
int main()
{
char a = 3;
char b = 127;
char c = a + b;
printf("%d\n", c);
return 0;
}
运行结果: 有了前面的铺垫,看到这个结果也不会感到震惊。
原理剖析:
巩固练习1:
#include<stdio.h>
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;
}
运行结果:只打印c 原理剖析:
巩固练习2: 运行结果:
2.算术转换
寻常算数转换:如果某个操作符的各个操作数属于不同的类型,那么其中一个操作数的必须转换为另一个操作数的类型,否则操作就无法进行。
代码感受: 转换规则:
3.操作符属性
关于优先级,这里值提供部分,有个感觉就好,记不住就加括号:
4.一些问题表达式
那是不是掌握了操作符的优先级及结合性及是否控制求值顺序,就一定能得到表达式的唯一结果了呐?也不是滴。下面来看一些问题表达式。
(1)代码1:
a*b + c*d + e*f;
由于* 比 + 的优先级高,只能保证,* 的计算是比 + 早,但是优先级并 不能决定第三个*比第一个+早执行。变量之间的牵连,会导致结果可能不同。
(2)代码2: (3)代码3: 不同编译器下跑出结果不同: 求值应有唯一路径,那么这样代码就没什么意义。 (4)代码4
(5)代码5 很早小边就拿出过这段代码了
Linux平台下,编译结果 vs2013平台下,编译结果 调试起来,转到反汇编,简单看看即可: 本文完
相信小伙伴们会收获满满的。
|