整型提升:为什么你的char相加变成了负数??

📅 2026/8/13 11:03:03
整型提升:为什么你的char相加变成了负数??
文章目录操作符的分类二进制和进制转化二进制转十进制十进制转二进制2进制转8进制和16进制2进制转8进制(421法)2进制转16进制(8 4 2 1法)原码、反码、补码位移操作符左移操作符右移操作符位操作符:、|、^、~按位与运算| 按位或^ 按位异或~ 按位取反逗号表达式下标访问[],函数调用()[]下标引用操作符函数调用操作符结构体结构的声明结构体变量的定义和初始化操作符的属性:优先级,结合性优先级结合性表达式求值整型提升算术转换总结1. 进制转换2. 原码、反码、补码3. 位移操作符4. 位操作符5. 其他操作符6. 操作符属性7. 表达式求值核心要点操作符的分类算数操作符: 、- 、* 、/ 、%移位操作符: 、位操作符: 、| 、^赋值操作符: 、 、- 、/ 、% 、 、 、 、| 、^单目操作符: ! 、 、-- 、 、* 、 、- 、 ~ 、sizeof 、(类型)关系操作符: 、 、 、 、 、!逻辑操作符:、||条件操作符:? :逗号表达式: ,下标引用: []函数调用:()结构成员访问: · 、-上述操作符我们在之前的文章中了解过算数操作符,赋值操作符,逻辑操作符,条件操作符和一部分单目操作符,今天介绍剩下的操作符,有一些是关于进制的,我们先来介绍一下进制转换二进制和进制转化我们经常能见到2进制,8进制,10进制,16进制这样的讲法写法,那么这到底是啥呢?其实2进制,8进制,10进制16进制是数值的不同表现形式而已例如我们用这些进制表示15这个数二进制:1111八进制:17十进制:15十六进制:F//注意十六进制的数值之前写0x//八进制的数值之前写0我们平常学习的都是十进制,满十进一,二进制八进制十六进制我们可以知道分别是满二进一,满八进一,满十六进一,十进制中的数值是0到9,十进制中没有十,同理二进制中只有0和1,八进制中只有0到7,那么十六进制中是0到16吗?其实十六进制比较特殊,它的数值是0到9和A到F组成,因为如果十六进制包括11,12,13会产生歧义,十六进制数123,这个是表示12和3还是1,2,3呢?因此十六进制是0到9和A到F二进制转十进制十进制中123表示的是一百二十三,那么为什么是表示的一百二十三呢?其实十进制每一位上都有权重,我们从小数点的左边开始数0,1,2,3…第一个数乘以10的0次方第二个数乘以10的1次方以此类推乘完再把他们相加得出十进制的123表示的是一百二十三,其中10的0次方,10的1次方就是权重二进制与之类似,第一个数乘以2的0次方第二个数乘以2的1次方二进制,八进制,十六进制转十进制都遵循上述方式即可每一位上的数值*每一位的权重再求和就转化为了十进制十进制转二进制采用短除法,把余数倒数输出即可2进制转8进制和16进制2进制转8进制(421法)8进制的数字每一位是0到7,0到7的数字写成2进制,最多有三个二进制位就足够,比如7的二进制为111因此在2进制转化为8进制的时候,从二进制序列中最右边向左开始每三个二进制位会转化为一位8进制数,剩余不够三个二进制位的时候我们在前面补0就行了,凑够三个3个再转换在前面补0不会改变其原本的大小如果在后面补0会影响数的本来大小导致我们转换错误我们找到三个2进制数自动从高位到低位(从左到右)数421(因为一个八进制数对应三个二进制数),如果这个二进制位是1那么就对应421的位数上的数字如果二进制位为0那么就是0,对应好之后我们要把得出的数字相加比如2进制数:10101101,转化为8进制数为:0255,0开头的数字会被当做8进制2进制转16进制(8 4 2 1法)十六进制的数值每一位是0到9,A到F,各自写成2进制最多有四个2进制位就足够了,和上面2进制转8进制同理这里不再赘述,运算的方式也一样只是多了一位,下面直接给出例子例如:把1001110101转化为16进制为:0x275,十六进制表示的时候在前面添加0x原码、反码、补码整数的2进制表示方式有三种为原码,反码,补码有符号整数的三种表示方法都由符号位和数值位两部分组成,2进制序列中最高位的一位是符号位,剩余的都是数值位符号位0表示正数,“1表示负数”正整数的原、反、补码都相同负整数的三种表示方法各不相同原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码反码:将原码的符号位不变,其他位置依次按位取反(1变成0,0变成1)补码:反码1补码的补码就是原码无符号整数的三种表达方式都一样没有符号位,每一位都是符号位对于整型来说数据存放内存中其实存放的是数据的补码,因为在计算机中,数值一律使用补码来表示和存储,因为使用补码,可以把符号位和数值位统一处理,同时,如果我们使用原码进行计算会出错误下面个大家举个例子就能更好地理解了//例如我们计算1(-1)100000000000000000000000000000001----原,反,补一样-110000000000000000000000000000001----原码11111111111111111111111111111110----反码11111111111111111111111111111111----补码//两数的原码去计算:100000000000000000000000000000010----结果是-2//两数的补码计算:100000000000000000000000000000000----此时会溢出,当溢出的时候和我们直接俄舍弃最高位00000000000000000000000000000000---最终结果为0位移操作符左移操作符:右移操作符:位移操作符的操作数只能是整数左移操作符移位规则:左边抛弃,右边补0intmain(){inta1;//00000000000000000000000000000001intba1;//00000000000000000000000000000010printf(%d %d,a,b);return0;}//结果为a的值为1,b的值为2,a的值是不变的//左移了1位会在原来的值上乘以2,如果左移了两位那么就会乘以4,注意可能会有符号的变化右移操作符移位规则:右移分为两种:1.逻辑右移:左边用0填充,右边丢弃2.算术右移:左边用原来这个值的符号位填充,右边丢弃关于什么时候用逻辑右移什么时候用算术右移这个取决于编译器,一般都是使用算术右移intmain(){inta-10;intba1;printf(%d %d,a,b);return0;}注意对于位移运算,不要移动负数位,这个标准没有定义intmain(){inta10;intba-1;//errorreturn0;位操作符:、|、^、~//按位与|//按位或^//按位异或~//按位取反**符号位是要变的**操作数必须是整数按位与运算对应的二进制位进行与运算,只要有0则为0,两个数都为1才为1intmain(){inta1;// 00000000000000000000000000000001intb-1;// 10000000000000000000000000000001// 11111111111111111111111111111110// 11111111111111111111111111111111---补码//00000000000000000000000000000001// 00000000000000000000000000000001----最终与运算结果为1intcab;printf(%d,c);return0;}| 按位或对应的二进制位进行与运算,只要有1则为1,两个数都为0才为0intmain(){inta1;// 00000000000000000000000000000001intb-1;// 10000000000000000000000000000001// 11111111111111111111111111111110// 11111111111111111111111111111111---补码//|00000000000000000000000000000001// 11111111111111111111111111111111----注意这里是补码我们还要求出来原码,补码的补码就是原码// 10000000000000000000000000000001----最后结果为-1intca|b;printf(%d,c);return0;}^ 按位异或对应的二进制位进行与运算,相同为0,相异为1intmain(){inta1;// 00000000000000000000000000000001intb-1;// 10000000000000000000000000000001// 11111111111111111111111111111110// 11111111111111111111111111111111---补码//^00000000000000000000000000000001// 11111111111111111111111111111110----注意这里是补码我们还要求出来原码,补码的补码就是原码// 10000000000000000000000000000010---求出原码为-2intca|b;printf(%d,c);return0;}~ 按位取反顾名思义就是取反即可intmain(){inta1;// 00000000000000000000000000000001//~11111111111111111111111111111110----注意符号位也进行取反这里得到的是补码需要我们转为原码// 10000000000000000000000000000001---取反// 10000000000000000000000000000010---原码,即对1按位取反的结果是-2intb~a;printf(%d,b);return0;}异或的三个特点a^a0 //相同的两个数字异或是00^aa //0和任何数字异或都为这个数字本身2^8 ^382^3 ^88 //异或支持交换律逗号表达式exp1,exp2,exp3,.....expn逗号表达式就是用逗号隔开多个表达式逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果例如:intmain(){inta0;intb0;intc(a0,ab10,ba1);printf(%d,c);//最终打印结果为11return0;}下标访问[],函数调用()[]下标引用操作符操作数:数组名和下标值intmain(){inta[10];//创建数组a[3]10;//使用下标操作符//这里a和3是操作数return0;}函数调用操作符接受一个或者多个操作数,第一个操作数是函数名,剩余的操作数是函数的参数intadd(inta,intb){returnab;}intmain(){intcadd(2,3);//此处()是函数调用操作符return0;}结构体C语言已经提供了内置类型例如:char,short,int等,但是只有这些内置类型是不够的假设我们想要描述一本书,一个物体这时候单一的内置类型是不够的描述一本书需要名字,类型,作者等,C语言为了解决这个问题,增加了结构体这种自定义的数据类型可以让我们创造适合的类型结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量例如数组,指针,甚至是其他结构体数组:是一组相同类型的元素的集合结构的声明structtag//tag是自定义的{number-list;//成员列表}variable-list;//变量列表,这个列表可有可无结构体变量的定义和初始化structstu//这个结构体类型就是struct stu{//一个或多个成员-这些成员都是用来描述学生的charname[20];intage;charid[11];//....}stu4,stu5,stu6;//这三个变量是在我们声明变量的时候直接创建的,也是全局变量,当然我们这个位置不写也可以structstu3;//全局变量intmain(){structstus1{zhangsan,20,20240106};//初始化也是使用大括号来初始化structstus2{.age20,.namepyz,.id20240203};//这里我们也可以按照我们想要的顺序去初始化,默认情况下都是上面我们定义的顺序去初始化return0;}我们也可以两个结构体一起使用structpeop{charname[30];intage;};structnote{structpeopa[10];//可以存放十个人的信息};intmain(){structpeopp1{pyz,20};structnoteb{{bgh,19},{thl,20}};printf(%s,p1.name);printf(%s,b.a[1].name);// . :结构成员访问操作符//结构体变量.结构体成员return0;}操作符的属性:优先级,结合性C语言的操作符有两个重要的属性:优先级和结合性,这两个属性决定了表达式求值的计算顺序优先级优先级指的是如果一个表达式包含多个运算符,哪个运算符应该优先执行各种运算符的优先级是不一样的35*4;相邻两个操作符的优先级更好的先算,算完之后再和优先级低的去计算上面实例中表达式里面有加法运算符和乘法运算符,由于乘法运算符的优先级比加法的优先级高因此先算5*4而不是35结合性如果两个运算符优先级相同,优先级没办法确定先计算哪个,这时候就看结合性,根据运算符是左结合还是右结合,决定执行顺序大部分运算符是左(从左到右)结合,少数运算符是右(从右到左)结合,比如赋值符().6/3*2;上面例子中/和*的优先级一样,他们都是左结合运算,因此先算6/3运算符的优先级顺序很多,下面是部分优先级顺序建议记住这些常用的圆括号()自增运算符,自减运算符–单目运算符和-乘法*,除法/加法,减法-关系运算符(,)等赋值运算符因为圆括号的优先级最高因此可以使用它来改变其他运算符的优先级详细内容可以参考:https://zh.cppreference.com/w/c/language/operator_precedence表达式求值整型提升C语言中整型算术运算总是至少以缺省(默认)整型类型(int)来进行的为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升如何进行整型提升1.有符号整数提升是按照变量的数据类型的符号位来提升2.无符号整数提升高位补0即可当我们输入的是整数的时候默认是int,输入浮点数的时候默认是double,这个是规定intmain(){chara10;//00000000000000000000000000001010 int型32位 char型8位因此存储到a中需要截断//00001010 --acharb120;//00000000000000000000000001111000//01111000--b//00000000000000000000000000001010--10//00000000000000000000000001111000--120//00000000000000000000000010000010---相加后的结果,如果要存入c需要截断//10000010--ccharcab;printf(%d,c);//结果为-126//%d -以十进制的形式打印一个有符号的整型(int)//11111111111111111111111110000010-补码,这里是有符号整型的提升补充的是有符号位,如果是无符号的话直接补0就好了//10000000000000000000000001111101-原码算术转换如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转为另一个操作数的类型,否则操作就无法进行下去,下面的层次体系称为寻常算数转换longdoubledoublefloatunsignedlongintlongintunsignedintint如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另一个造作书的类型后执行运算inta10;doubleb10.2;//如果a和b相加那么a需要转化为double类型才可以进行计算算术转化的类型最小也是int,也就是说整型提升之后的结果再进行计算的时候才会涉及到算数转换总结本文系统性地介绍了 C 语言中剩余的操作符及相关重要概念主要内容总结如下1. 进制转换二进制转十进制按位权重法每位数值 × 2ⁿ 求和十进制转二进制短除法取余倒序二进制转八/十六进制421 法每 3 位一组和 8421 法每 4 位一组2. 原码、反码、补码有符号整数原码直接翻译、反码符号位不变其余取反、补码反码1计算机存储整型数据在内存中一律以补码形式存放便于统一处理符号位与数值位3. 位移操作符左移左边抛弃右边补 0相当于乘以 2ⁿ右移分为逻辑右移补 0和算术右移补符号位编译器通常采用算术右移注意不要移动负数位行为未定义4. 位操作符按位与有 0 则 0全 1 才 1|按位或有 1 则 1全 0 才 0^按位异或相同为 0相异为 1~按位取反所有位取反包括符号位异或特性aa0、0aa、支持交换律5. 其他操作符逗号表达式从左到右依次执行整个表达式的结果是最后一个表达式的结果下标访问 []操作数为数组名和下标值函数调用 ()第一个操作数为函数名其余为参数结构体自定义复合类型使用.和-访问成员6. 操作符属性优先级决定哪个运算符先执行圆括号最高结合性当优先级相同时决定运算方向左结合或右结合7. 表达式求值整型提升char、short 等类型在运算前自动提升为 int算术转换不同类型操作数运算时按类型层次long double double float unsigned long int … int向上转换核心要点进制转换是理解计算机数据表示的基础。补码是计算机中整数存储和运算的统一形式。位移与位操作直接操作二进制位常用于底层优化和位掩码。操作符的优先级与结合性决定了复杂表达式的计算顺序必要时使用圆括号明确意图。整型提升与算术转换保证了表达式运算的精度和类型安全。掌握这些操作符和底层概念不仅能写出正确的 C 语言代码更能深入理解计算机如何处理数据为后续学习指针、内存管理等高级主题打下坚实基础。