1.C语言中左值和右值的区别
左值可以放在赋值符左边的值,右值是放在赋值符右边的值。
左值,没有内存实体的左值是存在的
右值,可以有内存实体,也可以没有内存实体(寄存器),a+1,&a,右值都在寄存器。
2.调试、下断点
断点:可以中断程序执行,观察内部过程
GPU,显卡的处理器
3.const和define的区别
const会进行类型转换,而define不会,只是的替换
#define K 100.0
const int Knum = 100.0; //赋值号会自动进行数据类型转换
void main()
{
printf("%d\n", K);
printf("%d", Knum);
}
4.printf的容错机制
printf的容错机制,第一个错误,第二个会连带这错误,而且printf也不会进行类型转换
`printf("%d, %d\n", 10.9, 10);`结果两个数都是错的,而换成两行,则10就会正常输出
//printf按照%d,%u,按照你指定的方式解析二进制
unsigned int data1 = -1; //二进制数
printf("%u\n", data1); //二进制数据,%d,有符号十进制
printf("%d\n", data1); //%u无符号十进制
5.变量的赋值就是拷贝二进,内存之间不能直接拷贝,必须通过CPU
常量必须初始化,const常量只有在初始化的时候是左值。
6.指针的本质
指针是一种数据类型,类型决定了大小(步长),决定了二进制数据的解析方式
void main8()
{ //指针是一种数据类型,类型决定了大小(步长),决定了二进制数据的解析方式
int num = 100;
double *p1 = # //这里只是把num的地址赋给了p1,而num并没有进行类型转换
int *p2 = #
float *p3 = #
printf("%f\n", *p1); //乱码
printf("%d\n", *p2); //可以
printf("%f\n", *p3); //不可以,没有进行类型转换0.00000
getchar();
}
手机号使用long long int存数
7.float存储
浮点数按指数(阶码)方式存储
阶码分为三大部分,符号位s,幂数e,系数x
1.第31个bit为符号位,为0表示正数,为1表示负数(32-bit为单精度,64-bit浮点数为双精度);
2.第30~23bit,共8bit为幂数,其读数值用e表示,以2为底;
3.第22~0bit,共23bit作为系数,视为二进制纯小数,假定该小数的十进制值为x;
浮点数的值用十进制表示为
(-1)^s*(1+x)*2^(e-127)
符号位的代码推演
float fl1 = 10.0;
float fl2 = -10.0;
printf("%p, %p\n", &fl1, &fl2);
10.0的十六进制41200000 二进制0 100 0001 0 010 0000 0000 0000 0000 0000
-10.0的十六进制c1200000 二进制1 100 0001 0 010 0000 0000 0000 0000 0000
结论第32个bit是符号位
系数的代码推演
float fl1 = 10.0;
float fl2 = 11.0;
float fl3 = 5.0;
printf("%p, %p, %p\n", &fl1, &fl2, &fl3);
10.0的十六进制41200000 二进制0 100 0001 0 010 0000 0000 0000 0000 0000
11.0的十六进制41300000 二进制0 100 0001 0 011 0000 0000 0000 0000 0000
10 100 0001 0
1010 +130(130-127) 1.010*2^3
11 100 0001 0
1011 +130(130-127) 1.011*2^3
幂数的代码推演
float fl1 = 1.0;
float fl2 = 2.0;
float fl3 = 0.5;
printf("\n%p,%p,%p", &fl1, &fl2, &fl3);
1.0的十六进制3f800000 二进制0 011 1111 1 000 0000 0000 0000 0000 0000
2.0的十六进制40000000 二进制0 100 0000 0 000 0000 0000 0000 0000 0000
0.5的十六进制3f000000 二进制0 011 1111 0 000 0000 0000 0000 0000 0000
1.0 011 1111 1
0000 127 1.000*2^0
2.0 100 0001 0
0000 128 1.000*2^1
0.5 100 1111 0
0000 126(e-127) 1.000*2^(-1)
8.为什么使用补码
补码的优势是运算速度快
例一个字节大小的 1 ,-3 相加
1的原码: 0000 0001
-3的原码:1000 0011
第一步,转换成减法,求绝对值, 看那个数大
第二步 绝对值的大数减小数 得到0000 0010
第三步 判断符号位 得到1000 0010, -2
使用补码
1的补码: 0000 0001
-3的补码: 1111 1101
两个数相加 1111 1110
1111 1110 的原码是1000 0010 就是-2
再例如 3和-1相加
3的补码: 0000 0011
-1的补码: 1111 1111
两个数相加 10000 0010
多的1舍弃,得到0000 0010
0000 0010 的原码还是0000 0010 就是2
利用进位巧妙的把符号位放到运算里来,大大加快了计算的效率
9.位运算
与 & 0&0->0 0&1->0 1&0->0 1&1->1
或 | 0|0->0 0|1->1 1|0->1 1|1->1
异或 ^ 0^0->0 0^1->1 1^0->1 1^1->0 相同为0,不相同为1
使用异或交换数据,避免溢出的风险
unsigned char ch1 = 15; //0000 1111
unsigned char ch2 = 255;//1111 1111
ch1 = ch1^ch2; //ch1 = 1111 0000 ch2 = 1111 1111
ch2 = ch2^ch1; //ch2 = 0000 1111 ch1 = 1111 0000
ch1 = ch1^ch2; //ch1 = 1111 1111 ch2 = 1111 0000
位取反 ~ ~0 = 1 ~1 = 0
!0 = 1是逻辑运算符不是位运算符
unsigned char ch1 = ~0; ~0之后 1111 1111
unsigned int int1 = ~0; ~0之后 1111 1111 1111 1111 1111 1111 1111 1111
左移 << 0001<<1 -->0010 0001<<2 -->0100 //相当于每次乘以2
int num = 1;
printf("%d\n", num << 1); //打印的值是2,但是num的指没有改变
printf("%d\n", num << 2); //打印的值是4,但是num的指没有改变
printf("%d\n", num); //打印的值是1
printf("%d\n", num <<= 2); //打印的值是4,num的值改变 <<= 相当于+=
printf("%d\n", num); //打印的值是4
右移 >> 0100>>1 -->0010 0100>>2 -->0001 //相当于每次除以2
与:某一段清零
或:指定的字符置一
异或:指定的字符反转
10.求一个数的二进制中1的个数
利用与操作来求
例如 100 1100100 3个1
100-1=99 1100011
100&99=96 1100000 少了一个1 +1
96-1=95 1011111
96&95=64 1000000 少了一个1 +1
64-1=63 0111111
64&63=0 0000000 少了一个1 +1
代码实现
for (; num; num = num&(num-1))
wei++;
递归
int getwei3(int num)
{
if (num == 0)
return 0;
else
return 1 + getwei3(num&(num - 1));
}
11.求一个数的补码和原码
求补码
因为一个数在内存中就是按照补码的方式存的
所以1的二进制是0000 0000 0000 0000 0000 0000 0000 0001
-1的二进制是1111 1111 1111 1111 1111 1111 1111 1111
我们构造一个数 1000 0000 0000 0000 0000 0000 0000 0000
把这个数与1或者-1位求与就可以得到这个数的这一位是什么,然后再把1或者-1左移一位,就是到了第二位的值,以此类题
int num = 1;
int data = 1 << 31;
for (int i = 1; i <= 32; i++) {
printf("%c", (data&num ? '1' : '0'));
num <<= 1;
if (i % 4 == 0)
printf(" ");
}
递归的写法是
void buma(int n, int cnt)
{
int data = 1 << 31;
if(cnt == 0)
return;
if(cnt %4 == 0)
printf(" ");
printf("%c", (data&n ? '1' : '0'));
n <<=1;
--cnt;
buma(n,cnt);
}
求原码
根据原码和补码的转换规则,正数的原码和补码一样,负数的原码和补码的转换规则是:
原码->补码:符号位不变,其余位取反+1
补码->原码:符号位不变,其余位取反+1
例如num = -1;
num = ~num +1;//这是全部按位取反,再加一,但是符号位也取了反
num = num | (1<<31);//这是把符号位再重新置为1
//然后在按位把这个数读出来
代码就是:
if (num < 0) {
num = ~num + 1;//根据补码求原码
num = num | data;
}
for (int i = 1; i <= 32; i++) {
printf("%c", (data&num ? '1' : '0'));
num <<= 1;
if (i % 4 == 0)
printf(" ");
}
反码:
-1的原码 1000 0001
反码 1111 1110
补码 1111 1111
所以一个数的反码就是这个数的补码-1
代码:
if (num < 0) {
num -=1;
}
for (int i = 1; i <= 32; i++) {
printf("%c", (data&num ? '1' : '0'));
num <<= 1;
if (i % 4 == 0)
printf(" ");
}
补码求原码 正数不变 负数的话取反+1在重置符号位
补码求反码 正数不变 负数的话-1