1.进制转换
1.1 10进制转2进制
方法:短除法
1.2 2进制转换8进制
8进制的数字每⼀位是0~7的,0~7的数字,各⾃写成2进制,最多有3个2进制位就⾜够了,⽐如7的⼆进制是111,所以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算⼀个8进制位,剩余不够3个2进制位的直接换算。
如:2进制的01101011 ,换成8进制:0153 ,++0开头的数字,会被当做8进制。++
我有一个问题:怎么区别 0111是8进制还是2进制?
1.3 2进制转换16进制
2进制转化16进制和2进制转换8进制十分相似,
16进制的数字每⼀位是0~9,a ~f 的,0~9,a ~f的数字,各⾃写成2进制,最多有4个2进制位就⾜够了,
⽐如 f 的⼆进制是1111,所以在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进
制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算。
如:2进制的01101011,换成16进制:0x6b,16进制表⽰的时候前面加0x
2 原码,反码,补码
整数 的2进制表⽰⽅法有三种,即原码、反码和补码
有符号整数的三种表⽰⽅法均有符号位 和数值位 两部分,++2进制序列中,最⾼位的1位是被当做符号++
++位,剩余的都是数值位。++
符号位都是⽤0表⽰"正",⽤1表⽰"负"。
正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同。
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
++反码得到原码也是可以使⽤:取反,+1的操作++
对于整形来说:数据存放中其实存放的是补码。
cpp
//操作符详解
#include<stdio.h>
int main()
{
int num1 = 10;
//10存放在整型变量num1中,占4个字节==32个bit位
//0000 0000 0000 0000 0000 0000 0000 1010 -原码
//0000 0000 0000 0000 0000 0000 0000 1010 -反码
//0000 0000 0000 0000 0000 0000 0000 1010 -补码
//正数的原码,反码,补码都一样
int num2 = -10;
//-10存放在整型变量num2中,占4个字节==32个bit位
//1000 0000 0000 0000 0000 0000 0000 1010 -原码
//1111 1111 1111 1111 1111 1111 1111 0101 -反码
//1111 1111 1111 1111 1111 1111 1111 0110 -补码
//负数的原码,反码,补码要经过计算
return 0;
}
为什么计算机中存补码呢?
在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀ 处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
简单举个例子:
cpp
1-1
1+(-1)
0000 0000 0000 0000 0000 0000 0000 0001 -1的补码
1111 1111 1111 1111 1111 1111 1111 1111 -(-1)的补码
相加
1 0000 0000 0000 0000 0000 0000 0000 0000
最高位被丢弃
结果是0
3 移位操作符(移动的是补码)
3.1左移操作符
移位规则:左边抛弃,右边补0
cpp
#include<stdio.h>
int main()
{
int num = 10;
int n = num << 1;
printf("n= %d\n", n);
printf("num= %d\n", num);
return 0;
}
3.2 右移操作符
右移操作符比左移操作符稍微复杂一点。
首先他的移位规则就分为两种:
- 逻辑右移:左边⽤0填充,右边丢弃
- 算术右移:左边⽤原该值的符号位填充,右边丢弃
应用哪种规则取决于编译器,目前大部分编译器上使用的算术右移,
cpp
#include <stdio.h>
int main()
{
int num = -10;
int n = num >> 1;
printf("n= %d\n", n);//-5
printf("num= %d\n", num);//-10
return 0;
}
逻辑右移:
算术右移
警告⚠️:
对于移位运算符,不要移动负数位,这个是标准未定义的。
cpp
int num = 10;
num>>-1;//error
- 位操作符:& | ^ ~
注意:他们操作的对象必须是整数
区别: && (逻辑与)||(逻辑或),他们是逻辑操作符
cpp
#include <stdio.h>
int main()
{
int num1 = 3;
//0000 0000 0000 0000 0000 0000 0000 0011 3的补码
int num2 = -5;
//1111 1111 1111 1111 1111 1111 1111 1011 -5的补码
//
//按照(二进制)位进行运算
printf("%d\n", num1 & num2);//3
//& 有0就是0,全1为1
//0000 0000 0000 0000 0000 0000 0000 0011
printf("%d\n", num1 | num2);//-5
//| 有1为1,全0为0
//1111 1111 1111 1111 1111 1111 1111 1011
printf("%d\n", num1 ^ num2);//-8
//^ 相同为0,相异为1
//1111 1111 1111 1111 1111 1111 1111 1100
printf("%d\n", ~0);//-1
//~ 按位取反
// 1111 1111 1111 1111 1111 1111 1111 1111 -(-1)的补码
return 0;
}
拓展⾯试题:
不能创建临时变量(第三个变量),交换两个整数
方法:使用^
cpp
#include <stdio.h>
int main()
{
int a = 10;
//0000 0000 0000 0000 0000 0000 0000 1010
int b = 20;
//0000 0000 0000 0000 0000 0000 0001 0100
a = a ^ b;
//a=
//0000 0000 0000 0000 0000 0000 0001 1110
//30
b = a ^ b;
//b=
//0000 0000 0000 0000 0000 0000 0000 1010
//10
a = a ^ b;
//a=
//0000 0000 0000 0000 0000 0000 0001 0100
//20
printf("a = %d b = %d\n", a, b);
return 0;
}
练习1 :编写代码实现 :求一个整数存储在二进制中1的个数
cpp
int main()
{
//方法1:&的应用
int num =5;
int i = 0; int count = 0;
for (i = 0; i < 32; i++)
{
if (((num >> i) & 1) == 1)//不用考虑正负数,移位操作符移动的是补码
count++;
}
printf("%d", count);
return 0;
}
问题: 什么时候要考虑正负数嘞?
cpp
#include <stdio.h>
int count_one_bit(unsigned int n)
//int count_one_bie(int n) 针对负数就可能出错
{
int count = 0;
while (n)
{
if (n % 2 == 1)
count++;
n = n / 2;
}
return count;
}
方法3:
cpp
//最高效的一种
int count_one_bit(unsigned int n)
{
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
return count;
}
- 单目操作符
单⽬操作符有这些:
!、 ++ 、 -- 、 & 、 * 、 + 、 - 、 ~ 、 sizeof 、 ( 类型 )
6.逗号表达式
逗号表达式,就是⽤逗号隔开的多个表达式。
逗号表达式,从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。
7.下标访问[]、函数调⽤()
7.1 [ ] 下标引⽤操作符
操作数:⼀个数组名 + ⼀个索引值
cpp
int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
//[ ]的两个操作数是arr和9。
7.2 () 函数调用操作符
cpp
#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;
}
8.结构体访问操作符
C语⾔已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类
型还是不够的,假设我想描述学⽣,描述⼀本书,这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要 名字、年龄、学号、⾝⾼、体重等;描述⼀本书需要作者、出版社、定价等。C语⾔为了解决这个问题,增加了结构体这种⾃定义的数据类型,让程序员可以⾃⼰创造适合的类型。
📌 结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚至是其他结构体
结构体声明
cpp
struct Stu
{
char name[20];
int age;
float score;
}stu;//全局变量
结构体的使用
cpp
//结构体的声明
struct Stu
{
char name[20];
int age;
float score;
}stu = {"Bob",18,97.5f};//全局变量的初始化
int main()
{
//结构体变量的初始化
struct Stu s1 = { "Mary",15,96.5 };//局部变量
//结构体的直接访问
printf("%s %d %lf\n", s1.name, s1.age, s1.score);
printf("%s %d %lf\n", stu.name, stu.age, stu.score);
//结构体的间接访问
struct Stu* ps = &s1;
printf("%s %d %lf\n", ps->name,ps->age,ps->score);
return 0;
}
- 操作符的优先级和结合性
9.1 优先级
优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的。
1 3 + 4 * 5 ;
⾯⽰例中,表达式 3 + 4 * 5 ⾥⾯既有加法运算符( + ),⼜有乘法运算符( * )。由于乘法的优先级⾼于加法,所以会先计算 4 * 5 ,⽽不是先计算 3 + 4 。
9.2结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执⾏),⽐如赋值运算符( = )。
1 5 * 6 / 2 ;
上⾯⽰例中, * 和 / 的优先级相同,它们都是左结合运算符,所以从左到右执⾏,先计算 5 * 6 ,
再计算 6 / 2 。 运算符的优先级顺序很多,下⾯是部分运算符的优先级顺序(按照优先级从⾼到低排列),建议⼤概
记住这些操作符的优先级就⾏,其他操作符在使⽤的时候查看下⾯表格就可以了。
• 圆括号( () )
• ⾃增运算符( ++ ),⾃减运算符( -- )
• 单⽬运算符( + 和 - )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
• 赋值运算符( = )
由于圆括号的优先级最⾼,可以使用它改变其他运算符的优先级。
C语言中,可以通过数字的前缀来区分二进制和八进制。如果一个数字以0开头,则表示它是八进制数;如果一个数字以0b或0B开头,则表示它是二进制数。
例如,对于数字0111,根据前缀0,它被视为八进制数。要将其转换为十进制数,可以使用C语言中的atoi()函数或strtol()函数。
下面是一个示例代码:
cpp#include <stdio.h> #include <stdlib.h> int main() { char *binary = "0111"; int decimal = strtol(binary, NULL, 8); printf("Decimal value: %d\n", decimal); // 输出:73 return 0; }
在上面的代码中,我们将字符串"0111"转换为八进制数73,并将其打印为十进制数