你不知道的C语言知识(第3期)

在这里插入图片描述
本期介绍🍖
主要介绍:C语言中一些大家熟知知识点中的盲区,这是第三期。



1. 整形在内存中的存储模式

1.1 原码、反码、补码

  在计算机中,整数的二进制表示方式有3种,即原码、反码、补码。有符号整数的三种表示方法匀有符号位数值位两部分组成,最高位被当作符号位,剩余位都是数值位(符号位0表示正,1表示负)。正整数的原、反、补码都相同,负数的原、反、补码不同,需要通过一些列换算得到。换算规则如下:

  • 原码:直接将整数按照正负翻译成二进制的形式,就得到原码。
  • 反码:将原码的符号位不变,其他位依次按位取反,就得到反码。
  • 补码:反码+1就得到补码。

  结论:整数在内存中是以补码的形式进行存放的。通过如下案例,调试中的内存我们就可以得知。
在这里插入图片描述


1.2 为什么要以补码的形式存放

  在计算机系统中整数一律用补码来表示和存储,原因在于:

  1. 使用补码,在运算时能将符号位和数值域统一处理,同时加减法也可以统一处理。

  补码的提出解决了一个问题:CPU只有加法器。那CPU在读取完数据后如何进行减法操作呢?只需要将减法运算转换成加负数的运算就行了。而加负数的运算就必须带上符号位。
  如果不以补码的形式存放,而以原码的形式进行存放。那在计算(1)+(-1)的时候我们就无法得我们所认为的结果。原码是无法进行负数的运算的,但补码却可以。如下图所示:
在这里插入图片描述

  1. 补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

  大家知道原码转补码:先按位取反,再+1操作。按道理,补码转原码只有反过来执行(先-1,再按位取反操作)才能得到原码。可事实上,补码完全可以使用原码转换成补码的方法来求原码(即:补码先按位取反后再+1),求得的结果完全与原码相同。举例如下图所示:
在这里插入图片描述

  1. 如果内存是以补码的形式存放,可以保证数据在整型提升前与提升后表达的值不变。

1.3 整形数值超存储范围

  当存放的整型数值超过类型的存储范围时,就会导致数据的丢失。而真正存放进去的数值,只会是一个在该类型存储范围内的数。

举个例子:如若有一个char类型的变量,初值为0。使得其每次循环只自增1,循环1000次。问该变量会最后的结果?

#include<stdio.h>
#include<Windows.h>

int main()
{
	char n = 0;
	int i = 0;
	for (i = 1; i <= 1000; i++)
	{
		printf("%d ", n);
		n++;
		Sleep(100);
	}
	return 0;
}

在这里插入图片描述
  结果是什么并不重要,重要的是知道了有符号整型存在着这样一种规律,即:随着变量不断的自增,它的值只会在其类型的存储范围内无限的循环下去,并不会超出该范围。 同理对于无符号整数来说,其值只会在0~255这个范围内循环下去(转折点为:255自增1变为0)。


2. 位操作符

  C语言中基本的位操作符有: 左移、右移、按位与、按位或、按位异或、取反。警告:对于移位操作,不要移动负数位,这个是标准未定义的!!!

2.1 左移"<<"

  左移规则:左移时高位丢弃,低位补0。左移操作其实就是在左乘2,如下图所示,正整数7向左移动一位后不难算出结果为14;负整数-7向左移动一位结果为-14。
在这里插入图片描述
在这里插入图片描述


2.2 右移"<<"

  右移规则:右移分为两种:算数右移、逻辑右移

算数右移:低位丢弃,高位补原符号位
逻辑右移:低位丢弃,高位补0

  而在vs编译器中采用的是算数右移。如下图所示:

在这里插入图片描述

  可以发现,其实右移操作是在做整数除2操作。


2.3 按位取反"~"

  按位取反规则:把目标的每一个二进制位取反,即:使得每一位上的0变成1,1变成0。例子如下:
