一、二进制和进制转换
在开始操作符的学习之前我们先学习一下进制的知识,因为操作符中会涉及到位移操作符(移动二进制的位)
我们常听到二进制、八进制、十进制、十六进制,这些都是什么意思呢?
例15为例
二进制:1111
八进制:17
十进制:15
十六进制:F
生活最常见的就是十进制,满十进一就是十进制的规则,同理二进制就是满二进一,八进制就是满八进一以此类推
1.进制的相互转换
1111和17两个表达在某种意义上就是表达一样的数字,分别用二进制和八进制去表示了十进制中的15,这样转换的前提是两者表达的数字大小一样,那在不同进制下的数字是怎么体现出其大小的呢?
权重
百位 | 十位 | 个位 | ||
十进制的位 | 1 | 2 | 3 | |
权重 | 10^2 | 10^1 | 10^0 | |
权重值 | 100 | 10 | 1 | |
求值 | 1*100 + 2*10 + 3*1 = | 123 |
同理在二进制下
二进制的位 | 1 | 1 | 0 | 1 | |
权重 | 2^3 | 2^2 | 2^1 | 2^0 | |
权重值 | 8 | 4 | 2 | 1 | |
求值 | 1*8 + 1*4 + 0*2 + 1*1 = | 13 |
1.十进制转二进制
例:十进制125转化为二进制是多少
125 / 2 = 62 余1
62 / 2 = 31 余0
31 / 2 = 15 余1
15 / 2 = 7 余1
7 / 2 = 3 余1
3 / 2 = 1 余1
1 / 2 = 0 余1
由下而上所得的余数就是10进制转换为2进制
所以十进制下125转换为二进制就是1111 1101
2.二进制转八进制和十六进制
二转八:
八进制数由0~7组成,我们知道7用二进制表示是111,也就是说,八进制中的每一位数最多只需要3位的二进制数来表示
八进制数0153转换为二进制数:
八进制数可分解成
0 1 5 3
对应的二进制就是
000 001 100 011
2进制 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 1 |
8进制 | 1 | 5 | 3 |
二转十六
和二转八一样的道理,十六进制数由0~9,a~f组成,最大的数为F也就是15,二进制表达为1111
也就是说十六进制中的一位数最多需要四位数的二进制数来表示
十六进制0x6b用二进制表示
十六进制数可分解成
6 b
对应的二进制就是
0110 1011
2进制 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 1 |
16进制 | 6 | b |
插入: %d打印十进制 %o打印八进制 %x打印十六进制
二、原码、反码、补码
一个整型数据在计算机中的二进制表达有三种方式
有符号整型(signed int)三种方式都有符号位和数值位,二进制中,最高位为符号位
1为正数、0为负数
*正整数的原码、反码、补码都一样
*负整数则三种均不同
原码:直接将数值按照正负数的形式翻译成⼆进制
反码:将原码的符号位不变,其他位依次按位取反
补码:反码+1
补码得到原码:取反,+1
注意:对于整型来说, 数据内存中存放的其实是补码
原因:
官方解释:
在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
如果看不懂也没事,下面的例子可以帮助大家
总的来说,补码和原码的计算得出结果并不一样
1-1
1+(-1)
00000000000000000000000000000001 ---> 1的原码
10000000000000000000000000000001 ---> -1的原码
10000000000000000000000000000010 -> -2
使用补码就可以
00000000000000000000000000000001 ---> 1的补码
10000000000000000000000000000001
11111111111111111111111111111110
11111111111111111111111111111111 ----> -1的补码
00000000000000000000000000000001 ----> 1的补码
相加:
00000000000000000000000000000000 ---> 0
三、操作符详解
1. 操作符的分类
• 算术操作符: + 、- 、* 、/ 、%
• 移位操作符: << >>
• 位操作符: & | ^ ~
• 赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
• 单⽬操作符: !、++、--、&、*、+、-、~ 、sizeof、(类型)
• 关系操作符: > 、>= 、< 、<= 、 == 、 !=
• 逻辑操作符: && 、||
• 条件操作符: ? :
• 逗号表达式: ,
• 下标引⽤: [ ]
• 函数调⽤: ( )
• 结构成员访问: . 、 ->
上诉有一部分想必大家已经很熟悉了,这次博主注意讲解之前没有涉及和遇见的内容
2.移位操作符: << >>
分为左移和右移
注意:操作数只能是整数
左移:左边抛弃,右边补0
#include<stdio.h>
int main() {
int num = 10;
int n = num << 1;
printf("%d\n",num);//二进制0000 0000 0000 0000 0000 0000 0000 1010
//0 (0)000 0000 0000 0000 0000 0000 0001 0100(左边舍去,右边补0 )
printf("%d\n", n);//0000 0000 0000 0000 0000 0000 0001 0100 ---->20
return 0;
}
输出
10
20
右移则有两种情况:
1.左边补0,右边丢弃
过于暴力,简单一想,先不说数字的变化过大,甚至连正负都被改变了
2.左边用原本是符号位补充,右边丢弃
#include<stdio.h>
int main() {
int num = 10;
int n = num >> 1;
printf("%d\n", num);//二进制0000 0000 0000 0000 0000 0000 0000 1010
// 0 0000 0000 0000 0000 0000 0000 0000 101(0)(左边用原本是符号位补充,右边丢弃)
printf("%d\n", n);// 0000 0000 0000 0000 0000 0000 0000 0101 ---->5
return 0;
}
警告:一定不要移到负数位,这个操作在C语言中是没被定义的
3.位操作符: & | ^ ~
1 & //按位与 有0则0,全1则1
2 | //按位或 有1则1,全0则0
3 ^ //按位异或 相同是1,不同是0
4 ~ //按位取反 符号位不变其他取反
注意:操作数只能是整数
#include<Stdio.h>
int main() {
int n = -1;
int m = 5;
printf("%d\n", n & m);
printf("%d\n", n | m);
printf("%d\n", n ^ m);
printf("%d\n", ~n);
return 0;
}
变态的面试题
不用第三个变量完成两个数的交换
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
a = a ^ b;
b = a ^ b;//b = a
a = a ^ b;//a= b
printf("%d %d", a, b);
return 0;
}
将a的值赋成a^b的值
这样
b = a ^ b ^ b = a
a = a ^ b ^ b(a) = b
异或运算:
a^a = 0
a^0 = a
求一个整型存储在内存中的二进制中1的个数
#define _CRT_SECURE_NO_WARNINGS 1
#define N 10
#include<stdio.h>
int main() {
int num = N;
int count = 0;
while (num) {
if (num % 2 == 1) {
count++;
}
num = num / 2;
}
printf("%d\n", count);
return 0;
}
//10---->1010
//法2
int main() {
int num = N;
int count = 0;
for (int i = 0; i < 32; i++) {
if (num & (1 << i))
count++;
}
printf("%d\n", count);
return 0;
}
//00001010
//00000001 1
//1 << i
//00000010
//依次对比尾位
//法3
int main() {
int num = N;
int count = 0;
while (num) {
count++;
num = num & (num - 1);
}
printf("%d\n", count);
return 0;
}
//00001010 10
//00001001 10-1=9
//两者&变成
//00001000 10二进制表达中最右侧的1就被删除了,这时count+1
这边给大家介绍三种方法,思维层度是一步步晋升的
大家在学习的过程中,可能会遇见很多无从下手的问题,看到别人的解法也是一脸惊讶,还能这么写代码
要见过才能知道,不能空想要一步一步去学,学的多了见得多了,才能想出来自己的方法
方法一:
以123为例
123%10=3
123/10=12
12%10=2
12/10=1
1%10=1 --->count++
按照这样的方法,对于二进制数来说,就是循环的模2和除以2
方法二:
这个方法的思维和简单,就是一位一位的去比较
二进制在系统中一般有32为,这样我们就需要循环32次
//00001010
//00000001 1
//1 << i
//00000010
//依次对比尾位
方法三:
这是博主之前完全没见过的方法,博主第一次遇见的时候也吃了一惊
//00001010 10
//00001001 10-1=9
//两者&变成
//00001000 10二进制表达中最右侧的1就被删除了,这时count+1
直到现在博主依旧无法想象这是怎么想出来的,只能说无处无高人,我们所见识的东西还是太少了。
4. 单目操作符
单目操作符有这些:!、++、--、&、*、+、-、~ 、sizeof、(类型)
单目操作符的特点是只有⼀个操作数,在单⽬操作符中只有&和*没有介绍,这2个操作符,我们放在学习指针的时候学习。
5. 逗号表达式
1 exp1, exp2, exp3, …expN
逗号表达式,就是⽤逗号隔开的多个表达式。逗号表达式,从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。
6. 下标访问[]、函数调用()
[ ] 下标引⽤操作符
操作数:⼀个数组名 + ⼀个索引值
int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
[ ]的两个操作数是arr和9。
函数调⽤操作符接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
四、结构成员访问操作符
1.结构体
用来描述复杂对象
其和数组一样是自定义类型
C语言已经提供了内置类型:char、short、int、long、float、double等
但是当我们遇见复杂类型的对象是就不够用了,如:学生这个对象,拥有 名字、性别、年龄、成绩等等一系列的,这个时候就需要我们自己创造不同的变量
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚⾄是其他结构体。
结构体的声明
struct tag
{
member-list;
}variable-list;
struct Point
{
int x;
int y;
}p1;
struct Point p2;
struct Point p3 = { 10,20 };
struct Stu
{
char name[15];
int age;
};
struct Stu s1 = { "zhangsan",20 };
struct Stu s2 = {.age = 20, .name = "lisi"};
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10,{4,5},NULL};
struct Node n2 = { 20,{5,6},NULL};
结构体成员的直接访问
用点操作符(.)访问
#include <stdio.h>
struct Point
{
int x;
int y;
}p = {1,2};
int main()
{
printf("x: %d y: %d\n", p.x, p.y);
return 0;
}
结构体成员的间接访问
有的时候没有一个结构体变量,而是一个指向结构体的指针
这个时候就吧p.x p.y
这样的直接访问形式改为:
struct Point *p = &p;//获得结构体变量的地址
p->x p->y
五、结束语
下一篇博主会给大家讲解一下操作符是使用注意事项,之后我们就进入指针的学习了!
敬请期待!