目录
算数操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
1.算数操作符
+ - * / %
1-1. 取模/取余 %
得到的相除之后是余数 【取模负数】
取模操作符的两个操作数必须是整数
1-2. 除法 /
得到的是相除之后的商
(当除号两端都是整数,执行整数除法,两端只要有一个浮点数,执行浮点数除法)
2.移位操作符
(操作数只能是整数)
不要移动负数位!!!(标准未定义行为)
<< 左移操作符 >> 右移操作符
2.1左移操作符
将操作数在内存中存储的二进制位向左移动,后面补0
int a = 5;
00000000 00000000 00000000 00000101 // <- 对补码进行操作(内存存储的是补码,进行运算也使用补码,正整数的原反补相同)
int b = a << 1;
00000000 00000000 00000000 00001010 // <- 补码/原码
//(b = 10 *****左移操作符部分情况下相当于将操作数乘以2*****)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
int a = -5;
11111111 11111111 11111111 11111011 // <- 补码
int b = a << 1;
11111111 11111111 11111111 11110110 // <- 补码
10000000 00000000 00000000 00001010 // <- 原码
//(b = -10)
2.2右移操作符
(到底是算数右移还是逻辑右移,取决于编译器 ~ )
常见的编译器下都是算术右移
2.2.1.算数右移(主要用来进行有符号位的倍增、减半)
(右边丢弃,左边补符号位)
2.2.2.逻辑右移(主要用来进行无符号位的倍增、减半)
(右边丢弃,左边补0)
//正数的算数右移和逻辑右移是相同的
int a = 5;
00000000 00000000 00000000 00000101 <-对补码进行操作 右移一位,左边补符号位0
int b = a >> 1;
00000000 00000000 00000000 00000010 <- 补码
//(b = 2 *****右移操作符部分情况下相当于将操作数除以2*****)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————
//负数的算数右移
int a = -5;
11111111 11111111 11111111 11111011 // <- 补码
int b = a >> 1;
11111111 11111111 11111111 11111101 // <- 补码
10000000 00000000 00000000 00000011 // <- 原码
//(b = -3)
———————————————————————————————————————————————————————————————————————————————————————————————————————————————
//负数的逻辑右移
int a = -5;
11111111 11111111 11111111 11111011 // <- 补码
int b = a >> 1;
01111111 11111111 11111111 11111101 // <- 补码
00000000 00000000 00000000 00000011 // <- 原码
//(b = 3)
3.位操作符
(对两个操作数存在内存中的二进制补码进行操作)
& 按位与 |按位或 ^按位异或
1.按位与
(两个二进制序列,只要有0就为0,只有同为1时才为1)
【可以通过 a & 1,然后 a >> 1 ,来的到a的二进制序列的每一位】
2.按位或
(两个二进制序列,只要有1就为1,只有同为0时才为0)
3.按位异或
(两个二进制序列,相同为0,相异为1)
【不使用临时变量交换两个数——缺点:只能用于整数的交换,可读性低】
4.赋值操作符
赋值操作符 = 复合赋值符+= -= *= /= %= <<= >>= &= |= ^=
a = x = y+ 1; 支持连续赋值但是不提倡
x = y + 1;
a = x; 这样写更加清晰且易于调试
3 = 1000;(报错:左操作数必须为左值)
左值,是可以放在等号左边的,一般是一块空间。
右值,是可以放在等号右边的,一般是一个值或者一块空间的内容。
5.单目操作符
// !逻辑反操作 -负值 +正值 &取地址 sizeof计算操作数的类型长度(以字节为单位) ~按位取反
--前置、后置-- ++前置、后置++ *间接访问操作符(解引用操作符) (类型) 强制类型转换
1.按位取反
int a = 10;
00000000000000000000000000001010
将这一位的二进制数字改成1
a |= (1 00000000000000000000000000000100 (1
在将刚才0改成的1,再改回0
将 (1 11111111111111111111111111111111111011 ~(1
2.逻辑反操作
printf("%d", !2);打印0,(!非零数)为假,为0
pirntf("%d", !0);打印1,(!0)为真,为1
6.关系操作符
> >= < <= != ==
!!!注意 !!!
==
编译的过程中==和=不小心写错,程序会出错
7.逻辑操作符
&& 逻辑与 ||逻辑或
&& 左操作数为假右边不计算
| | 左操作数为真右边不计算
8.条件操作符
(exp1 ? exp2 :exp3) 三目操作符
9.逗号表达式
exp1, exp2, exp3, ……expN
用逗号隔开的多个表达式
从左向右依次执行,整个表达式的结果是最后一个表达式的结果
10.下标引用、函数调用、结构成员
[]下标引用操作符 ()函数调用操作符 . -> 结构体操作符
1.下标引用操作符
arr[7] --> *(arr + 7) --> *(7 + arr) --> 7[arr]
通过上面推导得到 arr[7] 和 7[arr] 效果是一样的
2.结构体操作符
结构体变量.结构体成员
结构体指针->结构体成员
struct Stu { char name[20]; int age; double score; }; int main() { struct Stu s = {"zhangsan", 20, 85.5}; printf("%s %d %.2lf\n", s.name, s.age, s.score); struct Stu* ps = &s; printf("%s %d %.2lf\n", ps->name, ps->age, ps->score); return 0; }
11.表达式求值
1.隐式类型转换
c的整型算数运算至少以缺省整型类型的精度来进行计算
为了获得这个精度,表达式中字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,
同时也是CPU的通用寄存器的长度。
依次,两个char类型的相加,在CPU执行实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU是难以直接实现两个8比特字节直接相加运算(虽然及机器指令中可能有这种字节相加指令)。
所以,表达式中各种长度可能小于int长度的整型值,都想必须转换成int或unsigned int,然后才能送入CPU去执行运算。
char a = 0xb6; char b = 0xb600; char c = 0xb6000000; if(a == 0xb6) printf("a"); if(b == 0xb600) printf("b"); if(c == 0xb6000000) printf("c"); 结果为:c char 和 short 放不下数据 0xb6 会发生截断,那么a 和 b就不是 0xb6这个值了。
char c = 1; printf("%u\n", sizeof(c)); printf("%u\n", sizeof(+c)); printf("%u\n", sizeof(-c)); 输出结果为:1 4 4 因为c前面加了+或-就相当于参与运算,就会发生整型提升为int类型,所以结果为4。
2.算数转换
如果某个操作的各个操作数属于不同类型,那么除非其中一个操作数的类型转换为另一个操作数的类型,
否则操作无法进行。
下列层次体系称为寻常算数转换。
long double double float unsigned long int long int unsigned int int
12.操作符的属性
复杂表达式求值的三个影响因素:
操作符的优先级
操作符的结合性(N/A 不考虑结合性)
是否控制求值顺序
表达式要确定唯一的计算路径,若计算路径不唯一,那么就是有问题的