+ - * / %
1. / 操作符的两个操作数都是整数时,执行的是整除运算,只要两个操作数中有一个是浮点数,执行的就是浮点数除法
#include
int main()
{
float n1 = 3 / 2;
printf("3 / 2 = %f\n", n1);
float n2 = 3.0 / 2;
printf("3.0 / 2 = %f\n", n2);
return 0;
}
2. % 操作符的两个操作数必须是整数,返回的是余数(除了取余操作符,其它四个算数操作符的操作数既适用于浮点型,也适用于整型)
<< >>
移位操作符移动的是二进制位
1.左移位操作符<< :左边丢弃,右边补0
#include
int main()
{
int a = 5;
int b = a << 1;
printf("a = %d\n", a); //a = 5
printf("b = %d\n", b); //b = 10
return 0;
}
2.右移位操作符>>:
a.逻辑移位:右边丢弃,左边补0
b.算术移位:右边丢弃,左边补原符号位
#include
int main()
{
int a = -1;
int b = a >> 1;
printf("a = %d\n", a); //a = -1
printf("b = %d\n", b); //b = -1
//由于进行移位操作后得到的值为-1,因此说明编译器采用的是算术右移
return 0;
}
警告:移动负数位是未定义的
int a = 1;
a << -1; //报错
& | ^
操作数为整数,两个整数在内存中对应的二进制位进行运算
1.按位与 & :当且仅当两个位都为1时,结果为1,否则结果为0
2.按位或 | :当且仅当两个位都为0时,结果为0,否则结果为1
3.按位异或 ^ :两个位相同,结果为0,两个位不同,结果为1
#include
int main()
{
//a在内存中的补码
//00000000 00000000 00000000 00000101
//b在内存中的补码
//00000000 00000000 00000000 00000001
int a = 5;
int b = 1;
//00000000 00000000 00000000 00000001
int c = a & b;
printf("c=%d\n", c); //c=1
//00000000 00000000 00000000 00000101
c = a | b;
printf("c=%d\n", c); //c=5
//00000000 00000000 00000000 00000100
c = a ^ b;
printf("c=%d\n", c); //c=4
return 0;
}
a ^ a = 0 a ^ 0 = a
用此规律,我们实现如下一题
不创建临时变量,实现两个数的交换
#include
int main()
{
int a = 5;
int b = 8;
a = a ^ b;
//b = a^b^b = a^0 = a
b = a ^ b;
//a = a^b^a = b^0 = b
a = a ^ b;
printf("a=%d,b=%d", a, b); //a=8,b=5
return 0;
}
=
赋值操作符的结合性是从右到左,依次如下赋值后a的值相等
#include
int main()
{
int a = 2;
int b = 1;
int c = 9;
//1.
a = b = c; //a=9
//2.
b = c;
a = b; //a=9
return 0;
}
+= -= *= /= %= <<= >>= &= ^= |=
如+=操作符
int a = 5;
a += 5; //a=10
a = a + 5; //a=10
其他复合赋值符都可以写成如上形式
注:相加之前右侧表达式需要被完整求值
#include
int main()
{
int a = 5;
int b = 2;
//应为a=a+(2*b)
//错误:a=(a+2)*b
a += 2 * b;
return 0;
}
! & * sizeof ~ - + (类型) ++ --
逻辑反操作符 !:操作数为真,结果为假,产生一个整型结果0,操作数为假,结果为真,产生一个整型结果1
取地址操作符 & :结果为操作数的地址
间接访问操作符(解引用操作符) * :访问指针所指向的值
判断操作数的类型长度 sizeof :单位为字节
a.当操作数是类型名时,必须要加( )
b.当操作数时表达式时,( )加或不加都是合法的
#include
int main()
{
int a = 1;
int sz1 = sizeof int; //不合法
int sz2 = sizeof(int); //合法
int sz3 = sizeof a; //合法
int sz4 = sizeof(a); //合法
return 0;
}
同时在判断表达式的长度时,结果是操作数的类型大小,而不需要对表达式进行求值
#include
int main()
{
int a = 5;
short b = 0;
int sz = sizeof(b = a + 1);
printf("sz=%d,a=%d,b=%d", sz, a, b);
return 0;
}
sizeof一般都是在编译时就求值,只需要表达式最终结果的类型长度,如上述代码,sizeof(b = a + 1)中的操作数为表达式b=a+1,在编译过程中就可判断出表达式最终应是short类型,因此sizeof结果为2,而b=a+1却不进行运算
当sizeof的操作数是数组名时:
void test(int* arr)
{
printf("%zd\n", sizeof(arr)); //8
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%zd\n", sizeof(arr)); //40
printf("%zd\n", sizeof(ch)); //10
test(arr);
return 0;
}
sizeof的返回值类型时size_t,无符号整型,用 %zd 打印,如上可以看出,当sizeof的操作数是数组名时,得到的结果是数组元素总共所占字节数,当数组名作为实参,sizeof所计算的事实上是指向数组首元素的指针变量的大小
#include
int i; //全局变量自动初始化为0
int main()
{
//-1
//补码 11111111 11111111 11111111 11111111
i--;
//sizeof(i) = 4
//sizeof的返回值类型是size_t,是一个无符号整型,i与它进行比较要先进行算术转换为一个无符号整型
//-1的补码看作是一个无符号整型时是一个非常大的正数,大于4
if (i > sizeof(i))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
求补操作符(按位取反操作符)~ :操作数中为1的位变为0,位为0的变为1
正值,负值 + - :+什么也不做,-产生操作数的负数
(类型):强制类型转换
#include
int main()
{
int a = 1;
float b = (float)a;
printf("%f", b);
return 0;
}
若不进行强制类型转换,会产生警告
由于优先级很高,因此若想强制类型整个表达式,则需要将整个表达式用括号括起来
自增,自减操作符 ++ -- :分为前置和后置,此操作符将复制一份变量值的拷贝,前置操作符在进行复制之前增加变量的值,后置操作符在进行复制后再增加变量的值
#include
int main()
{
int x = 5;
int a = x++; //先将x的拷贝值赋给a,x再增加1: a=5, x=6
int b = x--; //先将x的拷贝值赋给b,x再减少1: b=6, x=5
int y = 5;
int c = ++y; //先将y的值增加1,然后将增加后的拷贝值赋给c: c=6, y=6
int d = --y; //先将y的值减少1,然后将减少后的拷贝值赋给d: d=5, y=5
return 0;
}
> >= < <= != ==
操作符的两个操作数如果满足操作符所指定的关系,表达式的结果为1,如果不符合,表达式的结果为0
&& ||
逻辑与 &&:如果两个表达式的值都为真,则整个表达式的值为真,如果有任一表达式为假,整个表达式的值则为假
逻辑或 || :如果两个表达式的值都为假,则整个表达式的值为假,如果有任一表达式为真,整个表达式的值则为真
短路求值:对于&&操作符,如果第一个操作数的值为假,右操作数则不进行求值
对于 || 操作符,如果第一个操作数的值为真,右操作数则不进行求值
#include
int main()
{
int a = 5;
int b = 4;
int c = 3;
//因为ab为真,则c++求值
if (a > b && c++)
;
printf("%d\n", c); //4
return 0;
}
#include
int main()
{
int a = 5;
int b = 4;
int c = 3;
//因为a>b为真,则c++不求值
if (a > b || c++)
;
printf("%d\n", c); //3
//因为a
expression1 ? expression2 : expression3
expression1 先计算,如果值为真,整个表达式的值就是 expression2 的值,expression3 不进行求值;如果 expression1 的值为假,整个表达式的值就是 expression3 的值,expression2 不进行求值
expression1 , expression2 , ... , expressionN
逗号表达式,从左向右逐个求值,整个表达式的值就是最后一个表达式的值
#include
int main()
{
int a = 5;
int b = 3;
//整个表达式的值是b=1的值,为真
if (a + 1, b > 0, b = 1)
{
printf("%d\n", b);
}
//整个表达式的值是b=0的值,为假,if语句内的代码块不执行
if (a + 1, b > 0, b = 0)
{
printf("%d\n", b);
}
return 0;
}
[ ] ( ) . ->
下标引用操作符 [ ] :两个操作数,一个是数组名一个是索引值
函数调用操作符 ( ) :接受一个或多个操作符,第一个操作数是函数名,剩余操作数是传给函数的参数
结构变量.成员名:如 s.a 访问 s 中名叫 a 的成员
指向结构体的指针->成员名:如p是指向结构s的指针,p->a 访问s中名叫a的成员
表达式求值有两个规则
由于C语言中的算术运算至少以默认整型类型(int)进行,因此char和short型操作数在使用前要被转换为int,这种转换叫整形提升
整型提升的原因:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度,因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。 通用CPU难以直接实现两个8比特字节直接相加运算。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
整型提升的方法:按照变量数据类型的符号位进行提升
#include
int main()
{
unsigned char a = 5;
//a在内存中的补码为
//00000101
//a为无符号char,整型提升时高位补0
//00000000 00000000 00000000 00000101
char b = -1;
//b在内存中的补码为
//11111111
//b为有符号char,整型提升时高位补符号位,为1
//11111111 11111111 11111111 11111111
char c = 1;
//c在内存中的补码为
//00000001
//c为有符号char,整型提升时高位补符号位,为0
//00000000 00000000 00000000 00000001
return 0;
}
如果一个操作符的各个操作数是不同类型的,那么就需要其中一个操作数转换为另一个操作数的类型,否则操作无法进行
long double > double > float > unsigned long int > long int > unsigned int > int
如上,如果一个操作数 a 的类型排名小于另一个操作数 b 的类型排名,那么此操作数 a 应先转换为操作数 b 的类型,再执行操作
但是如果这种算术转换不合理,可能会产生问题,如:int算术转换为float,可能会损失精度
表达式的求值顺序由3个因素决定:操作符优先级,结合性,和操作符是否控制执行顺序。两个相邻操作符哪个先执行取决于优先级,若优先级相同,则取决于结合性。而逻辑与,逻辑或,逗号运算符,条件运算符则可以决定表达式的求值顺序。
优先级 | 运算符 | 含义 | 结合性 | 运算对象个数 |
---|---|---|---|---|
1 | ( ) | 聚组 | N/A | N |
( ) | 函数调用 | L-R | 1函数名+N参数 | |
[ ] | 下标引用 | L-R | 1数组名+1索引值 | |
. | 访问结构成员 | L-R | 1结构变量+1成员名 | |
-> | 访问结构指针成员 | L-R | 1指向结构体的指针+1成员名 | |
2 | ++ | 后置自增 | L-R | 1 |
-- | 后置自减 | L-R | 1 | |
! | 逻辑反 | R-L | 1 | |
~ | 按位取反 | R-L | 1 | |
+ | 正值 | R-L | 1 | |
- | 负值 | R-L | 1 | |
++ | 前置自增 | R-L | 1 | |
-- | 前置自减 | R-L | 1 | |
* | 解引用操作符 | R-L | 1 | |
& | 取地址操作符 | R-L | 1 | |
sizeof | 长度操作符 | R-L | 1 | |
(类型) | 类型转换 | R-L | 1 | |
3 | * | 乘法 | L-R | 2 |
/ | 除法 | L-R | 2 | |
% | 取余 | L-R | 2 | |
4 | + | 加法 | L-R | 2 |
- | 减法 | L-R | 2 | |
5 | << | 左移位 | L-R | 2 |
>> | 右移位 | L-R | 2 | |
6 | < | 小于 | L-R | 2 |
<= | 小于等于 | L-R | 2 | |
> | 大于 | L-R | 2 | |
>= | 大于等于 | L-R | 2 | |
7 | == | 等于 | L-R | 2 |
!= | 不等于 | L-R | 2 | |
8 | & | 按位与 | L-R | 2 |
9 | ^ | 按位异或 | L-R | 2 |
10 | | | 按位或 | L-R | 2 |
11 | && |
逻辑与 | L-R | 2 |
12 | || | 逻辑或 | L-R | 2 |
13 | ? : | 条件操作符 | N/A | 3 |
14 | = | 赋值 | R-L | 2 |
+= | 以...加 | R-L | 2 | |
-= | 以...减 | R-L | 2 | |
*= | 以...乘 | R-L | 2 | |
/= | 以...除 | R-L | 2 | |
%= | 以...取余 | R-L | 2 | |
<<= | 以...左移 | R-L | 2 | |
>>= | 以...右移 | R-L | 2 | |
&= | 以...与 | R-L | 2 | |
^= | 以...异或 | R-L | 2 | |
|= | 以...或 | R-L | 2 | |
15 | , | 逗号操作符 | L-R | N |
注:此表格其实不用花时间去记,平时用到了可以查查,或者直接用括号(聚组)括起来即可
但尽管有了优先性,结合性,还是会有些表达式,它们在不同的编译器下表达式的结果可能不同,我们要避免写出这些问题表达式