目录
一、算数操作符
二、移位操作符
1.左移操作符
2.右移操作符
(1) 逻辑右移
(2) 算术右移
(3)小总结
三、位操作符
四、赋值操作符
五、单目操作符
六、关系操作符
七、逻辑操作符
八、 条件操作符
九、逗号表达式
十、下标引用、函数调用和结构成员
1. [ ]下标引用操作符
2. ( )函数调用操作符
3. 访问一个结构的成员
十一、表达式求值
1.隐式类型转换
2.算术转换
3.操作符的属性
十二、操作符优先级
+ - * / %
>> 右移操作符
<< 左移操作符
注意:移位操作符的操作数只能是整数
规则巧记:左边抛弃,右边补0
这里假设存的是 int n =15;的二进制 进行左移 n<<1; 如果n 在没有被左移赋值的情况下,n自身的值是不会发生变化。(这里与自增自减不一样)
在深层次看一下,一个数 n<<1, 发现相当于该数 n * 2^1。n << 2; 等于 n * 2^2
移位规则:右边舍弃,左边补0
假设内存 存放的是 -1 补码的二进制
移位规则:右边舍弃,左边用原值的符号位进行填充
如果 算术右移与 逻辑右移 总是分不清。巧记:(算术右移,可以想象成 算数,既然算数肯定会有正负,进而想到 左边填充的是原符号位)
注意:对于位操作符,不存在移动负数位,C语言标准并未规定
int num = 10;
num>> -1; //error(不要多次一举)
num << 1; // ok 右移 -1 这不相当于 左移 1 嘛。
& //按位与 巧记:有0则0,其中 一个数 a&1 可求 该数的每一个二进制位
| //按位或 巧记:有1则1
^ //按位异或 巧记:相同为0,相异为1
注:操作数必须是整数
【例】1
#include
int main() { int a = 1; //0001 int b = 2; //0010 /* * a& b; * 0001 * 0010 * 0000 * * a|b * 0001 * 0010 * 0011 * * a^b * 0001 * 0010 * 1100 */ return 0; }
接下来看一道面试题
【例】2 不能创建临时变量(第三变量),实现两个数的交换
解法一:
两个数进行来回加减来进行两个数的交换,通过调试的监视的窗口我们可以看到两个数的交换
解法二:
#include
int main()
{
int a = 10;
int b = 20;
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d",a,b);
return 0;
}
从打印结果可以看到a 与 b的值进行了交换
【解析】^ 按位异或 相同为0,相异为1(可以理解为 相同假,相异为真)
前面,提到 可以用a&1 该数的每一个二进制位,看下方例题
【例】编写代码,求一个整数存储在内存中的二进制中1的个数
#include
int main()
{
int n = 10;
int count = 0;
// 0000 1010
// 0000 0001
int i = 0;
for (i = 0; i < 32;i++)
{
if (n & 1 == 1)
{
count++;
}
n = n>>1;
}
printf("%d",count);
return 0;
}
这里用到了 >> 和 ^
解法二:
#include
int main()
{
int n = 10;
int count = 0;
while (n)
{
if (n%2 == 1)
{
count++;
}
n /= 2;
}
return 0;
}
这里的思路是 因为计算机存储是二进制 0 和1 ,%2取余判断是否为1,/2进行下一位
解法三:
#include
int main()
{
int n = -1;
int i = 0;
int count = 0;
while (n)
{
count++;
n = n & (n - 1);
//1111 1111 1111 1111 1111 1111 1111 1111
//1111 1111 1111 1111 1111 1111 1111 1110
//1111 1111 1111 1111 1111 1111 1111 1110
}
printf("%d ", count);
return 0;
}
这里的优化 是借助 两个数差1 进行按位与,一个一个位找1
= 这是赋值符,不是等于!!!
赋值操作符 给一个变量进行赋值 注意赋值操作符的优先级比较低(包括复合赋值符)
int age = 18;
age = 20; //对变量进行赋值
赋值操作符支持连续使用;
int a = 0;
int b = 10;
int c = 1;
a = b = c+1; //这里是连续赋值
虽然可以连续赋值,但是代码的可读会下降
b = c+1;
a = b;
//这样子是不是看着更用以理解
代码的可读性也是很重要的
接下来看复合赋值符都有哪些
+= 、-=、*= 、/= 、%= 、>>= 、<<= 、&= 、 |= 、^=
int a = 10;
a = a+10;
//可以写成这样
a += 10;
其他复合赋值符同上方用法一致
单目操作符就是操作数只有一个
sizeof ()括号里面不是类型时可以省略(),说到sizeof ,其中 size_t 是一种类型,是一种无符号整型的,size_t 就是为sizeof专门设计的一种类型,打印时使用%zd,但是size_t 在不同平台下可能是 unsigned int 也有可能是 unsigned long long int
sizeof(数组名) ,计算的是整个数组的大小
++前置,先对该数+1,然后再去使用这个数
++后置,相当于 先使用该数,然后在对该数进行 +1
-- 与上方的++同理
> 、 >= 、 < 、 <= 、!= 、==
注意:== 这个是 等于
= 这个是 赋值
&& 逻辑与
|| 逻辑或
要区分
& 与 && ; | 与 ||
1&2 ---> 0
1&&2 ---> 1 (左边为假,右边无需计算,直接为假)
1 | 2 ---> 3
1 || 2 ---> 1 (左边为真,右边无需计算,直接为真)
【例】1笔试题,求结果输出的值
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
【结果】a = 1 b = 2 c = 3 d = 4
解析 首先 a = 0 ,a++ ,是先使用后 +1,又有&& (左边为假,右边无需计算,直接为假)
打印的是 a = 1,其他值正常打印
【例】2 对这个题进行改编 a = 1
#include
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
【结果】 a = 2 b = 3 c = 3 d = 5
解析 a = 1时,左边为真,后面表达式继续进行计算,进而 a = 2, b = 3,c =3 ,d= 5
【例】3 对这个题进行改编 && 改为 || , 并求出i 的值
#include
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
【结果】 a = 1 b = 3 c = 3 d = 4 i = 1
解析 因为时逻辑或 (左边为真,右边无需计算,直接为真)虽然先使用 a 为假,但接下来的操作数 ++b , b = 3为真 进而后面无需计算。而 i 表达式里面有真 则 i = 1, 为真
表达式1 ?表达式2 :表达式3;
唯一 一个三目操作符
在这里 看一个 求最大值的代码
a>b?a:b; //中文解释 a>b 吗?大于就是a, 不大于就是b
【例】求三个数的最大值
#include
int main()
{
int a = 1;
int b = 2;
int c = 3;
int max = 0;
max = a > b ? a : b;
max = max > c ? max : c;
printf("%d",max);
return 0;
}
这里是使用两次条件操作符进行计算三个数中的最大值
表达式1 , 表达式2 ,... , 表达式n
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c 的结果是 13
逗号表达式可以对代码进行优化
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
//使用逗号表达式可以改写为
while (a = get_val(), count_val(a), a > 0)
{
//业务处理
}
注意 操作数为 一个数组名 + 一个索引值
int arr[20]; //创建数组
// [ ] 的操作数 是 arr 和 20
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1(); //()作为函数调用操作符。
test2("hello bit.");//()作为函数调用操作符。
return 0;
}
. 结构体.成员名
-> 结构体指针 -> 成员名
#include
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
表达式求值的顺序一部分是由操作符的 优先级 和 结合性 决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个(8bit)字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
//例
char a,b,c;
a = b + c;
在上述代码中,b和c的值被提升为普通整型(int),然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于a中
整型提升
整形提升是按照变量的数据类型的符号位来提升的
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
无符号整形提升,高位补0
【例】1
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;
}
【结果】c
实例的a,b要进行整型提升,但是c不需要整形提升a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真.
【a】1101 0110 整型提升1111 1111 1111 1111 1111 1111 1101 0110
【b】1101 0110 0000 0000 整型提升1111 1111 1111 1111 1101 0110 0000 0000
【例】2
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
【结果】1 4 4
实例2中的,c只要参与表达式运算,就会发生整型提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节
如果某个操作符的 各个操作数的类型不一样,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面 寻常算术转换
long double
double
unsigned long int
long int
unsigned int
int
向上转换 如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
注意: 算数转换要合理,否则可能会出现精度丢失
float pi = 3.14;
int num = f;//num = 3 精度丢失
复杂表达式的求值有三个影响的因素。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
点击下方链接查看
链接:操作符优先级
注意:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。