一 ,操作符的分类
1.算术操作符:+ ,-, * ,/ ,%
2.移位操作符:<< >>
3.位操作符 : & | ^ ~
4.赋值操作符: = ,+= ,-= ,*= ,/= ,%= ,<<= , >>= ,&= ,|= ,^=
5.单目操作符:! , ++ , --, - , + , * , & , ~ ,sizeof(类型)
6.关系操作符:> , < , >= ,<= , == , !=
7.逻辑操作符:&& || !
8.条件操作符:? :
9.逗号表达式: ,
10.下标引用 : [ ]
11.函数调用 : ( )
12.结构成员访问: . , ->
二 ,二级制和进制转化
在日常生活中,我们常常听到不同的进制,时钟有60进制,12进制,每周的7进制。其实无论是多少进制,都是数值的不同表示方法:
比方说:数值15的各种进制表现形式:
15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F
// 16 进制的数值之前写: 0x
// 8进制的数值之前写 :0
以我们日常生活中常见的10进制做类比:
- 10进制满10进1
- 10进制的每一位数值是0-9组成的
推理到2进制:
- 2进制满2进1
- 2进制的每一位数值都是0-1组成的
2.1 二进制转十进制
在10进制中,520表示的是五百二十,是因为10进制中的每一位数都是有权重的,这里的520=0*10^0 + 2*10^1 + 5*10^2;
这与二进制也是类似的,只不过权重不同:
这里以二进制1101来举例:
2.2 10进制转二进制
2.3 2进制转8进制
8进制的数字每一位都是0-7的,然后0-7的数字写成2进制,最多需要3位二进制数就足够了,比方说7的二进制数是111,所以在2进制数转成8进制数的时候,从最右边的最低为开始选3个数为一组来计算即可,如果不够3位,前补0或者直接换算;
2.4 2进制数转16进制数
16进制的每一位是0-9,a-f,然后把它们写成二进制的数,最多需要4位二进制数即可;
注意16进制表示时,前面加0x,所以二进制数 01101011 的16进制形式为 0x6b;
三 ,原码,补码,反码
整数中的2进制表示方法有三种,即原码,反码,补码;
有符号整数的三种表示方法均有符号位和数值为两部分,2进制序列中,最高位的1位被当作符号位,其余的都是数值为。
符号位中以0表示“正”,以1表示”负“;
四 , 移位操作符
<< 左移操作符
>> 右移操作符
注意: 操作数只能是整数
4.1 左移操作符
移位规制:左边抛弃,右边补0 ---->进行运算的是数值的二进制(内存中的补码)
正数:
int main()
{
int n = 10;
int m = n << 1;
printf("%d\n", n);
printf("%d\n", m);
return 0;
}
负数:
int main()
{
int a = -10;
int b = a << 1;
int c = a << 2;
//10000000 00000000 00000000 00001010
//补码:
//11111111 11111111 11111111 11110110
//左移:
//11111111 11111111 11111111 11101100 --- b在内存中的补码
//10000000 00000000 00000000 00010100
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
return 0;
}
我们不难发现:左移运算符在一定程度上(并不是都是),每次左移一位,数值会在原有的基础上乘以2;
需要注意的是:负数要转化成补码进行移动,最后在转化原码得到移动结果。
4.2 右移操作符
移动规则: 右移运算分为两种
1.逻辑右移:左边用0填充,右边丢弃
2.算数右移:左边用原来该数值的符号位填充,右边丢弃
右移是逻辑右移还是算数右移是取决于编译器的,但大多数的编译器采用的是算数右移。
int main()
{
int a = -10;
int b = a >> 1;
int c = a >> 2;
int d = a >> 3;
//10000000 00000000 00000000 00001010
//11111111 11111111 11111111 11110110 --- 补码
//11111111 11111111 11111111 11111011
//10000000 00000000 00000000 00000101 -- -5
//右移到底是算数右移还是逻辑右移,是取决于编译器,但是大部分编译其采用的都是算术右移
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
printf("%d\n", d);
return 0;
}
注意:对于移位符号位,不要移动负数位,这个标准是未定义的。
五 ,位操作符
& 按位与
| 按位或
^ 按位异或
~ 按位非
注意:他们的操作数必须是整数
int main()
{
int a = -5;
int b = 13;
int c = a & b;
int d = a | b;
int e = a ^ b;
int f = ~a;
//a:10000000 00000000 00000000 00000101
//a:11111111 11111111 11111111 11111011 -5补码
//b:00000000 00000000 00000000 00001101 13补码
// 00000000 00000000 00000000 00001001 & 9
// 11111111 11111111 11111111 11111111 |
// 10000000 00000000 00000000 00000001
// 11111111 11111111 11111111 11110110 ^
// 10000000 00000000 00000000 00001010
// 00000000 00000000 00000000 00000100 ~
printf("%d\n", c);
printf("%d\n", d);
printf("%d\n", e);
printf("%d\n", f);
return 0;
}
试着完成:
题目:不能创建临时变量(第三个变量),从而实现两个整数交换;
如果我们使用第三个变量就很容易实现,就像现在你有一瓶酱油和一瓶醋,我们可以通过一个空瓶子把这两个容器里的液体交换;
int main()
{
int a = 10;
int b = 20;
int c = 0;
printf("交换前:%d %d\n", a, b);
c = a;
a = b;
b = c;
printf("交换后:%d %d\n", a, b);
return 0;
}
但是题目要求不可以用第三个变量,但我们可以先把两个数相加,然后赋值给a,然后再用赋值后的a减去b 实则上就是我们原来的a值,然后把这个值赋给 b ,再用a值减去 b ,就是我们原来的 b 值,然后赋值给a ,即可实现交换;
int main()
{
int a = 10;
int b = 20;
printf("交换前:%d %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:%d %d\n", a, b);
return 0;
}
这样计算,当数值较小的时候的确可以实现我们的数值交换,但是当数值越来越大的时候,两个数值进位相加,可能会超出整数表达的最大值,可能会存在溢出问题!
所以我们再次优化这个算法:
a^a == 0
a^0 ==a
异或:相异为1,相同为0
两个二进制数没有进位运算,不会导致溢出!
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b; //a ^ b^ b; ----> a
a = a ^ b; //a ^ b ^ a ---->b
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
练习1:
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
while (n)
{
if (n % 2 == 1) {
count++;
}
n /= 2;
}
printf("二进制中1的个数为%d\n", count);
return 0;
}
但是我们很快就可以发现,针对负数是有问题的;
然后我们,就会想,什么运算会不丢失原有而二进制的数值,然后又可以方便计算的,最后,我们可以想到按位与(&),让每一个二进制数按位与1 ;
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
for (int i = 0; i < 32; i++)
{
if ((1 & (n >> i)) == 1)
{
count++;
}
}
printf("%d\n", count);
return 0;
}
但是这里我们计算了32次,然后我们思考,能不能再优化一下:
int main()
{
int n = 0;
scanf("%d", &n);
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
printf("%d\n", count);
return 0;
}
练习二:
题目:写一个代码判断 n 是否是2的次方数
2次方数有:1 2 4 8 16 32 64 .....
我们把他们转成二进制数:
00000001
00000010
00000100
00001000
00010000
................
我们发现他们有且仅有一个1
int main()
{
int n = 0;
scanf("%d", &n);
if ((n & (n - 1)) == 0)
{
printf("Yes!\n");
}
else
{
printf("No!\n");
}
return 0;
}
案例3:
题目:编写代码将13⼆进制序列的第5位修改为1,然后再改回0
13 的 2 进制序列: 00000000000000000000000000001101将第 5 位置为 1 后: 00000000000000000000000000011101将第 5 位再置为 0 : 00000000000000000000000000001101
int main()
{
int n = 13;
n |= (1 << (5 - 1));
//00000000 00000000 00000000 00001101
printf("%d\n", n);
//00000000 00000000 00000000 00001101 --13
//00000000 00000000 00000000 00010000 |
// 1 <<4
//00000000 00000000 00000000 00011101
//11111111 11111111 11111111 11101111 &
//00000000 00000000 00000000 00001101
n &= (~(1 << (5 - 1)));
printf("%d\n", n);
return 0;
}
六 ,单目操作符
!、++、--、&、*、+、-、~ 、sizeof、(类型)
七 , 逗号表达式
1.以逗号依次隔开的多个表达式
2.从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
//代码2
if (a =b + 1, c=a / 2, d > 0)
//代码3
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
//...
a = get_val();
count_val(a);
}
如果使⽤逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}
八 , 下标访问[],函数调用()
1.下标引用操作符[]: 操作数:一个数组名+一个索引值
int arr[ 10 ]; // 创建数组arr[ 9 ] = 10 ; // 实⽤下标引⽤操作符。[ ] 的两个操作数是 arr 和 9 。
2.函数调用():
# include <stdio.h>void test1 (){printf ( "hehe\n" );}void test2 ( const char *str){printf ( "%s\n" , str);}int main (){test1(); // 这⾥的 () 就是作为函数调⽤操作符。test2( "hello bit." ); // 这⾥的 () 就是函数调⽤操作符。return 0 ;}
九,操作符的属性:优先级,结合性
C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。
十, 表达式求值
1.整型提升:
int main()
{
char a = 5;
//00000000 00000000 00000000 00000101
//00000101 截断
char b = 126;
//00000000 00000000 00000000 01111110
//01111110
char c = a + b;
//00000101 --- 发生整型提升 --00000000 00000000 00000000 00000101
//01111110 --- 发生整型提升 --00000000 00000000 00000000 01111110
//char --- signed char
//00000000 00000000 00000000 10000011 --- 截断 -- 10000011
//当以%d 形式进行打印的时侯,打印的是有符号整数
//又会对c发生整形提升
//11111111 11111111 11111111 10000011 -- 内存中的补码
//10000000 00000000 00000000 01111101
printf("%d\n", c);
return 0;
}
如何进⾏整体提升呢?
1. 有符号整数提升是按照变量的数据类型的符号位来提升的2. 无 符号整数提升,高位补0
2.算数转换:
long doubledoublefloatunsigned long intlong intunsigned intint