操作符详解
C语言中的操作符(也叫运算符)类别很多,五花八门。不同的操作符分工不同,常见的可以分为以上几类。
1. 算数操作符
+ - * / %
- 除了
%
操作符外,其他的几个操作符都可以作用于整数和浮点数. - 对于
/
操作符
如果两个操作数都为整数,就执行整数除法.
如果但凡有一个浮点数,则执行浮点数除法. %
操作符的两个操作数必须为整数,返回的是整除之后的余数.
2. 移位操作符
<< 左移操作符 (按二进制)
>> 右移操作符 (按二进制)
左移操作符的移位规则:
左边抛弃,右边补 0 .
右移操作符的移位规则:
1.逻辑移位: 若为正数,右边抛弃,左边补 0
2.算数移位: 若为负数,右边抛弃,左边补 1 (符号位)
注:对于移位操作符,不要移动负数位(i >> -1
),这个操作也归于一类操作,叫做未定义行为
这种行为被称为程序员的高压线之一,因为它的执行结果完全无法预期,不知道输出结果该作何解释,有何意义。
int num = 10;
num >> -1;
//error
同时我们应该知道 i >> 1
相当于i / 2
; i << 1
相当于i * 2
;
3. 位操作符
& //按位与
| //按位或
^ //按位异或 (相同为0,不同为1)
注:位操作符的操作数必须是整数.
一个小练习:
#include <stdio.h>
int main(){
int num1 = 1
int num2 = 2;
num1 & num2;
num1 | num2;
num1 ^ num2;
return 0;
}
例一:代码实现求一个整数储存在内存中的二进制 1 的个数。
//方法一
#include <stdio.h>
int main(){
int num = 10;
int count = 0;//计数器
while(num){
if(num % 2 ==1){
count++;
}
num = num / 2;
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}
这样可以达到目标,但是代码就尽善尽美了吗?
如果输入的是负数计算结果还是正确的吗?
- 显然不能,负数会在
num = num / 2;
操作后发生错误 , 负数本应该会有很多1但是不能正确输出,这时候就要想着去优化和改进代码了
//方法二
#include <stdio.h>
int main(){
int num = -1;
int count = 0;
int i = 0;
for(i = 0;i < 32;i++){
if(((num >> i) & 1) == 1){
count++;
}
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}
- 代码二与代码一的区别最主要是:
建立了循环32次,(int类型4个8进制位),通过右移每一位并进行& 1
操作,判断每一位是否为1,而不是去与 2 求余,从二进制的角度分析问题,比直观常理感觉上更为严谨.
那么还可以继续优化吗?看着循环32次好像可以改进,如果数字的二进制只有几位低位存在1,高位都是0,那么判断32次有必要吗?所以:
//方法三
#include <stdio.h>
int main(){
int num = -1;
int count = 0;
int i = 0;
while(num){
count++;
num = num & (num - 1);
}
printf("二进制中1的个数:%d\n",count);
return 0;
}
- 代码三中唯一费解的就是
num = num & (num - 1)
这一句.
这个语句的作用就是每执行一次此语句,就减少一个二进制位,说法比较抽象,大家可以用 num = 14
进行测试便知.
4. 赋值操作符
不满意现在的值,通过赋值操作符可以随时重新赋值:
int weight = 120; //体重120斤.
weight = 89; //好的,我瘦了.
赋值操作符也可以连续使用:
int a = 10, x = 0,y = 20;
a = x = y + 1;
以上这样写代码可读性并不高
x = y + 1;
a = x;
同样的语义,这样的写法就更加清晰且易于调试,推荐上面这种.
5. 复合操作符
+=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的效果:
int x = 10;
x = x + 10;
x += 10; //复合赋值,二者等效
6. 单目操作符
! // 逻辑反操作
- //负值
+ //正值
& //取地址
sizeof //操作数的类型长度
~ //按位取反
-- //自减操作符
++ //自增操作符
* //间接访问操作符(解引用操作符)
(类型) //强制类型转换
这里谈一下自增自减运算符,代码如下:
#include <stdio.h>
int main(){
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n",ret);
printf("%d\n",i);
return 0;
}
程序的执行结果不 ! 确 ! 定 !
(++i) + (++i) + (++i)
这一语句在不同操作系统下运算得到值是不同的
笔者尝试在vs2017上与linux系统环境gcc编译器上得到了不同的结果,vs上得到结果是10 4
,而linux环境上是12 4
- 所以这一句代码不是一个好代码,可以说就是一个坑~
- 是先算自增还是先算加减,还是边加减边自增,不明确操作系统,谁都说不准,因为只有编译器知道.
- 表达式的求值依赖于运算符的优先级和结合性,但操作符的优先级和结合性又不能确定唯一的计算路径.
- 这样的表达式产生的结果严重依赖于表达式的求值顺序,所以尽量不要写这样子的表达式.
没必要纠结这些细枝末节,看官们就当了解了解,看个戏吧
7. 关系操作符
>
>=
<
<=
!= //用于测试"不相等"
== //用于测试"相等"
小心再编程过程中 =
与==
书写错误.
8. 逻辑操作符
&& //逻辑与
|| //逻辑或
! //逻辑非
- 注意区别逻辑与和按位与.
1 & 2 -->0
1 && 2 -->1
- 注意区分逻辑或和按位或.
1 | 2 -->3
1 || 2 -->1
请观察代码:
#include <stdio.h>
int main(){
int i = 0,a=0,b=2,c=3,d=4;
i = a++ && ++b && d++;
//j = a++ || ++b || d++;
printf("%d %d %d %d",a,b,c,d);
return 0;
}
这就是一道短路求值的习题.
短路求值是:
- 如果
&&
左侧表达式为 假,右侧表达式不会求值. - 如果
||
左侧表达式为 真,右侧表达式不会求值.
有了短路求值的知识,上面这段程序就迎刃而解了,而且,短路求值大多数编程语言都支持.
9. 条件操作符
exp1 ? exp2 : exp3
如
if(a > 5)
b = 3;
else
b = -3;
就和 a > 5 ? b = 3 : b = -3;
是等效的
很明显后者要简便很多啊,不知道这颗 语法糖 各位程序员吃到了吗.
10. 逗号表达式
exp1 , exp2 , exp3 , ...., expN
逗号表达式就是用逗号分隔开的表达式
从左向右执行,整个表达式返回结果就是最后那个表达式的结果,前面的语句都会执行,但不会返回值.
逗号表达式的语法和规定都很简单,就不展开解释了.
11. 下标引用/函数调用/结构成员操作符
[]
下标引用操作符
操作数: 一个数组名 + 一个索引值
int arr[10]; //创建数组
arr[9] = 10; //使用下标引用操作符
// [ ] 的两个操作数是arr和9
()
函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名.剩余操作数就是传递给函数的参数
void test1(){
printf("hehe");
}
void test2(const char* str){
printf("%s",str);
}
int main(){
test1();
test2("hello world");
return 0;
}
- 访问结构成员操作符
.
结构体 . 成员名
->
结构体指针 -> 成员名
具体会在后面结构体部分总结.