目录
算数操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
表达式求值
11.1 隐式类型转换
算数操作符
💭 注意
/ 除法 --得到的是商
% 取模(取余)--得到的是余数
如果除法操作符的两个操作数都是整数,执行的是整数除法
如果除法操作符的两个操作数只只要有一个是浮点数,执行的是小数除法
取模操作符两端必须都是整数
例:
int main()
{
printf("%d\n", 10 / 2);//5
printf("%d\n", 10 % 2);//0
printf("%lf\n", 10 / 3.0);//3.333333
printf("%d\n", 10 / 3);//3
return 0;
}
移位操作符
移位操作符移动的是二进制的位。
10进制的数据中:都是0~9的数字组成的。
2进制的数据中:都是0~1的数字组成的。
8进制的数据中:都是0~7的数字组成的。
16进制的数据中:都是0~9 a~f组成的。
数据的二进制表示:
整数的二进制表示形式由3种:原码 反码 补码
注意: 正整数的原码、反码、补码是相同的
负整数的原码、反码、补码要计算
整数在内存中存储的是补码。
原码:把一个数按照正负直接翻译成二进制就是原码
反码:原码符号位不变,其他位按位取反
补码:反码+1
2.1左移操作符 <<
左移一位二进制位(补码),后面空出的一位补0
正数进行左移操作:
int main()
{
int a = 3;
int b = a << 1;
printf("%d\n", b);
return 0;
}
负数进行左移操作:
int main()
{
int a = -3;
int b = a << 1;
printf("%d\n", b);
return 0;
}
负数的补码和原码不同,所以在进行负数移位时应先求出该数字的补码,之后在补码的基础上进行移位操作。
-3的补码:
补码进行移位操作:
移位后根据移位后的二进制位进行-1取反得到原码,即为我们所求的b
也可以继续取反,+1。
2.2 右移操作符 >>
右移分为两种:
逻辑移位:左边用0填充,右边丢弃
算术移位:左边用原该值的符号位填充,右边丢弃
而右移采用以上哪种,取决于编译器。一般使用算术右移。
int main()
{
int a = -5;
int b = a >> 1;
printf("%d\n", b);
return 0;
}
💭 注意:移位操作不要移动负数位。
位操作符--位指的是二进制位(补码)
& 按位与
| 按位或
^ 按位异或
💭 注意:这里进行的操作数必须都是整数形式
& 按位与:对应的二进制位进行按位与,有0结果为0,全1结果为1。即相同为1,不同为0。
例:
int main()
{
int a = 3;
int b = -5;
int c = a & b;
printf("%d\n", c);
//000000000000000000000000000000011 3的补码
//100000000000000000000000000000101
//111111111111111111111111111111010
//111111111111111111111111111111011 -5的补码
//000000000000000000000000000000011 3
}
| 按位或:对应的二进制位进行按位或,有1则为1,全0为0。
例:
int main()
{
int a = 3;
int b = -5;
int c = a | b;
printf("%d\n", c);
return 0;
//000000000000000000000000000000011 3的补码
//111111111111111111111111111111011 -5的补码
//111111111111111111111111111111011 -5的补码,原码为-5
}
^ 按位异或:对应的二进制位,相同为0,相异为1
例:
int main()
{
int a = 3;
int b = -5;
int c = a ^ b;
printf("%d\n", c);
return 0;
//000000000000000000000000000000011 3的补码
//111111111111111111111111111111011 -5的补码
//111111111111111111111111111111000 补码
//100000000000000000000000000000111 取反
//100000000000000000000000000001000 加1 -8
}
💭:异或支持交换律和结合律
例1:不创建临时变量实现两个数字交换
int main()
{
int a = 3;
int b = 5;
printf("前%d %d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("后%d %d\n", a, b);
return 0;
}
例2:求一个整数存储在内存中的二进制1的个数
int main()
{
int a = 10;
int count = 0;
int i = 0;
while (i < 32)
{
if (a & 1 == 1)
{
count++;
}
a = a >> 1;
i++;
}
printf("%d\n", count);
//000000000000000000000000000001010 2
return 0;
}
赋值操作符
赋值操作符可以进行连续赋值。
单目操作符
! 逻辑反操作
& 取地址操作符
sizeof 操作数的类型长度
~ 对二进制位按位取反
-- 前置、后置--
++ 前置、后置++
*间接引用操作符(解引用操作符)
强制类型转换(类型)
~ 对二进制位按位取反
int main()
{
int a = 0;
printf("%d\n", ~a);
//00000000000000000000000000000000
//11111111111111111111111111111111
//11111111111111111111111111111110
//10000000000000000000000000000001 -1
return 0;
}
int main()
{
int a = 3;
//000000000000000000000000000011
//000000000000000000000000001000
a |= (1 << 3);
printf("%d\n", a);
//000000000000000000000000001011
//111111111111111111111111110111
a &= (~(1 << 3));
printf("%d\n", a);
return 0;
}
关系操作符
> >= < <= != ==
7.逻辑操作符--只关注真假
&& 逻辑与
|| 逻辑或
&&操作符左边为假右边不再计算
|| 操作符左边为真右边不再计算
8.条件操作符
exp1 ? exp2 : exp3
例:两个表达方式结果是一样的
int main()
{
int a = 0;
int b = a ? 1 : 2;
printf("%d\n", b);
/*if (a)
{
b = 1;
}
else
{
b = 2;
}*/
return 0;
}
9.逗号表达式
exp1 , exp2 , exp3 , .......expn
逗号表达式是用逗号隔开的表达式,逗号表达式从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
例:
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, b = a + 1);//13
printf("%d\n", c);
return 0;
}
10.下标引用、函数调用和结构成员
下标引用:[ ]
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d", arr[5]);
return 0;
}
❗注意:arr和10是[ ]的操作数
函数调用( )
int main()
{
int a = strlen("abc");
printf("%d", a);
return 0;
}
void test()
{
}
int main()
{
test();
return 0;
}
❗注意:test是()的操作数
访问结构体成员
结构体.成员名
结构体->成员名
struct S
{
int num;
char c;
};
int main()
{
struct S s = { 100,'b' };//结构体的初始化使用{}
printf("%d\n", s.num);
printf("%c\n", s.c);
return 0;
}
❗注意:.操作符 结构体变量.结构体成员
struct S
{
int num;
char c;
};
void test(struct S* ps)
{
/*printf("%d\n", (*ps).num);
printf("%c\n", (*ps).c);*/
printf("%d\n", ps->num);
printf("%c\n", ps->c);
}
int main()
{
struct S s = { 100,'b' };//结构体的初始化使用{}
test(&s);
return 0;
}
❗注意:结构体指针->结构体成员
11.表达式求值
表达式求值由操作符的优先级和结合性决定,在一些情况下有些表达式的操作数在求值的过程中可能要转换成其他类型来计算。
11.1隐式类型转换 主要讨论short和char类型
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换被称为整型提升。
什么意思呢?简单来讲,CPU的通用寄存器长度是整型变量的长度也就是4个字节,(为了获得更高的精度)即使是两个char类型的相加,也要先转换为整型int的长度再进行相加。
char a,b,c;
a=b+c;
b和c的值被提升为普通整型,然后进行加法运算,加法运算完成后,结果被截断,然后再存储于a中。
如何进行整型提升?
负数的整型提升:
char c1=-1;
变量c1的二进制位(补码)有8个比特位 11111111
char为有符号的char
所以整型提升的时候,高位补充符号位,即为1
提升后的结果:
11111111111111111111111111111111
正数的整型提升:
char c2=1;
变量c2的二进制位(补码)有8个比特位 00000001
char为有符号的char
所以整型提升的时候,高位补充符号位,即为 0
提升后的结果:
00000000000000000000000000000001
无符号整型提升,高位补0
int main()
{
char a = 3;
//00000000000000000000000000000011
//00000011 截断
char b = 127;
//00000000000000000000000001111111
//01111111 截断
char c = a + b;
//00000000000000000000000000000011
//00000000000000000000000001111111
//00000000000000000000000010000010
//10000010 c 截断
//整型提升
printf("%d", c);
//因为c是char类型的数据所以又要进行整型提升
// //10000010 有符号数用符号位填充
//11111111111111111111111100000010 补码
//10000000000000000000000011111101 取反
//10000000000000000000000011111110 原码
return 0;
}
int main()
{
char c = 1;
char d = 2;
printf("%u\n", sizeof(c));//1
printf("%u\n", sizeof(+c));//4 进行了整型提升
printf("%u\n", sizeof(-c));//4 进行了整型提升
printf("%u\n",sizeof(c+d));//4 进行了整型提升
return 0;
}
11.2算术转换 主要讨论字节大小大于等于int的变量类型
如果某个操作符的各个操作数属于不同类型,除非其中一个操作数转换为另一个操作数的类型,否则操作无法进制,下面层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
类型向上转换 例如int 和long int进行表达式求值,将int 转换为long int 进行计算。
复杂表达式的求值有3个影响因素:
操作符的优先级 操作符的结合性 是否控制求值顺序
两个相邻操作符先执行哪一个取决于它们的优先级,如果两者优先级相同取决于它们的结合性
注意控制求值顺序操作符:&& 逻辑与 | |逻辑或 ?:条件操作符 ,逗号表达式控制求值顺序,也就是说可以控制哪些算哪些不算
&&操作符左边为假右边不再计算
|| 操作符左边为真右边不再计算
exp1?exp2:exp3 exp1真则表达式为exp2的值否则为exp3
, 逗号表达式的值为最后一个表达式的值
操作符的属性:
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer);
return 0;
}
函数的调用先后顺序无法通过操作符的优先级决定
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);//12
printf("%d\n", i);//4
return 0;
}
在我们写代码时,还是希望能够写出能表现出操作符优先顺序的代码。