C语言运算符、整形提升、截断及整形浮点型存储

C语言运算符、整形提升与截断

前言

1.首先需要了解整数储存的机制;
2.原码、反码、补码之间的关系;
2.(1)正整数原码、反码、补码相同,直接进行二进制转换就可;(2^32-1个正整数)
2.(2)负整数32位的首位为符号位(1代表负数);(2^31-1个符数)
我们来说个例子,例如:-1
首先写出-1的补码为:
1000 0000 0000 0000 0000 0000 0000 0001 -----原码
其次求出反码:符号位不变,其他位取反,得到反码
1111 1111 1111 1111 1111 1111 1111 1110 -----反码
最后反码+1得到补码,内存中存的都是补码
1111 1111 1111 1111 1111 1111 1111 1111 -----补码
3.整形提升、截断

part 1.位运算操作符

(1)按位与操作符&: 如果两个对应位置的二进制位都为1,则该位结果为1,否则为0。看下面的例子:

    int a = 15;   //0000 0000 0000 0000 0000 0000 0000 1111 ---15的二进制表示
    int b = 19;   //0000 0000 0000 0000 0000 0000 0001 0011 ---19的二进制表示
    int c = a & b;//0000 0000 0000 0000 0000 0000 0000 0011 ---a&b=3的二进制表示

(2) 按位或操作符|: 如果两个对应位置的二进制位都为0,则该位结果为0,否则为1。看下面的例子:

    int a = 15;   //0000 0000 0000 0000 0000 0000 0000 1111 ---15的二进制表示
    int b = 19;   //0000 0000 0000 0000 0000 0000 0001 0011 ---19的二进制表示
    int c = a | b;//0000 0000 0000 0000 0000 0000 0001 1111 ---a|b=31的二进制表示

(3)按位异或操作符^:如果两个对应位置的二进制位相同时结果为0,否则为1。看下面的例子:

    int a = 15;   //0000 0000 0000 0000 0000 0000 0000 1111 ---15的二进制表示
    int b = 19;   //0000 0000 0000 0000 0000 0000 0001 0011 ---19的二进制表示
    int c = a^b;  //0000 0000 0000 0000 0000 0000 0001 1100 ---a^b=28的二进制表示

下面运用异或来实现俩个数的交换:

#include <stdio.h>
int main()
{
 int a = 10;  //0000 0000 0000 0000 0000 0000 0000 1010 ---10的二进制表示
 int b = 20;  //0000 0000 0000 0000 0000 0000 0001 0100 ---20的二进制表示
 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;   //c=0000 0000 0000 0000 0000 0000 0001 0100 ---20的二进制表示
 printf("a = %d b = %d\n", a, b);
 return 0;
}

通过异或来实现俩个数字的交换不会出现溢出的情况。
(4) 左移操作符<<:移位规则:左边抛弃、右边补0。看下面的例子:

int main(){
    int a = 19;  //00000000000000000000000000010011
    int b = a<<1;//00000000000000000000000000100110
    printf("%d", b);//输出结果38
    return 0;
}

(5) 右移运算符>>:
移位规则:首先右移运算分两种:

  1. 逻辑移位 左边用0填充,右边丢弃
  2. 算术移位 左边用原该值的符号位填充,右边丢弃

在这里插入图片描述
在这里插入图片描述
(6)按位取反操作符~:对该数的二进制位,若该位为1则结果为0,若改为为0则结果为1。例如:

    int a = 19;//00000000000000000000000000010011
    int b = ~a;//11111111111111111111111111101100(内存中的补码)
               //11111111111111111111111111101011(反码,即补码-1)
               //10000000000000000000000000010100(原码,即反码符号位不变,其余位数取反)

part 2.整形提升与截断

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
1111 1111  1111  1111  1111  1111  1111  1111

//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
0000 0000 0000 0000 0000 0000 0000 0001

整形提升的例子:
//实例1
char a,b,c;
...
a = b + c;
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
1111 1111 1111 1111 1111 1111 1111 1111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
0000 0001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
0000 0000 0000 0000 0000 0000 0000 0001
//无符号整形提升,高位补0