在这里插入图片描述


2.4 按位与"&"

  按位与规则:把参见运算两个数所对应的二进制位分别进行“与”运算,即:两位同为“1”,结果才为“1”,否则为0。例子如下:
在这里插入图片描述


2.5 按位或"|"

  按位或规则:把参见运算两个数所对应的二进制位分别进行“或”运算,即:两位同为“0”,结果才为“0”,否则为1。例子如下:
在这里插入图片描述


2.6 按位异或"^"

  按位异或规则:把参见运算两个数所对应的二进制位分别进行“异或”运算,相同为0,相异为1。例子如下:
在这里插入图片描述


3. 逗号表达式

  逗号表达式,是由逗号操作符隔开的多个表达式。逗号表达式从左向右依次执行,整个表达式的结果是最后一个表达式的结果。

//逗号表达式:exp1,exp2,exp3,……expN;

  举例如下:问c的值是多少?

int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);

  可以看出初始化c的是一个逗号表达式,逗号表达式会从左向右依次执行。执行表达式a>b,结果为0;执行表达式a=b+10,a被赋值为12,表达式结果为12;执行表达式a,结果为12;执行表达式b=a+1,b被赋值为13,表达式结果为13。又因为逗号表达式结果是最后一个表达式的结果,所以c被赋值为13。


4. 操作符的属性:优先级、结合性

  C语言的操作符有两个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。
  优先级:如果一个表达式包含多个操作符,哪个操作符应该由优先执行。
  结合性:如果两个运算符优先级相同,优先级没办法决定优先计算哪个,这时候就需要看结合性了。根据运算符是左结合,还是右结合,决定执行顺序。

优先级操作符描述运算类用法示例结果类型结合性是否控制求值顺序
1()聚组N/A(表达式)与表达式相同N/A
()函数调用rexp(rexp,…,rexp)rexpL/R
[ ]下标引用rexp[rexp]lexpL/R
.访问结构体成员lexp.成员名lexpL/R
->访问结构指针成员rexp->成员名lexpL/R
2!逻辑非单目运算! rexprexpR/L
~按位取反~ rexprexpR/L
++自增1++ lexp、lexp ++rexpR/L
- -自减1- - lexp、lexp - -rexpR/L
+单目,表示求正值+ rexprexpR/L
-单目,表示求负值- rexprexpR/L
*间接访问* rexplexpR/L
&取地址& lexprexpR/L
(类型名)强制类型转换(类型名)rexprexpR/L
sizeof求所占字节数sizeof rexp、sizeof(类型)rexpR/L
3*、/、%乘、除、整数取余算数运算rexp * rexp、rexp / rexp、rexp % rexprexpL/R
4+、-加、减rexp + rexp、rexp - rexprexpL/R
5<<、>>左移位、右移位位运算rexp << rexp、rexp >> rexprexpL/R
6<、<=、>、>=小于、小于等于、大于、大于等于关系运算rexp < rexp、rexp <= rexp、rexp > rexp、rexp >= rexprexpL/R
7==、!=等于、不等于rexp == rexp、rexp != rexprexpL/R
8&按位与位运算rexp & rexprexpL/R
9^按位异或rexp ^ rexprexpL/R
10|按位或rexp | rexprexpL/R
11&&逻辑与逻辑运算rexp && rexprexpL/R
12||逻辑或rexp || rexprexpL/R
13?:条件运算三目运算rexp ? rexp:rexprexpR/L
15=、+=、-=、*=、/=、%=、&=、^=、|=、<<=、>>=赋值运算双目运算lexp = rexprexpR/L
16逗号表达式顺序运算rexp,rexprexpL/R

  注意:上表中的rexp是右值表达式,代表其值;lexp是左值表达式,代表其所在内存空间。N/A是空的意思,R/L是从右向左的意思,L/R是从左向右的意思。


