目录
1. 算数操作符
+加法,-减法,*乘法,/除法,%取模(取余)
- %操作符的两个操作数必须为整数
- +-*/四个操作符可以作用于整数和浮点数
- /操作符的两个操作数都为整数时,执行整数除法。只要有一个是浮点数,执行浮点数除法
整数除法和浮点数除法不同。浮点数除法的结果是浮点数,而整数除法的结果是整数。整数是没有小数部分的数。在C语言中,整数除法结果的小数部分被丢弃,这一过程被称为截断(fruncation)。
#include <stdio.h>
int main()
{
printf("%d\n", 7 / 2); // 3
printf("%lf\n", 7. / 2); // 3.500000
printf("%lf\n", 7 / 2.0); // 3.500000
printf("%lf\n", 7.0 / 2.0); // 3.500000
printf("%d\n", -7 / 2); //-3
printf("%lf\n", 7 / -2.); //-3.500000
printf("%lf\n", 7.0 / -2); //-3.500000
printf("%lf\n", -7. / -2.); // 3.500000
return 0;
}
2. 移位操作符
2.1 左移操作符
整数的二进制表示有3种:原码、反码、补码。正整数的原码、反码、补码相同。负整数的反码是原码符号位不变,其他位按位取反,补码是反码+1。整数在内存中存储的是补码。
+7:
原码:00000000000000000000000000000111
反码:00000000000000000000000000000111
补码:00000000000000000000000000000111
-7:
原码:10000000000000000000000000000111
反码:11111111111111111111111111111000
补码:11111111111111111111111111111001
移位规则:左边抛弃,右边补0
int a = 7;
int b = a << 1;
0[00000000000000000000000000001110]
int a = -7;
int b = a << 1;
1[11111111111111111111111111110010]
1 << n = 2的n次幂
int a = 1;
int b = a << 1;
0[00000000000000000000000000000010] = 2的1次幂
int b = a << 2;
00[00000000000000000000000000000100] = 2的2次幂
int b = a << 3;
000[00000000000000000000000000001000] = 2的3次幂
2.2 右移操作符
移位规则:
- 逻辑移位:左边补0,右边抛弃
- 算术移位:左边补原符号位,右边抛弃(常见编译器都是算术移位)
算术移位:
int a = 7;
int b = a >> 1;
[00000000000000000000000000000011]1
int a = -7;
int b = a >> 1;
[11111111111111111111111111111100]1
n >> 1 和 n / 2 非负数和负偶数时二者等价
n为非负数时,n >> 1 等价于 n / 2。
10 >> 1 = 5 10 / 2 = 5 0 >> 1 = 0 0 / 2 = 0
n为负偶数时,n >> 1 等价于 n / 2。
-10 >> 1 = -5 -10 / 2 = -5
n为负奇数时,n >> 1 = n / 2 - 1(n >> 1是n除以2向下取整,n / 2直接丢弃小数部分)。
-5 >> 1 = -3 -5 / 2 = -2
3. 位操作符
&按位与,|按位或,^按位异或
位操作符通过逐位比较两个运算对象,生成一个新值。对于每个位:
- &:两个操作数相应的位都为1,结果为1(有0则0,记忆法:&看起来像0)
- | :两个操作数相应的位至少有一个为1,结果为1(有1则1,记忆法:|看起来像1)
- ^:两个操作数相应的位相同为0,相异为1(无进位相加)
3.1 按位与
int a = 3;
int b = -5;
int c = a & b;
// a的补码:00000000000000000000000000000011
// b的补码:11111111111111111111111111111011
// c的补码:00000000000000000000000000000011
n >> i & 1 获取二进制的i位
一个数n,假设它的二进制的倒数第一位称为0位,倒数第二位称为1位……想要获取i位,就要在i位上& 1。
以75(000000000000000000000001001011)为例,
要想获取0位,就要在0位上& 1:
000000000000000000000001001011
&000000000000000000000000000001
=000000000000000000000000000001
=1
要想获取1位,就要在1位上& 1,如果直接& 10(二进制),得到的结果是10(二进制),这个结果并不是我们想要的,我们想要的结果是1。所以把要把0位去掉,再& 1。
先>> 1,再& 1:
75 >> 1 = [000000000000000000000000100101]1
000000000000000000000000100101
&000000000000000000000000000001
=000000000000000000000000000001
=1
同理,要想获取2位,就要先>> 2,再& 1:
75 >> 2 = [000000000000000000000000010010]11
000000000000000000000000010010
&000000000000000000000000000001
=000000000000000000000000000000
=0
n & 1 和 n % 2 非负数时二者等价
显然,n & 1表示获取n的二进制的最后一位,奇数的二进制的最后一位一定是1,偶数的二进制的最后一位一定是0。所以,当n为非负数时,n & 1和n % 2等价;当n为负数时,由于取模运算保留符号,n % 2 = -1,而n & 1 = 1。
n为非负数时,n & 1等价于n % 2
- n为奇数:n & 1 = 1 n % 2 = 1
- n为偶数:n & 1 = 0 n % 2 = 0
n为负数时,
- n为奇数:n & 1 = 1 n % 2 = -1
- n为偶数:n & 1 = 0 n % 2 = 0
n & ~ (1 << i) 将二进制的i位变为0
一个数n,假设它的二进制的倒数第一位称为0位,倒数第二位称为1位……想要将i位修改成0,就要在i位上& 0,其他位都& 1。
以75(000000000000000000000001001011)为例,
要将3位修改成0,就要在3位上& 0,其他位都& 1:
000000000000000000000001001011
&111111111111111111111111110111
=000000000000000000000001000011
n & n - 1 将二进制最右边的1变为0
n - 1表示将n的二进制最右边的1的右侧区域(包括1)全部按位取反。
n & n - 1表示将n的二进制最右边的1变为0,其余位不变。
24: 00000000000000000000000000011000
23: 00000000000000000000000000010111
24&23:00000000000000000000000000010000
如果n & n - 1为0,则n是2的幂。
+2: 00000000000000000000000000000010
+1: 00000000000000000000000000000001
+4: 00000000000000000000000000000100
+3: 00000000000000000000000000000011
2&1:00000000000000000000000000000000
4&3:00000000000000000000000000000000
n & -n 提取二进制最右边的1
-n表示将n的二进制最右边的1的左侧区域(不包括1)全部按位取反。
+20: 00000000000000000000000000010100
-20: 11111111111111111111111111101100
20 & -20:00000000000000000000000000000100
3.2 按位或
int a = 3;
int b = -5;
int c = a | b;
// a的补码:00000000000000000000000000000011
// b的补码:11111111111111111111111111111011
// c的补码:11111111111111111111111111111011
n | 1 << i 将二进制的i位变为1
一个数n,假设它的二进制的倒数第一位称为0位,倒数第二位称为1位……想要将i位修改成1,就要在i位上| 1,其他位都| 0。
以75(000000000000000000000001001011)为例,
要将2位修改成1,就要在2位上| 1,其他位都| 0:
000000000000000000000001001011
|000000000000000000000000000100
=000000000000000000000001001111
3.3 按位异或
int a = 3;
int b = -5;
int c = a ^ b;
// a的补码:00000000000000000000000000000011
// b的补码:11111111111111111111111111111011
// c的补码:11111111111111111111111111111000
异或运算支持交换律和结合律,即:
a ^ b = b ^ a (a ^ b) ^ c = a ^ (b ^ c)
a ^ a = 0 0 ^ a = a
a ^ a = 0:
如,3 ^ 3 = 00000011 ^ 00000011 = 00000000
0 ^ a = a:
如,0 ^ 3 = 00000000 ^ 00000011 = 00000011
不创建临时变量实现两个数的交换:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a ^ b; // 10^20
b = a ^ b; // 10^20^20=10^0=10
a = a ^ b; // 10^20^10=0^20=20
printf("a = %d b = %d\n", a, b);
return 0;
}
4. 赋值操作符
=赋值
复合赋值符:+=,-=,*=,/=,%=,<<=,>>=,&=,|=,^=
x += 10; // 等价于x = x + 10;
C语言支持多重赋值,其他很多语言都不支持。赋值的顺序是从右往左。
a = b = c = d = 10;
5. 单目操作符
单目就是一元的意思,只有一个运算对象。
!逻辑反操作
-负值,+正值
&取地址,*间接访问操作符(解引用操作符)
sizeof计算变量或类型所创建的变量的长度(单位:字节),返回size_t类型的值
~对一个数的二进制按位取反
++自增,--自减(前置++:先++,后使用,后置++:先使用,后++)
(类型)强制类型转换
6. 关系操作符
>大于,>=大于或等于,<小于,<=小于或等于,!=不相等,==相等
7. 逻辑操作符
&&逻辑与:两个表达式全为真,整个表达式为真
||逻辑或:两个表达式有一个为真,整个表达式为真
短路原则:
- exp1&&exp2:
当exp1为真时,再判断exp2的真假,来确定整个表达式的真假;
当exp1为假时,可以确定整个表达式为假,就不用判断exp2的真假(即不执行exp2)。
- exp1||exp2:
当exp1为真时,可以确定整个表达式为真,就不用判断exp2的真假(即不执行exp2);
当exp1为假时,再判断exp2的真假,来确定整个表达式的真假。
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 10 && b-- == 2) // a == 10为假,可以确定整个表达式为假,不执行b-- == 2
printf("a=%d b=%d\n", a, b);
else
printf("a=%d b=%d\n", a, b); // a=1 b=2
int c = 3;
int d = 4;
if (c < 10 || d++ == 4) // c < 10为真,可以确定整个表达式为真,不执行d++ == 4
printf("c=%d d=%d\n", c, d); // c=3 d=4
else
printf("c=%d d=%d\n", c, d);
return 0;
}
8. 条件操作符
exp1?exp2:exp3
条件操作符是唯一的三目操作符。先求解exp1,若其值为真,则将exp2的值作为整个表达式的取值,否则将exp3的值作为整个表达式的取值。
9. 逗号表达式
exp1,exp2,exp3,...,expN
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
10. 下标引用操作符
[]下标引用操作符
p[i]=*(p+i) p[i][j]=*(p[i]+j)=*(*(p+i)+j)
11. 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
如,test(),Add(a,b)。
12. 结构成员访问操作符
- 结构体变量.成员变量
- 结构体指针->成员变量
#include <stdio.h>
struct Book
{
char bookName[20];
char authorName[20];
int publishYear;
};
struct Novel
{
struct Book b;
char type[10];
char length[10];
};
int main()
{
struct Novel n = { { "射雕英雄传","金庸",1957 },"武侠","长篇" };
struct Novel* p = &n;
printf("%s %s %d %s %s\n", n.b.bookName, n.b.authorName, n.b.publishYear, n.type, n.length);
printf("%s %s %d %s %s\n", (*p).b.bookName, (*p).b.authorName, (*p).b.publishYear, (*p).type, (*p).length);
printf("%s %s %d %s %s\n", p->b.bookName, p->b.authorName, p->b.publishYear, p->type, p->length);
return 0;
}
13. 操作符的属性
表达式(expression)由操作符和操作对象组成,每个表达式都有一个值,必须根据操作符的执行顺序来求值。
操作符的执行顺序取决于它们的优先级,如果优先级相同,取决于他们的结合性。
C语言操作符优先级有15级,按优先级从大到小排序:
优先级 | 操作符 | 描述 | 用法示例 | 结合性 | |
---|---|---|---|---|---|
1 | () | 聚组/函数调用 | (表达式)/函数名(形参声明) | L-R | |
[] | 下标引用 | 数组名[常量表达式] | |||
. | 访问结构成员 | 结构成员访问 | 结构体.成员名 | ||
-> | 访问结构指针成员 | 结构体指针->成员名 | |||
2 | + | 正值 | 单目操作符 | +表达式 | R-L |
- | 负值 | -表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 前置自增 | ++变量 | |||
++ | 后置自增 | 变量++ | |||
-- | 前置自减 | --变量 | |||
-- | 后置自减 | 变量-- | |||
* | 解引用 | *指针变量 | |||
& | 取地址 | &变量 | |||
! | 逻辑非 | !表达式 | |||
~ | 按位取反 | ~表达式 | |||
sizeof | 计算长度 | sizeof(数据类型/变量) | |||
3 | * | 乘法 | 算术操作符 | 表达式*表达式 | L-R |
/ | 除法 | 表达式/表达式 | |||
% | 取模 | 整型表达式/整型表达式 | |||
4 | + | 加法 | 表达式+表达式 | ||
- | 减法 | 表达式-表达式 | |||
5 | << | 左移 | 移位操作符 | 变量<<表达式 | |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 关系操作符 | 表达式>表达式 | |
>= | 大于或等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于或等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | ||
!= | 不等于 | 表达式!=表达式 | |||
8 | & | 按位与 | 位操作符 | 表达式&表达式 | |
9 | | | 按位或 | 表达式|表达式 | ||
10 | ^ | 按位异或 | 表达式^表达式 | ||
11 | && | 逻辑与 | 逻辑操作符 | 表达式&&表达式 | |
12 | || | 逻辑或 | 表达式||表达式 | ||
13 | ?: | 条件操作符 | 表达式1?表达式2:表达式3 | R-L | |
14 | = | 赋值 | 赋值操作符 | 变量=表达式 | |
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
/= | 除后赋值 | 变量/=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
15 | , | 逗号 | 表达式,表达式,… | L-R |
1. 优先级
#include <stdio.h>
int main()
{
// 优先级:自增>加法>赋值>逗号
int a, b, c;
a = 5;
c = ++a;//a=6,c=6
b = ++c, c++, ++a, a++; // c=7,b=7,c=8,a=7,a=8
b += a++ + c; // b=7+8+8=23,a=9
printf("a = %d b = %d c = %d\n:", a, b, c); // a = 9 b = 23 c = 8
return 0;
}
2. 结合性
*p++:解引用(*)和后置自增(++)优先级相同,结合性都是从右往左,所以*p++等价于*(p++):
- 先执行*p
- 再p++