//实例2
#include<stdio.h>
int main()
{
    char a = 0xb6;
    short b = 0xb600;
    int c = 0xb6000000;
    if (a == 0xb6)
    {
        printf("a");
    }
    if (b == 0xb600)
    {
        printf("b");
    }
    if (c == 0xb6000000)
    {
        printf("c");
    }
    return 0;
}
`

实例2中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式
a0xb6 , b0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真.
所程序输出的结果是:c

实例2:

#include <stdio.h>
int main()
{
    char a = -1;
    //1000 0000 0000 0000 0000 0000 0000 0001 -----原码
    //1111 1111 1111 1111 1111 1111 1111 1110 -----反码
    //1111 1111 1111 1111 1111 1111 1111 1111 -----补码
    //发生截断
    //1111 1111  ----a
    //由于要打印整形(%d)格式,所以发生整形提升,前面补充符号位
    //1111 1111 1111 1111 1111 1111 1111 1111 -----补码
    //1111 1111 1111 1111 1111 1111 1111 1110 -----反码
    //1000 0000 0000 0000 0000 0000 0000 0001 -----原码
    //a那打印就是-1

    signed char b = -1;
    unsigned char c = -1;
    //1000 0000 0000 0000 0000 0000 0000 0001 -----原码
    //1111 1111 1111 1111 1111 1111 1111 1110 -----反码
    //1111 1111 1111 1111 1111 1111 1111 1111 -----补码
    //发生截断
    //1111 1111  ----c
    //由于要打印整形(%d)格式,所以发生了整形提升,前面补充符号位,但c是无符号数,所以前面补0
    //由于是正数-----补码==反码==原码
    //0000 0000 0000 0000 0000 0000 1111 1111 
    printf("a=%d,b=%d,c=%d", a, b, c); //a= -1  b= -1 c= 255
    return 0;

}

实例3:

#include <stdio.h>
int main()
{
    char a = -128;
    //1000 0000 0000 0000 0000 0000 1000 0000 -----原码
    //1111 1111 1111 1111 1111 1111 0111 1111 -----反码
    //1111 1111 1111 1111 1111 1111 1000 0000 -----补码
    //截断
    //1000 0000 ----a
    //由于要打印无符号整形(%u)格式,所以发生整形提升,前面补充符号位
    //1111 1111 1111 1111 1111 1111 1000 0000 -----补码==反码==原码  
    printf("%u\n", a);   //打印的值-----4294967168‬
    return 0;
}

既然我们已经学会了整形提升和截断,来让我们写一下下面这几道题:
题目1:以下代码会发生什么情况

#include<stdio.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)        //无符号整形恒大于0,所以会死循环
	{                               //当i变成-1时,把有符号数转变位无符号数为32个1然后一直死循环下棋
                                  //1000 0000 0000 0000 0000 0000 0000 0001 -----原码
                                  //1111 1111 1111 1111 1111 1111 1111 1110 -----反码
                                  //1111 1111 1111 1111 1111 1111 1111 1111 -----补码 -1对应的无符号数
		printf("%d\n", i);
	}
	return 0;
}

无符号整形恒大于0,所以会死循环。

题目2::以下代码打印的值为多少

#include<stdio.h>
#include<string.h>
int main()
{
    char a[1000];
    int i;
/*a是字符型数组,strlen找的是第一次出现尾零(即值为0)的位置。考虑到a[i]其实是字符型,
如果要为0,则需要 - 1 - i的低八位要是全0,也就是问题简化成了“寻找当 - 1 - i的结果第一次出现低八位全部为0的情况时,
i的值”(因为字符数组下标为i时第一次出现了尾零,则字符串长度就是i)。只看低八位的话,此时 - 1相当于255,所以i == 255的时候,
- 1 - i(255 - 255)的低八位全部都是0,也就是当i为255的时候,a[i]第一次为0,所以a[i]的长度就是255了。*/
    for (i = 0; i < 1000; i++)
    {
        a[i] = -1 - i;      //-128~127
    }
    printf("%d", strlen(a));   //strlen(a)----255
    return 0;
}

part 3.计算器的大小端

何为大小端

在这里插入图片描述
我们来通过一个代码来看看自己当前使用的平台是使用大端存储还是小端存储。

#include<stdio.h>
int check_sys()
{
	int i =0x00000001;     
	char* p = (char *)&i;   //让它只能访问一个字节的内容
	return *p;
}
int main()
{
	if (check_sys() == 1)
	{
		//把一个数据的低位字节序的内容,存放在低地址处
		//            高位字节的内容,存放在高位地址处
		printf("小端\n");   //1在小端内存存储的十六进制----------01 00 00 00
	}
	else
	{
		//把一个数据的低位字节序的内容,存放在高位地址处
                  //高位字节序的内容,存放在低位地址处
		printf("大端\n");   //1在大端内存存储的十六进制----------00 00 00 01
	}
	return 0;
}

在这里插入图片描述
通过上图我们发现vs2019存储是小端字节序存储

part 4.数据存储-浮点型

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
举例来说: 十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。 那么,按照上面V的格式,可以得出s=0,
M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。
IEEE 754规定: 对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
在这里插入图片描述
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
在这里插入图片描述
IEEE 754对有效数字M和指数E,还有一些特别规定。 前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形
式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。
比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。
以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

至于指数E,情况就比较复杂。
首先,E为一个无符号整数(unsigned int) 这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的
取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真
实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E
是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

然后,指数E从内存中取出还可以再分成三种情况:
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前
加上第一位的1。 比如: 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,
则为1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位
00000000000000000000000,则其二进制表示形式为:
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为
0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

int main()
{
	int n = 9;
	//0 00000000 00000000000000000001001 ----整数存储形式 
	//(-1)^0 * 0.00000000000000000001001 * 2^-126   ----单精度浮点数存储形式
	float *pFloat = (float *)&n;
	printf("n的值为:%d\n", n);//9
	printf("*pFloat的值为:%f\n", *pFloat);//0.000000

	*pFloat = 9.0;
	//1001.0
	//(-1)^0 * 1.001*2^3
	//S=0  ----表示的是符号位 0为正 ,负数为1
	//M=1.001
	//E=3     +127
	//9.0 -> 1001.0 ->(-1)^01.0012^3 -> s=0, M=1.001,E=3+127=130
	//01000001000100000000000000000000
	printf("num的值为:%d\n", n);//直接打印整形时,就是2进制到十进制转换
	printf("*pFloat的值为:%f\n", *pFloat);//9.0
	return 0;
}

好了,就说到这里。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值