目录
一.操作符的分类
1.算术操作符:+ – * / %
1.+ 和 –
2. *
3./
4.%
2.移位操作符
3.位操作符
4.赋值操作符
1.连续赋值
2.复合赋值
5.单目操作符
1.++和–
(1)前置++
(2)后置++
(3)前置–
(4)后置–
2.+和-
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号表达式
10.下标引用
11.函数调用
12.结构成员访问
二.进制转换
1.进制的介绍
1.1数字系统
1.2十进制
1.3二进制
1.4八进制
1.5十六进制
2.连除法
2.1十进制转化为二进制
2.2十进制转为八进制
2.3十进制转化为十六进制
3.二进制|八进制|十六进制的互相转换
3.1二进制数字转化为八进制
3.2二进制数字转化为十六进制
4.注意
三.原码 反码 补码
四.移位操作符
1.左移位操作符
2.右移位操作符
3.不能移动负数
五.位操作符
1.按位与:&
2.按位或:|
3.按位异或:^
4.按位取反:~
5.例题:不创建临时变量,实现两个数的交换;
方法一:
方法2:
6.例题:求一个整数储存在内存中的二进制中的1的个数;
一.操作符的分类
注:操作符也叫:运算符
1.算术操作符:+ – * / %
1.+ 和 –
运算符+和-用于完成加法和减法。
+和-都是有两个操作数的,位于操作符两端的就是他们的操作数,这种操作符也叫双目操作符
#includeint main(){int x = 1 + 2;int y = 2 - 1;printf("%d\n",x);printf("%d\n",y);return 0;}
2. *
运算符*用于完成乘法
#includeint main(){int a = 2;printf("%d\n",a * a);return 0;}
3./
运算符/用于完成除法
1.除号的两端如果都是整数 ,执行的是整数除法,得到的结果也是整数。
2.除号的两端如果存在浮点数,执行的是浮点数除法
例1:
#includeint main(){int x = 6 / 4;float y = 6 / 4;printf("%d %f\n", x, y);return 0;}
这段代码的结果是1和1.000000
虽然y是float的类型的变量,但是6/4执行的仍然是整数除法,整数除法只会返回整数部分,丢弃小数部分,所以结果是1.000000
如果我们想要得到浮点数的结果,两个操作数至少有一个是浮点数。
比如:
例2
#includeintmain(){float x = 6/4.0;printf("%f\n",x);return 0;}
这样结果就变成了1.500000
例3:
#includeint main(){int score = 5;score = (score/20) * 100;return 0;}
注意这串代码的结果是0而不是25.因为score/20执行整数除法结果是0,所以最终的答案也是0;
4.%
运算符%是表示求模运算的,即返回两个整数相除的余数,注意必须是整数,不能用于浮点数。
#includeint main(){int x = 6 % 4;printf("%d\n",x);return 0;}
结果是2
如果是负数求模的时候,结果的正负是由第一个运算符决定的。
#includeint main(){printf("%d\n",6%4); printf("%d\n",6%-4);printf("%d\n",-6%4);return 0;}
结果是2 2 -2。
第一个运算符是正,结果就为正,第一个运算符是负,结果就为负。
2.移位操作符
3.位操作符
4.赋值操作符
在创建变量的时候,比如:
int x = 1;//初始化x = 6;//赋值
我们给x初始化了一个值这叫初始化,如果在变量创建好了后,在给变量一个值,这叫赋值。
赋值操作符=是一个可以随时给变量赋值的操作符。
1.连续赋值
赋值操作符可以连续赋值
int x = 1;int y = 2;int z = 3;z = y + 1 = x + 2;
连续赋值的时候是从右到左连续赋值。
虽然可以这样写代码,但是可读性太低了,建议分开写,方便观察。
2.复合赋值
当我们对一个变量进行自增和自减的时候,我们可以简化代码
int a = 1a = a + 1;// a = a - 1;//或者a += 1;//a -= 1;
这两种写法效果是一样的
5.单目操作符
种类:!、++、–、 & 、 * 、 + 、 – 、~、sizeof、(类型)
注:sizeof也是操作符
在C语言中一些操作符只有一个操作数的,被称为单目操作符。
数据类型必须要加括号
1.++和–
++是一种自增操作符,根据放的位置不同,分为前置++和后置–。
(1)前置++
结论:先+1,再使用。
int a = 10;int b = ++a;
++就是加1.a原来是10,先++就变成了11,然后赋值给b,b就是11了.
(2)后置++
结论:先使用,再+1。
int a = 10;int b = a++;
a原本是10,这里先把a赋值给b,b就变成了10,然后a++,a就变成了11;
–和++类似,只不过++是加1,–是减1;
(3)前置–
结论:先-1,再使用。
int a = 10;int b = --a;//a = b = 9;
(4)后置–
结论:先使用,再-1。
int a = 10;int b = a--;// b = 10 , a = 9
2.+和-
这里的+和-是正号和负号的意思。都是单目操作符。
运算符+对正负值没用影响,可以省略不写。
inta = +1;等价于 int a = 1;
运算符-对数有影响,不能省略。
6.关系操作符
7.逻辑操作符
8.条件操作符
9.逗号表达式
10.下标引用
11.函数调用
12.结构成员访问
二.进制转换
1.进制的介绍
1.1数字系统
数字系统分为三种
远古时代 | 结绳记事/石板刻线 |
非位置化数字系统 | 罗马数字 |
数字化位置系统 | 二进制 | 八进制 | 十进制 | 十六进制 |
远古时代:在很久以前,人们不会数12345,所以当人需要对某些事情进行计数的时候,比如:上次下雨过了几天等等这类的问题,人们就只能使用结绳记事或者在石板上刻一根一根的线这样的方式进行记录.可是这样的计数方式弊端很大,数字越大越不好数.
为了解决上述的问题,人们想到了更加便捷的技术方法.那就是使用不同的符号来表示不同的数字
比如我们所使用的数字 111虽然使用了三次数字1,但是这三个数字1的含义却不相同,比如个位的1,表示的是1,十位的1表示的是10,百位的1表示的是100,同一个数字,在不同的位置,有着不同的含义,这就是位置化数字系统.
而在非位置化数字系统中,同样的符号只会表达相同的数字不会根据所处的不同位置而表示出不一样的数量,比如罗马数字,在VVV中的每一个V表示的都是5,VVV表示的就是5+5+5等于15.
我们常用的是位置化数字系统,因为人类有10根手指,我们就很自然的使用了十进制数字系统.但是计算机有所不同,其使用二进制.
1.2十进制
用10个可用符号表示一个数字:
1 2 3 4 5 6 7 8 9
比如十进制数字123
百位 | 十位 | 个位 | ||
权重 | 100/10^2 | 10/10^1 | 1/10^0 | |
数字 | 1 | 2 | 3 | |
权重值 | 1*10^2 | 2*10^1 | 3*10^0 | |
结果(10进制) | 100 | 20 | 3 | 123 |
1.3二进制
用两个可用符号来表示一个数字:
0 1
比如二进制数字1011
八位 | 四位 | 二位 | 个位 | ||
权重 | 8/2^3 | 4/2^2 | 2/2^1 | 1/2^0 | |
数字 | 1 | 0 | 1 | 1 | |
权重值 | 1*2^3 | 0*2^2 | 1*2^1 | 1*2^0 | |
结果(10进制) | 8 | 0 | 2 | 1 | 11(10进制) |
在十进制中,10的0次方等于1,所以第一位是个位,10的一次方等于10,所以第二位是十位,10的三次方等于100,所第三位是百位.
同理可得,在二进制中,2的0次方等于1,所以第一位是个位,第2位是二位,第3位是四位….八位.十六位等等.
1.4八进制
用8个可用符号来表示一个数字:
0 1 2 3 4 5 6 7
观察八进制数字 277
数字 | 2 | 7 | 7 | |
权重值 | 2*8^2 | 7*8^1 | 7*8^0 | |
结果(10进制) | 128 | 56 | 7 | 191(10进制) |
1.5十六进制
用16个可用符号来表示一个数字:
十六进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
观察十六进制数字2AE
数字 | 2 | A | E | |
权重值 | 2*16^2 | 10*16^1 | 14*16^0 | |
结果 | 512 | 160 | 14 | 686(10进制) |
以上是关于进制的讲解和其他进制的数字转化为十进制的数.
那么如何将其他进制的数转化为10进制呢
2.连除法
2.1十进制转化为二进制
举例:十进制数字29
商 | 余数 | |
进行整数除法(除以2) | 29 | |
除以2 | 14 | 1 |
除以2 | 7 | 0 |
除以2 | 3 | 1 |
除以2 | 1 | 1 |
除以2 | 0 | 1 |
不断进行除二直到被除数为0的时候结束
然后我们将所得到的余数从下往上进行排列 所得到的数字就是改数字的二进制形式
所以29(10进制)—->11101(2进制)
当然我们也可以反向检测一下答案对不对.
2.2十进制转为八进制
举例十进制数字900
商 | 余数 | |
除以8 | 900 | |
除以8 | 112 | 4 |
除以8 | 14 | 0 |
除以8 | 1 | 6 |
除以8 | 0 | 1 |
所以 900(10进制)—->1604(八进制)
2.3十进制转化为十六进制
举例数字2717
商 | 余数 | |
除以16 | 2717 | |
除以16 | 169 | 13 |
除以16 | 10 | 9 |
除以16 | 0 | 10 |
根据十进制与十六进制表格将对应的数字转化为符号
我们就得到了2717(10进制)—->A9D(16进制)
3.二进制|八进制|十六进制的互相转换
1.由于2^3 = 8,所以每三位二进制可以转化为1位八进制
2.由于2^4=16,所以每4位二进制可以转化为1位十六进制
如果不觉得麻烦可以先转化为十进制,再将这个十进制转化为你想要的数字,这里我们介绍另一种方法.
3.1二进制数字转化为八进制
举例 10111001(二)
我们从后面开始,依次选取3个数字,首先是001,然后是111,最后是10
二进制 | 10 | 111 | 001 |
含义(十进制) | 1*2^1+0*2^0 | 1*2^2+1*2^1+1*2^0 | 0*2^2+0*2^1+1*2^0 |
对应的十进制分别是 | 2 | 7 | 1 |
然后我们将所得的结果依次排列,就得到了八进制数字271(八进制),这就是二进制数字10111001的八进制形式.如果不确定,可以反向验证一下.
3.2二进制数字转化为十六进制
还是这个数字 10111001(二)
我们从后往前依次选取四个数字,首先是1001,然后1011.
二进制 | 1011 | 1001 |
含义(十进制) | 1*2^3+0*2^2+1*2^1+1*2^0 | 1*2^3+0*2^2+0*2^1+1*2^0 |
对应的十进制数字分别是 | 11 | 9 |
十六进制符号 | B | 9 |
所以我们得到了10111001(二进制)的十六进制数字是B9(十六).
4.注意
在计算机中八进制表示的时候,前面要加上0
十六进制表示的时候,前面要加上0x
三.原码 反码 补码
整数的二进制表达方式有三种方式,即原码.反码.补码.
这三种表示方式都是用二进制位表示数字,但有不同的规则来表示正负号。
1.原码:最高位表示符号位,0表示正数,1表示负数,其余位表示数值的大小.比如:+5的原码是00000101,-5的原码是10000101。
2.反码:正数的反码和原码相同,负数的反码就是将原码中的除符号位以外的所有位按位取反即可得到反码.比如:+5的反码就是00000101,-5的反码就是11111010.
3.补码:正数的补码与原码也相同,而负数的反码加一就可以得到补码,比如,+5的补码就是00000101,-5的补码就是11111011.
总结:
1.正整数的原码反码补码都相同
2.负整数的三种表示方式都不相同.
对于整形来说,数据存放在内存中的都是补码。
四.移位操作符
<<左移位操作符
>>右移位操作符
注意移位操作符的操作符只能是整数。
1.左移位操作符
移位规则:左边抛弃,右边补0
#includeint main(){int num = 10;int n = num << 1;printf("num = %d\n",num);printf("n = %d\n",n);return 0;}
结果是 10 和 20,说明<<移位操作符不会对num本身造成影响。
结果分析:
//10的2进制:原码 = 反码 = 补码:00000000000000000000000000001010//移位后 000000000000000000000000000010100最左边的0被抛弃结果就是00000000000000000000000000010100所以结果是2的四次方加2的二次方=20
图示:
2.右移位操作符
移位规则:1.逻辑右移:右边抛弃,左边补0
2.算术右移:右边抛弃,左边按照原符号位进行补充。
#includeint main(){int num = -1;int n = num >> 1;printf("num = %d\n",num);printf("n = %d\n",n);return 0;}
结果是-1和-1
过程分析:
//10000000000000000000000000000001--原码//11111111111111111111111111111110--反码//11111111111111111111111111111111--补码// 逻辑右移//01111111111111111111111111111111补//01111111111111111111111111111111反//01111111111111111111111111111111原// 太难算了//算术右移//11111111111111111111111111111111补//11111111111111111111111111111110反//10000000000000000000000000000001原//-1
在内存存放的是补码,我们移动后仍然是补码,需要换成原码,
在我使用的vs2022编译器上采用的算术右移(大多编译器都是算术右移)
3.不能移动负数
对于移位运算符来说,不要移动负数位。这是为定义的。
int num = 1;num >> -1;
这个代码是错误的,因为这种标准未定义。
五.位操作符
位操作符有四种:
1 | 按位与 | & |
2 | 按位或 | | |
3 | 按位异或 | ^ |
4 | 按位取反 | ~ |
注:位操作符的操作数必须是整数。
#includeint main(){int num1 = -3;//1000 0011--原//1111 1100--反//1111 1101--补int num2 = 5;//0000 0101 --原 -反 - 补printf("%d\n", num1 & num2);printf("%d\n", num1 | num2);printf("%d\n", num1 ^ num2);printf("%d\n", ~0);return 0;}
1.按位与:&
规则: 有0为0,无0为1;
int num1 = -3;//1111 1101--补int num2 = 5;//0000 0101 --原 -反 - 补printf("%d\n", num1 & num2);1111 11010000 0101
根据上述规则,我们可以知道两个数字按位与后补码的结果为0000 0101,与num2的补码相同,所以就是5.
2.按位或:|
规则:有1为1,无1为0
int num1 = -3;//1111 1101--补int num2 = 5;//0000 0101 --原 -反 - 补printf("%d\n", num1 | num2);1111 11010000 0101
所以我们得到的结果是1111 1101,所以会打印-3.
3.按位异或:^
规则:相同为0,相异为1;
int num1 = -3;//1111 1101--补int num2 = 5;//0000 0101 --原 -反 - 补printf("%d\n", num1 ^ num2);1111 11010000 0101
所以结果是1111 1000;因为符号位是1,所以是负数,最终补码,1000 1000,结果是-8;
4.按位取反:~
//0//0000 0000 原反补printf("%d",~0);取反后结果是1111 1111;
换算成原码结果是-1,-1的补码全是1,这个需要记住。
5.例题:不创建临时变量,实现两个数的交换;
如果可以创建临时变量:
int main(){int a = 1;int b = 2;int tmp = a;a = b;b = tmp;printf("%d %d",a,b);return 0;}
但是题目给了限制条件,所以不能这样做
方法一:
int main(){int a = 1;int b = 2;a = a + b;//先将a + b赋值给a;这样没有临时变量,但是也可以起到临时变量的作用b = a - b;//这里的b实际等于a + b -b;也就是把a赋给了b;a = a - b;//这里的a实际等于a + b - a;这样就实现了互换;printf("%d %d", a, b);return 0;}
弊端:当a和b的值很大时,可能会超过整形的最大值,出现越界的情况。
方法2:
这里就需要用到位操作符:按位异或
首先我们需要深入了解一下按位异或的其他特点:
1:
a ^ a = 0;a ^ 0 = a;
a和a补码相同,所以按位异或后结果就是0,而a和0按位异或,二进制位上是0的数还是0,是1的数还是1,不会发生变化;
2:交换律
a ^ b = b ^ a;a ^ b ^ a = a ^ a ^ b;
第一个很好理解,而第二种我们可以尝试举例来证明;这里不过多解释;
3.结合律:
(a ^ b) ^ c = a ^ (b ^ c);
这些性质都和我们学的加减法类似;
回到上面那道题:
代码:
int main(){int a = 1;int b = 2;a = a ^ b;//先将a^b的结果赋给a;b = a ^ b;//所以这里实际上是b = a ^ b ^ b;//b ^ b = 0;0 ^ a = a;// 这样就把a赋给了b;a = a ^ b;//a = a ^ a ^ b;//a ^ a = 0; 0 ^ b = b;//这样就把b的值赋值给了a;printf("%d %d",a,b);return 0;}
6.例题:求一个整数储存在内存中的二进制中的1的个数;
方法一:
int main(){int num = 10;int count = 0;//计数while (num){if (num % 2 == 1) {//这里取余数可以理解为二进制中第一位的数是不是1;如果是那么计数就加1;count++;}num = num / 2;//在我们学习移位操作的时,我们知道右移操作符起到了除2的效果;这样我们就去除了第一位的数,}printf("⼆进制中1的个数 = %d\n", count);return 0;}
在我们验证的过程中我们可以发现:正数都是对的,而负数出现了错误,比如-1的二进制补码全是1,但是我们得到的结果(32位环境下)却不是32,
因为-1%2结果是-1,count不会增加,而-1 / 2后,结果变成了0;就不会再进入循环了,所以是0;但是实际上我们知道这是不对的;
方法2:
int main(){int num = -1;int i = 0;int count = 0;for (i = 0; i < 32; i++){if (num & (1<<i))//如果括号中的结果不是0;那么证明这一位二进制位是1;随着i的增大,可以判断每一个二进制位;{count++;}}printf("二进制中1的个数 = %d\n", count);return 0;}
弊端: 这个方法中必须循环32次,(X64环境下就要64次),效率不高,还可以进行优化;
方法3:
int main(){int num = -1;int i = 0;int count = 0;while (num){count++;num = num & (num - 1);//此时的num中二进制最低位数的1,已经变成了0;}printf("二进制中1的个数 = %d\n", count);return 0;}