运算符概览
变量和常量可以参与各种运算,比如:
int a,b,c;
a=1+1;
b=a+1;
c=a+b;
参与运算的常量和变量称为 操作数(Operand),运算使用的符号称为 运算符(Operator),运算符和操作数所组成的算式称为 表达式(Expression)。
C 语言的运算符可以分成以下几类:
- 算术:
- 加
+
、减-
、乘*
、除/
、求余%
、自增++
、自减--
- 加
- 关系:
- 大于
>
、小于<
、等于==
、大于等于>=
、小于等于<=
、不等于!=
- 大于
- 逻辑
- 与
&&
、或||
、非!
- 与
- 位操作
- 位与
&
、位或|
、位非~
、位异或^
、左移<<
、右移>>
- 位与
- 赋值
- 简单赋值
=
、复合运算(+=
、-=
等)、复合位运算(&=
、|=
等)
- 简单赋值
- 条件
- 三目运算符
?:
- 三目运算符
- 逗号:把若干表达式组成一个新的表达式
- 指针
- 取内容
*
、取地址&
- 取内容
- 求字节:计算数据所占的字节数
sizeof
- 特殊:
- 括号
()
、下标[]
、成员->
.
等
- 括号
不同运算符放在一起时,按照一定先后顺序计算。一般来说,只需要记住几个诀窍:
- 乘除优先于加减
- 逻辑非 > 运算 > 关系 > 逻辑与 > 逻辑或 > 赋值
- 赋值、复合运算、复合位运算是除
,
外最低的
另外,有几个很容易出错的优先级问题:
优先级问题 | 表达式 | 误认为的结果 | 实际结果 |
---|---|---|---|
. 高于 * ,可以用 -> 来解决 |
*p.f |
(*p).f p所指对象的f |
*(p.f) p的f所指的对象 |
[] 高于 * |
int *ap[] |
int (*ap)[] ap是个指向数组的指针 |
int *(ap[]) ap是个指针的数组 |
函数() 高于* |
int *fp() |
int (*fp)() fp是个函数指针,返回 int |
int *(fp()) fp是个函数,返回 int* |
== 、!= 高于位操作 |
(val&mask!=0) |
(val&mask)!=0 |
val&(mask!=0) |
== 、!= 高于赋值 |
c=getchar()!=EOF |
(c=getchar())!=EOF |
c=(getchar()!=EOF) |
算术运算符高于移位运算符 | msb<<4+lsb |
(msb<<4)+lsb |
msb<<(4+lsb) |
逗号优先级最低 | i=1,2 |
i=(1,2) |
(i=1),2 |
最保险的做法还是勤用括号。
除了优先级外,另一个要关注的就是 左值 与 右值。左值指的是可以放在等号左边被赋值的表达式,而右值指的是可以放在等号右边用于赋值的表达式。目前只有变量可以做左值。
逻辑运算符
逻辑运算符会“偷懒”。比如说下面这段代码:
#include<stdio.h>
int main(void){
int i=0;
int j=0;
if((++i>0)||(++j>0)){
printf("i=%d\n",i);
printf("j=%d\n",j);
}
return 0;
}
\\输出
i=1
j=0
注意到逻辑或 ||
两边的语句中,只有前一个执行了,后一个却没有执行。这是因为逻辑或 ||
只要前面一个为真,输出即为真,所以后一个就无需执行了。这个问题在逻辑与 &&
中也存在,要注意啊❗。
条件运算符
条件运算符 ?:
是一个三目运算符,也就是它有三个操作数:
max=(a>b)?a:b;
\\等价于
if(a>b) max=a;
else max=b;
条件运算符的运行效率在未优化的情况下比 if-else
高,这是因为它俩对应的汇编程序不同。我们可以用下面这段程序比较速度:
#include<stdio.h>
#include<time.h> //用到clock()函数
int main() {
int begintime,endtime;
int a=0;
int b=1;
int max;
//用条件运算符
begintime=clock(); //计时开始
for(register int i = 0;i<100000;i++){
max=(i&1)?a:b;
};
endtime = clock(); //计时结束
printf("Running Time:%dms\n", endtime-begintime);
//用if-else
begintime=clock(); //计时开始
for(register int i = 0;i<100000;i++){
if(i&1) max=a;
else max=b;
};
endtime = clock(); //计时结束
printf("Running Time:%dms\n", endtime-begintime);
return 0;
}
不过现代编译器已经能自动优化 if-else
语句,有时候 if-else
甚至比 ?:
还要高效。所以也不用过分纠结用哪个,一般来说只有简单的赋值才用 ?:
.
另外有些考试很喜欢考条件运算符的优先级,比如说:
a>b?a:c>d?c:d
条件运算符的结合顺序是从右到左,所以右边的先计算:
a>b?a:(c>d?c:d)
自增与自减
自增和自减是非常让人迷惑的玩意。举两个例子:
//例1
int i=0,j=0;
printf("%d\n", i++);
printf("%d\n", ++j);
//++在后,i先取值,再自增
//++在前,j先自增,再取值
//例2
int i=3;
(++i)+(++i)+(++i);
//计算方式一:先计算三次 ++i,i 变为 6,然后三个 6 相加得到 18
//计算方式二:先计算前面的 (++i)+(++i),由于有两次 ++i,i 变为 5,5+5=10,然后 i 再自增变为 6,10+6=16
//两种方式都是对的,取决于用哪种编译器
//gcc 使用的是方式二
位运算
首先要注意的是,位运算的操作对象是 int,如果不是 int,就会转换成 int。我们可以用下面这个例子来说明。
unsigned char c = 0xff;
unsigned int i = ~c;
printf("%#x\n",c);
printf("%#x\n",i);
//输出
0xff
0xffffff00
char 是 8 位的,如果对 0xff 取反,结果应该是 0x00,但由于转换成了 int,所以实际上是对 0x000000ff取反,结果应该是 0xffffff00。
位运算在单片机中还是挺常用的,可以用来设置各种寄存器,比如说:
- 与:把某些位清0,其他位不变
- 或:把某些位置1,其他位不变
- 异或:把某些位取反,其他位不变
另外,可以用 « 或 » 来代替乘 $2^n$ 或 除 $2^n$,这样可以加快运算速度。比如:
257/8 == 256>>3;
456%32 == 456-(456>>4<<4)
还有一个 trick 就是可以用异或,在不引入第三变量的情况下,交换两个变量:
int a=0,b=1;
a=a^b;
b=a^b;
a=a^b;
//等效于
//a=(a^b)^((a^b)^b)
//b=(a^b)^b