5. 问题表达式解析

  贯彻这节的一句话:一个表达式如果没有办法确定它唯一的计算路径,那么该表达式就存在潜在的问题。注意:操作符的优先级,仅仅只能确定相邻的两个操作符谁先执行。

5.1 表达式1

//表达式1
a*b + c*d + e*f

  表达式1乍一看似乎没什么问题,但其无法通过操作符的优先级和结合性,确定唯一的计算路径。因为操作符的优先级,仅仅只能决定相邻的两个操作符谁先执行。就如表达式a*b + c*d + e*f,无法确定先求哪一个乘法,可能先求a*b,也可能c*d,也可能e*f。虽然先求哪个乘法都是一样的结果,但如若求任意一个乘法都会改变另外两个乘法的结果,那么就无法确定表达式唯一的值了。


5.2 表达式2

//表达式2
c + --c;

  对于表达式2来说,操作符的优先级只能决定--的运算在+的运算的前面。但是没有办法得知,第一个c值的获取是在第二个c进行--操作之前还是之后。所以结果是不可预测的,是有歧义的。


5.3 表达式3

#include <stdio.h>
int fun()
{
 static int count = 1;
 return ++count;
}
int main()
{
 int answer;
 answer = fun() - fun() * fun();
 printf( "%d\n", answer);//输出多少? 
 return 0;
}

  表达式3 answer = fun() - fun() * fun();,有唯一且确定的计算路径,但函数的调用先后顺序无法通过操作符的优先级、结合性确定,这就导致表达式有歧义。


6. 隐式类型转换

6.1 整型提升

6.1.1 什么是整型提升

  在C语言中,整型算数运算总是至少以默认的整形类型精度进行。为了达到这种精度,小于整型长度的变量(例如:字符型和短整型)在运算前会被转换为整型,这种转换被称为:整型提升


6.1.2 整形提升的意义

  由于整型运算都是在CUP中执行的,而CPU中专门处理整型运算的处理器(ALU)操作数的默认长度是int类型长度,同时也是CPU的通用寄存器的长度。因此,小于int型长度的变量在CPU中运算时,要先转化为CPU内整型操作数的标准长度,然后才能送入CPU中进行运算。


6.1.3 如何整型提升

  1. 有符号整型提升:按照变量的符号位来提升
  2. 无符号整型提升:高位补0

  下面举个列子,请问最后显示的结果是多少?

#include<stdio.h>
int mian()
{
	char a = 5;
	char b = 125;
	char c = a + b;
	printf("%d\n", c);
	return 0;
}

  分析:由于ab都是char型变量,故在进行a + b的算数运算前需要整型提升,按符号位提升,由于ab都是有符号的变量,故按符号提升,符号位是0,故高位补0,如下所示。之后将a + b的结果赋给变量c,但由于变量c的长度小于int型,故会发生截断,如下所示。最后,使用printf以整型的形式打印变量c,这时需要将c先转换位整型的类型,也就是整型提升,由于c是有符号变量,符号位是1,故高位补1,如下所示。将整型提升后的补码转换为原码,就是-126就是最后打印的结果。

char a = 5;
// a : 00000101
char b = 125;
// b : 01111101
char c = a + b;
// a : 00000000 00000000 00000000 00000101
// b : 00000000 00000000 00000000 01111101
//a+b: 00000000 00000000 00000000 10000010
// c : 10000010
printf("%d\n", c);
// c : 11111111 11111111 11111111 10000010 (补码)
// c : 10000000 00000000 00000000 01111101 (反码)
// c : 10000000 00000000 00000000 01111110 (原码)
// c : -126

6.2 算数转换

  在C语言中,两个不同类型的变量是无法进行运算的,除非将其中一个变量的类型转换为另一个变量的类型,且这种转换的方向是从低到高的,如下所示,则称为:算数转换

  1. long double
  2. double
  3. float
  4. unsigned long int
  5. long int
  6. unsigned int
  7. int

在这里插入图片描述

这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值