目录
算术操作符
- * / %
-
除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
-
对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
-
% 操作符的两个操作数必须为整数。返回的是整除之后的余数
移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。
左移操作符: 左边抛弃,右边补0

cpp
#include <stdio.h>
int main()
{
int num = 10;
printf("%d\n", num << 1); //20
printf("%d\n", num); //10
return 0;
}
右移操作符:
• 逻辑右移:左边用0填充,右边丢弃
• 算术右移:左边用该值的符号位填充,右边丢弃

注:对于移位操作符,不要移动负数位,这个是标准未定义的!
cpp
int num = 10;
num >> -1;//error
位操作符
& 按位与,都为1结果才为1,有1个0则为0
| 按位或,都为0结果才为0,有1个1则为1
^ 按位异或,相同为0,相异为1
注:他们的操作数必须是整数,且操作都是针对二进制位进行操作
cpp
#include <stdio.h>
int main()
{
int num1 = 1;
int num2 = 2;
printf("%d\n", num1 & num2); //0
printf("%d\n", num1 | num2); //3
printf("%d\n", num1 ^ num2); //3
return 0;
}
异或性质:
cpp
//1.交换律
a ^ b = b ^ a
//2.结合律
a ^ b ^ c = a ^ (b ^ c)
//3.一个数和本身异或等于0, 和0异或等于本身
a ^ a = 0
a ^ 0 = a
//4.一堆数异或在一起,异或顺序可以随便变
面试题:不创建临时变量,实现两个数交换:
解法一:使用加减法,问题是 a + b 有溢出风险
cpp
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("交换前: a = %d, b = %d\n", a, b); //a = 10, b = 20
a = a + b; //a是和
b = a - b; //把a赋值给b
a = a - b; //把b赋值给a
printf("交换后: a = %d, b = %d\n", a, b); //b = 10, a = 20
return 0;
}
解法二:使用异或运算符,问题是只能用于交换两个整数哦
cpp
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
printf("交换前: a = %d, b = %d\n", a, b); //a = 10, b = 20
a = a ^ b;
b = a ^ b; //a ^ b ^ b = a, 本质是将 a 赋值给 b
a = a ^ b; //a ^ b ^ a = b, 本质是将 b 赋值给 a
printf("交换后: a = %d, b = %d\n", a, b); //b = 10, a = 20
return 0;
}
**练习:**编写代码实现:求一个整数存储在内存中的二进制中1的个数
方法一:
cpp
#include <stdio.h>
int main()
{
int x = 10;
int cnt = 0;
while (x)
{
if (x % 2 == 1)
cnt++;
x /= 2;
}
printf("%d\n", cnt); //2
return 0;
}
这段代码是有问题的,是可能无法正确处理负数的情况
方法二:
cpp
#include <stdio.h>
int main()
{
int x = 10;
int cnt = 0;
for(int i = 0; i < 32; i++)
{
if (x & (1 << i))
cnt++;
}
return 0;
}
方法三:
cpp
#include <stdio.h>
int main()
{
int x = 10;
int cnt = 0;
while (x)
{
x = x & (x - 1); //每次让x的二进制位少一个1
cnt++;
}
printf("%d\n", cnt);
return 0;
}
赋值操作符
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
cpp
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值
我们也可以进行连续赋值,但不推荐,因为分开写更加通俗易懂,易于调试
cpp
int a = 10;
int x = 0;
int y = 20;
//连续赋值
a = x = y + 1;//连续赋值
//不连续赋值
x = y + 1;
a = x;
复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的形式,写起来更加简洁
cpp
int a = 10;
a = a + 20;
a += 20; //复合写法
单目操作符
! 逻辑反操作
- 负值
- 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
!逻辑反操作:真(非0)变假(0),假(0)变真(默认是1)
cpp
#include <stdio.h>
int main()
{
int a = 10, b = 0;
printf("%d\n", !a); //0
printf("%d\n", !b); //1
return 0;
}
前置++,后置++,前置--,后置--
cpp
#include <stdio.h>
int main()
{
int a = 10;
int aa = ++a; //前置++, 先++,后使用(此处指赋值)
printf("%d\n", aa); //11
int b = 10;
int bb = b++; //后置++, 先使用(此处指赋值), 后++
printf("%d\n", bb); //10
//--也是一样的道理
return 0;
}
关系操作符
>
>=
<
<=
!=
==
注意: = 和 == 不要搞混淆了
逻辑操作符
&& 逻辑与:都真才真,一假则假
|| 逻辑或: 都假才假,一真则真
cpp
int a = 10, b = 20;
if (a == 5 && b == 20) {} //假
if (a == 10 && b == 20) {} //真
if (a == 5 || b == 20) {} //真
if (a == 5 || b == 8) {} //假
考点: 逻辑操作符的短路特性
a && b:当a表达式为假,那么整个结果就是假的,b表达式不会再执行
a || b:当a表达式为真,那么整个结果就是真的,b表达式不会再执行
面试题: 考察前后置++以及逻辑操作符的短路特性
cpp
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); //a = 1, b = 2, c = 3, d = 4
return 0;
}
cpp
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); //a = 1, b = 3, c = 3, d = 4
return 0;
}
条件操作符
exp1 ? exp2 : exp3
exp1成立,执行exp2,exp1不成立,则执行exp3
cpp
int a = 5;
if (a > 3)
a = -1;
else
a = 0;
//将if-else转化成条件表达式
a = a > 3 ? -1 : 0;
eg:条件操作符求两个数最大值
cpp
int get_max(int a, int b)
{
return a > b ? a : b;
}
逗号表达式
exp1,exp2 ,exp3,exp4.....expN
逗号表达式,就是逗号隔开的多个表达式,从左向右执行,整个表达式的结果是最后一个表达式的结果
cpp
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("a = %d, b = %d, c = %d\n", a, b, c); //a = 12, b = 13, c = 13;
}
下标引用、函数调用和结构成员
\]:下标引用操作符 ( ):函数调用操作符 . 或 -\> :访问结构体成员
cpp
#include <stdio.h>
void test1() {}
void test2(const char* str) {}
struct stu
{
int age;
char* name;
};
int main()
{
int a[5] = {1, 2, 3, 4, 5};
a[3] = 40; //下标访问操作符
//()函数调用操作符, 操作数是函数名和函数参数
test1();
test2("hello world");
struct stu s;
s.age; //结构体变量.结构体成员
struct stu* ps;
ps->name; //结构体指针->结构体成员
return 0;
}
隐式类型转化
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算
例如,要计算a + b,由于a和b都是char类型,不足4字节,先要提升为int,然后相加得到一个int值,由于要存放到 char 类型的 c 变量里,要发生截断
cpp
char a, b, c;
c = a + b;
如何进行整形提升呢?
整形提升是按照变量的数据类型的符号位来提升的!
负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
无符号整形提升,高位补0
整形提升的例子:
eg1: 由于a和b都不足一个int,因此在判断相等时会整形提升,a的二进制和c的二进制最高位都是1,因此整形提升前面都补1,最后得到的值和原先值都不一样,所以程序最后打印c
cpp
#include <stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a");
if (b == 0xb600)
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
eg2: 只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节
cpp
#include <stdio.h>
int main()
{
char c = 1;
printf("%u\n", sizeof(c)); //1
printf("%u\n", sizeof(+c)); //4
printf("%u\n", sizeof(-c)); //4
return 0;
}
算术提升
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面的列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
警告:算术转换要合理,否则会有一些潜在的问题
cpp
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
操作符的属性
复杂表达式的求值有三个影响因素:
-
操作符的优先级
-
操作符的结合性
-
是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
注意:
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就存在问题!!!