10 |
| |
按位或 |
表达式|表达式 |
左到右 |
双目运算符 |
11 |
&& |
逻辑与 |
表达式&&表达式 |
左到右 |
双目运算符 |
一、常用的进制分类
二进制:由0和1组成
八进制:由0~7数字组成
十进制:由0~9数字组成
十六进制:由0~9和A~F组成
二、常用进制的转化
1、二进制转换
二进制转八进制
二进制与八进制对应关系如下图 :
二进制 | 八进制 | ||
000 | 0 | ||
001 | 1 | ||
010 | 2 | ||
011 | 3 |
100 | 4 | ||
101 | 5 | ||
110 | 6 | ||
111 | 7 |
计算方法: 我们采用取三合一的方法进行进制转换
取三合一:将二进制数从右至左依次分组,每三个二进制数为一组,一组代表一个八进制数,不足的在左侧补零
二进制 十进制
0011 0100 ---------- 000 110 100 ------------ 0 6 4
0011 1101 0010 ---------- 011 111 010 010 ----------- 3 7 2 2
二进制转十进制
计算方法:从右至左用二进制的每个数的乘以2的相应位置的次方,最后相加
二进制 十进制
00101010 42
0 * 2^0 + 1 * 2^1 + 0 * 2^2 + 1 * 2^3 + 0 * 2^4 + 1 * 2^5 + 0 * 2^6 + 0 * 2^7 = 42
二进制转十六进制:
二进制和十六进制对应关系如下图:
二进制 | 十六进制 |
0000 | 0 |
0001 | 1 |
0010 | 2 |
0011 | 3 |
0100 | 4 |
0101 | 5 |
0110 | 6 |
0111 | 7 |
1000 | 8 |
1001 | 9 |
1010 | A |
1011 | B |
1100 | C |
1101 | D |
1110 | E |
1111 | F |
计算方法:利用八四二一码进行加法运算。
二进制 十六进制
8 4 2 1 8 4 2 1 0+0+2+0
0 0 1 0 ----------------------------- 2
8+0+2+1
1 0 1 1 ----------------------------- B
8+4+0+1 0+4+0+1
1 1 0 1 0 1 0 1 ------------------------------ D5
2、八进制转换
八进制转二进制
计算方法:利用二进制与八进制关系图进行取一分三
八进制 二进制
3 -------------------------------------- 011
7 -------------------------------------- 111
5 -------------------------------------- 101
tips:二进制数的每次加一的规则是,满二进一,即 011 + 1 = 100
八进制转十进制
计算方法:从右至左用八进制的每个数的乘以8的相应位置的次方,最后相加
八进制 十进制
3 * 8^0
3 ------------------------------------------ 3
1 * 8^1 + 7 * 8^0
17 ------------------------------------------ 15
3 * 8^2 + 2 * 8^1 + 5 * 8^0
325 ------------------------------------------ 213
八进制转十六进制
计算方法:先将八进制转为二进制,然后用二进制利用八四二一码转为十六进制
八进制 二进制 十六进制
2+1
3 ----------------------- 0011 ---------------------- 3
4+2+1、2+1
1 6 3 ----------------------- 0111 0011 ----------------------- 7 3
8+2、8+4
2 5 4 ----------------------- 1010 1100 ---------------------- A C
3、十进制转换
十进制转二进制
计算方法:凑或除
除:除而取余,逆序输出
十进制 二进制
168 1010 1000
168 / 2 = 84 ...... 0
84 / 2 = 41 ...... 0
42 / 2 = 21 ...... 0
21 / 2 = 10 ...... 1
10 / 2 = 5 ...... 0
5 / 2 = 2 ...... 1
2 / 2 = 1 ...... 0 逆序输出
1 / 2 = 0 ...... 1 --------------------------- 1010 1000
凑:利用八四二一的进阶 128 64 32 8 4 2 1进行计算
!!(该进阶其实就是2^n...... 、2^2、2^1、2^0,当你发现不够用时就增加2的次方)!!
十进制 二进制
168 ----------------------------------- 1010 1000
168 = 128 + 32 +8
128 64 32 16 8 4 2 1
1 0 1 0 1 0 0 0 ------------------------------- 1010 1000
十进制转八进制
计算方法:除八取于,逆序输出
十进制 八进制
145 ----------------------------------- 221
145 / 8 = 18 ...... 1
18 / 8 = 2 ...... 2 逆序输出
2 / 8 = 0 ...... 2 ---------------------------- 221
十进制转十六进制
计算方法:除十六取余,逆序输出
十进制 十六进制
145 -------------------------------- 91
145 / 16 = 9 ...... 1
9 / 16 = 0 ...... 9 --------------------- 91
1534 -------------------------------- 5FE
1534 / 16 = 95 ...... 14 => E
95 / 16 = 5 ...... 15 => F
5 / 16 = 0 ...... 5 => 5 ----------- 5FE
4、十六进制转换
十六进制转二进制
计算方法:利用二进制与十六进制对应关系表直接转换
十六进制 二进制
7E ---------------------------------------------- 0111 1110
8 4 2 1
7=>7 0 1 1 1 先分开后合并
E=>14 1 1 1 1 ---------------------------------- 0111 1110
十六进制转八进制
计算方法:先转为二进制,后二进制利用取三合一的办法转换位为八进制
十六进制 二进制 八进制
001 111 100
7C ----------------------- 0111 1100 ------------------- 1 7 4
001 101 011
AB ----------------------- 1010 1011 ------------------- 2 5 3
000 110
6 ----------------------- 0110 ------------------- 6
十六进制转十进制
计算方式:从右至左用十六进制的每个数的乘以16的相应位置的次方,最后相加
十六进制 十进制
6A ------------------------------------- 106
A => 10
6A = 6 * 16^1 + A * 16^0
= 6 * 16^1 + 10 * 16^0
= 106
5、最简单的进制转换工具---计算器
其实还有一种更简单直接的办法,就是利用我们电脑自带的计算器。
在电脑桌面左下角搜索处直接搜索计算器即可。
这就是电脑自带的计算器了,当然你也可以在搜索到时鼠标右击将其固定在任务栏或者开始菜单中以便下次使用。
左侧的四个大写字母表示四种常用的进制,其中四个进制从上至下的排序分别为:
HEX:十六进制(计算机左侧栏的ABCDEF在需要进行十六进制运算时才会可以点击~)
DEC:十进制
OCT:八进制
BIN:二进制
鼠标点击某一个进制,并输入任意数字,即可得到该进制转化为其它进制的结果。
三、原码、反码、 补码
1、什么是原码、反码、补码?
整数的三种二进制表示方法叫做原码、反码、补码。
三种方法均有符号位和数值位两部分,符号位都是用0“表示正”,用1表示“负”,而数值位最高位的一位被当作是符号位,其余的都是数值位。
正整数的原码、反码、补码均相同,且符号位固定为1。
负整数的原码、反码、补码均不同,且符号位固定为0。
移码的符号位另算!!!
tips:对于整形来说:数据存放内存中其实存放的是补码。 为什么呢?
在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀ 处理; 同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是 相同的,不需要额外的硬件电路。
!!!所以在内存中的运算都是二进制的补码之间在运算!!!
2、已知真值求其对应原码、反码、补码和移码
在计算机组成原理这本书中,我们为了区别一般书写表示的数和机器中这些编码表示的数,通常将前者称为真值,将后者称为机器数或者机器码。
!!!(如果不是二进制表示形式记得先将其它进制形式转为二进制形式再进行计算)!!!
已知真值求原码
计算方法:x为正,符号位变为0,其余位不变;x为负,符号位变为1,其余位不变
我们设定 [x]原 为机器数, x为真值。
①x为正时,x= + 1001,[x]原 = 0 1001
②x为负时,x= - 1001,[x]原 = 1 1001
已知真值求反码
计算方法:x为正,真值的反码与原码相同;x为负,符号位变为1,其余位各位取反
①x为正时, x =(+122) =(+1111010)
[x]反 = [x]原 = 0 1111010
②x为负时, x =(-122) =(-1111010)
[x]原 = 1 1111010
||
v
[x]反 = 1 0000101
已知真值求补码
计算方法:x为正,真值的补码与原码相同;x为负,补码等于反码加一
①x为正时, x = (+122)= (+1111010)
[x]补 = [x]原 = 0 1111010
②x为负时, x = (-122) = (-1111010)
[x]原 = 1 1111010
||
v
[x]反 = 1 0000101
||
v
[x]补 = 1 0000110 求补码得先要求反码
已知真值求移码
计算方法:x为正,符号位变为一其余位不变;x为负,符号位变为0其余各位取反加一
①x为正时, x = +10101
[x]移 = 1 10101
②x为负时, x = -10101
[x]移 = 0 01011
列表总结四种码的变换方法
原码 | 反码 | 补码 | 移码 | |
真值为正 | 符号位变为0,其余各位不变 | 同原码 | 同原码 | 符号位变为1,其余为不变 |
真值为负 | 符号位变为1,其余各位不变 | 符号位变为1,其余各位取反 | 反码加一 | 符号位变为0,其余各位取反加一 |
四、移位操作符
!!!记住左移右移操作符在进行与或者其它运算时,绝对不是单纯的把移出去的那一个进行运算,同时也要牢记各类运算的运算规则!!!
1、左移位操作符:<<
箭头朝向:向左
运算方法:该数的二进制形式
移位规则:左边抛弃,右边补0
int num = 10; 00000000 00000000 00000000 00001010 num的二进制表示 int a = num << 1; 00000000 00000000 00000000 00010100 a的二进制表示 //移位后num的值不发生改变
2、右移位操作符:>>
箭头朝向:向右
运算方法:该数的二进制形式
移位规则:①逻辑右移:左边用0填充,右边丢弃
②算术右移:左边用原该值的符号位填充,右边丢弃
//逻辑右移: int num = -1; 11111111 11111111 11111111 11111111 num的二进制表示 int a = num >> 1; 01111111 11111111 11111111 11111111 a的二进制表示 //算术右移: int num = -1; 11111111 11111111 11111111 11111111 num的二进制表示 int a = num >> 1; 1 1111111 11111111 11111111 11111111 a的二进制表示 //这个被隔开的1就是符号位,末尾其实已经丢弃一个1了 //移位后num的值不发生改变
3、小拓展
对于一个整数,进行左移时结果为原值的两倍,右移时结果为原值的一半,当然,当右移时结果位原值一半时,若原值位奇数那么就向下取整
int main() { int num = 3; int a = num << 1; //当左移一位时 a = 6 ,当左移两位,即num << 2时,a = 12. int b = num >> 1; //当右移一位时 b = 6 ,当右移两位,即num << 2时,b = 0. printf("%d", a); printf("%d\n", b); return 0; }
注意:移位操作符的操作数只能为整数,同时该数不能为负数否则会报错!!!
num >> 操作数
num << 操作数
五、位操作符
1、按位与:&
内存中的运算方式:补码
运算规则:同1为1,其余为0
int a = 3; int b = -5; int c = a & b; // 00000000 00000000 00000000 00000011 3的补码 // 11111111 11111111 11111111 11111011 -5的补码 // 00000000 00000000 00000000 00000011 c的补码 printf("%d",c); // c = 3
2、按位或:|
内存中的运算方式:补码
运算规则:同0为0,其余为1
int a = 3; int b = -5; int c = a | b; // 00000000 00000000 00000000 00000011 3的补码 // 11111111 11111111 11111111 11111011 -5的补码 // 11111111 11111111 11111111 11111011 c的补码 //负数的补码字符为固定为1,正数的补码字符为固定为0,所以c为负数 // 11111111 11111111 11111111 11111010 c的反码,负数的补码=反码-1 // 10000000 00000000 00000000 00000101 c的原码,负数的原码=反码各个位取反 printf("%d",c); // c = -5
3、按位异或:^
内存中的运算方式:补码
运算规则:相同为0,不同为1
int a = 3; int b = -5; int c = a ^ b; // 00000000 00000000 00000000 00000011 3的补码 // 11111111 11111111 11111111 11111011 -5的补码 // 11111111 11111111 11111111 11111000 c的补码 //负数的补码字符为固定为1,正数的补码字符为固定为0,所以c为负数 // 11111111 11111111 11111111 11110111 反码,负数的补码=反码-1 // 10000000 00000000 00000000 00001000 原码,负数的原码=反码各个位取反 printf("%d",c); // c = -8
4、按位取反操作符:~
内存中的运算方式:补码
运算规则:二进制是0变为1,二进制是1变为0,符号位是+变为-
int main() { int n = 0; int a = ~n; //按位取反 printf("%d\n",a); // 00000000 00000000 00000000 00000000 n的二进制形式 // 11111111 11111111 11111111 11111111 a的二进制形式 }
注意:它们的操作数必须为整数!!!
操作数 ^ 操作数
操作数 & 操作数
操作数 | 操作数
六、简单小练
题目一:在不创建第三个变量的前提下,实现两个数的交换
#include <stdio.h> int main() { int a = 5; int b = 10; a = a^b; //将a与b异或运算的值赋值给a b = a^b; //此时a^b等价于a^b^b,由于b^b=0,所以a的值被赋值给了b a = a^b; //由于上一次只是对b进行了赋值所以此时a仍然等于a^b,故a^b就等价于a^b^a=b,实现将b的值 printf("a = %d b = %d\n", a, b); //赋值给a,两数交换完成 return 0; }
题目二:求一个整数存储在内存中的二进制中1的个数
//因为是内存中所以,进行运算的是二进制的补码 方法一: int Return(int a) { int count=0; for (int i = 0; i < 32; i++) { if (((a >> i) & 1) == 1) //一共三十二位,每次向右移位一位并进行位与运算判断结果是否为 //1,若是则count++,count的最终值代表二进制数中的所有1的个数 { //c语言进行按位与运算时系统会默认将数字转为二进制数后再计算 count++; } } return count; } int main() { int a; printf("请输入要判断的数字:"); scanf_s("%d",&a); int count = Return(a); printf("二进制数中一共右%d个1",count); }
题目三:修改一个整数二进制的某一个位置的数字(常见于单片机嵌入式)
//将a的二进制中的第五位改为1(从右向左数) int main() { int a = 13; //00000000 00000000 00000000 00001101 a的二进制形式 a = a | (1 << 4); //00000000 00000000 00000000 00000001 1的二进制形式 printf("%d\n",a); //00000000 00000000 00000000 00010000 1 << 4后的二进制形式 //1 << 4 后的结果与a进行或运算,同零为零,其余为一 //00000000 00000000 00000000 00011101 //a的最后结果 } //将a的二进制中的第五位改为0(从右向左数) int main() { int a = 13; a = a | (1 << 4); //00000000 00000000 00000000 00011101 //a第一次改变后的二进制形式 //11111111 11111111 11111111 11101111 //~(1 << 4)后的结果 a = a & ~(1 << 4); //二者进行与运算,同一为一,不同为0 //00000000 00000000 00000000 00001101 a的最后结果 printf("%d\n",a); }
题目四:判断一个整数是否是2的n次方?
//2的n次方这个整数的二进制只有一个1 int main() { int n = 0; scanf_s("%d",&n); if((n & (n - 1)) == 0) { printf("yes\n"); } else { printf("no\n"); } return 0; }
题目五:打印整数二进制的奇数位和偶数位
void PrintNum(int i) { int a = 31; //打印a的整个二进制序列 printf("该数的二进制形式为:"); while(a>=0) { printf("%d ", (i >> a) & 1); //每次都向右移a位,且每次移位后a减一 a--; //按位与操作符&,同一为一,其余为0 } //打印a偶数位上二进制序列 printf("\n偶数位:"); for (a = 30; a >= 0; a -= 2) //两个偶数或者两个奇数之间位数相差2 { printf("%d ", (i >> a) & 1); } //打印a奇数位上二进制序列 printf("\n奇数位:"); for (a = 31; a >= 1; a -= 2) //两个偶数或者两个奇数之间位数相差2 { printf("%d ", (i>> a) & 1); } } int main() { int i; printf("确定要输入的整数:>"); scanf_s("%d", &i); PrintNum(i); return 0; }
题目六:求两个数二进制中不同位的个数
Judge(int i, int j) { int a; int count = 0; for (a = 0; a < 32; a++) if (((i >> a) & 1) != ((j>>a) & 1)) { count++; //i和j在该位置上相同count就++记录一次 } return count; } int main() { int m, n; printf("请输入两个整数:"); scanf_s("%d %d", &m, &n); int count = Judge(m, n); printf("%d", count); }
tips:关于 “if (((i >> a) & 1) != ((j>>a) & 1))” 的解释
i和j每次都右移a位相当于每次都是这俩数的同一位在做比较,然后再整体与1做与运算,由于与运算是同1为1其余为0,所以每次i和j的二进制位中后面的部分与1与运算后结果都为0,只有最后一位才算的上是真正在和1做与运算,如果两个与运算结果分别为0和1那么证明该位置上两个数字的二进制分别为1和0,即在该位置上两个数的二进制位不同。
七、逗号表达式
形式:表达式1,表达式2,表达式3,......,表达式n
逗号表达式就是用逗号隔开的多个表达式,所有表达式从左至右依次执行,整体的结果是最后有一个表达式的结果。
情况一:
int main() { int a = 1; int b = 2; int c = (a>b,a=b+10,a,b=a+1); printf("%d",c); //c=13 }
情况二:
int main() { int a = 1; int b = 2; if (a =b + 1, c=a / 2, d > 0) //如果d>0结果为假,那么就不执行,跟前面的无关 printf("%d",c); }
情况三:(了解即可)
//同一种功能实现的方式不同 //方法一: int main() { a = get_val(); count_val(a); while (a > 0 ) { // 业务处理 //代码冗余 a = get_val(); count_val(a); }
//方法二: int main() { 如果使⽤逗号表达式,改写: while (a = get_val(), count_val(a), a> 0 ) { // 业务处理 //明显比方法一更加的方便 } }
八、下标访问[]、函数调⽤()
1、 下标引⽤操作符 [ ]
操作数:数组名+数组下标/索引值
int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; printf("%d\n",arr[5]); //下标引用操作符 //[]操作符的操作数是:arr 和 5 //类似于 3 + 5 ,其中3和5是+的操作数 }
2、函数调用操作符 ( )
操作数:函数名+参数名(tips:最少有一个操作数)
#include <stdio.h> int Add(int x,int y) { return x + y; } void test() { printf("hehe\n"); } int main() { //函数调用 int ret = Add(2,3); //()的操作数:Add,2,3 printf("%d\n",ret); return 0; }
3、解惑---sizeof是不是函数?
疑惑原因:我们为了计算变量大小的时候会写sizeof(变量名),这里看起来是既有操作符()又有操作数sizeof和变量名。但实际上sizeof不是函数,而是操作符。我们通过代码来解释这个问题:
//sizeof不是函数,是操作符 int main() { int a = 10; int n = sizeof a ; //如果sizeof是函数那么必须写成sizeof(a)的形式,但是我们发现不用加()也可以得 //到n的值,故不是。 printf("%d\n",n); }
九、操作符的属性:优先级、结合性
c语言的操作符有两个重要的属性:优先级和结合性,这两个属性在一定程度上决定了表达式求值的计算顺序
1、优先级
优先级是指:如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏,哪个应该后执行。各种运算符的优先级是 不⼀样的。比如:
3 + 4 * 5 ;
上⾯示例中,表达式 3 + 4 * 5 ⾥⾯既有加法运算符( + ),⼜有乘法运算符( * )。由于乘法
的优先级⾼于加法,所以会先计算 4 * 5 ,⽽不是先计算 3 + 4 。
2、结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右 结合(从右到左执⾏),⽐如赋值运算符( = )。
5 * 6 / 2 ;
上⾯⽰例中, * 和 / 的优先级相同,它们都是左结合运算符,所以从左到右进行计算,运算符的优先级顺序很多,下⾯是部分运算符的优先级顺序(按照优先级从⾼到低排 列),建议⼤概记住这些操作符的优先级就⾏,其他操作符在使⽤的时候查看 运算符优先级和结合性关系表即可。
• 圆括号( () )
• ⾃增运算符( ++ ),⾃减运算符( -- )
• ⼀元运算符( + 和 - )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
• 赋值运算符( = )
由于圆括号的优先级最⾼,可以使⽤它改变其他运算符的优先级。
3、运算符优先级和结合性关系表
优先级 |
运算符 |
名称或含义 |
使用形式 |
结合方向 |
说明 |
1 |
[] |
数组下标 |
数组名[常量表达式] |
左到右 |
-- |
() |
圆括号 |
(表达式)/函数名(形参表) |
-- |
||
. |
成员选择(对象) |
对象.成员名 |
-- |
||
-> |
成员选择(指针) |
对象指针->成员名 |
-- |
2 |
- |
负号运算符 |
-表达式 |
右到左 |
单目运算符 |
~ |
按位取反运算符 |
~表达式 |
|||
++ |
自增运算符 |
++变量名/变量名++ |
|||
-- |
自减运算符 |
--变量名/变量名-- |
|||
* |
取值运算符 |
*指针变量 |
|||
& |
取地址运算符 |
&变量名 |
|||
! |
逻辑非运算符 |
!表达式 |
|||
(类型) |
强制类型转换 |
(数据类型)表达式 |
-- |
||
sizeof |
长度运算符 |
sizeof(表达式) |
-- |
3 |
/ |
除 |
表达式/表达式 |
左到右 |
双目运算符 |
* |
乘 |
表达式*表达式 |
|||
% |
余数(取模) |
整型表达式%整型表达式 |
4 |
+ |
加 |
表达式+表达式 |
左到右 |
双目运算符 |
- |
减 |
表达式-表达式 |
5 |
<< |
左移 |
变量<<表达式 |
左到右 |
双目运算符 |
>> |
右移 |
变量>>表达式 |
6 |
> |
大于 |
表达式>表达式 |
左到右 |
双目运算符 |
>= |
大于等于 |
表达式>=表达式 |
|||
< |
小于 |
表达式<表达式 |
|||
<= |
小于等于 |
表达式<=表达式 |
7 |
== |
等于 |
表达式==表达式 |
左到右 |
双目运算符 |
!= |
不等于 |
表达式!= 表达式 |
8 |
& |
按位与 |
表达式&表达式 |
左到右 |
双目运算符 |
9 |
^ |
按位异或 |
表达式^表达式 |
左到右 |
双目运算符 |
10 |
| |
按位或 |
表达式|表达式 |
左到右 |
双目运算符 |
11 |
&& |
逻辑与 |
表达式&&表达式 |
左到右 |
双目运算符 |
12 |
|| |
逻辑或 |
表达式||表达式 |
左到右 |
双目运算符 |
13 |
?: |
条件运算符 |
表达式1? 表达式2: 表达式3 |
右到左 |
三目运算符 |
14 |
= |
赋值运算符 |
变量=表达式 |
右到左 |
-- |
/= |
除后赋值 |
变量/=表达式 |
-- |
||
*= |
乘后赋值 |
变量*=表达式 |
-- |
||
%= |
取模后赋值 |
变量%=表达式 |
-- |
||
+= |
加后赋值 |
变量+=表达式 |
-- |
||
-= |
减后赋值 |
变量-=表达式 |
-- |
||
<<= |
左移后赋值 |
变量<<=表达式 |
-- |
||
&= |
按位与后赋值 |
变量&=表达式 |
-- |
||
^= |
按位异或后赋值 |
变量^=表达式 |
-- |
||
|= |
按位或后赋值 |
变量|=表达式 |
-- |
15 |
, |
逗号运算符 |
表达式,表达式,… |
左到右 |
-- |
双目运算符就是符号两边有两个需要操作的内容,单目同理。符号相同但是有多种意思时要注意它到底是单目还是双目的运算符,就比如&。
十、表达式求值
1、整型提升
C语⾔中整型算术运算 总是⾄少以整数类型(int)的精度 来进⾏,为了获得这个精度,表达式中的字 符类型(char) 和 短整型(short) 操作数在使⽤之前会被转换为 普通整型(int / unsigned int) ,这种转换称为 整型提升 。
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀ 般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度。 通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执⾏运算。
两个char类型相加
char a = 5; char b = 8; char c = a + b;
这是不进行整型提升的时候:
这时进行整形提升之后:
很明显在两个数进行整型提升后,虽然可能前面int型的内存还没用完,但是可以有效避免因为超出内存分配给它的范围导致的结果被丢弃。同时也增加了结果的精确度。
如何进行整型提升?
①: 有符号整数提升是按照变量的数据类型的符号位来提升的,补码符号位为0则整型提升时高位补零,补码符号位为1则整型提升时高位补1.
② :⽆符号整数提升,⾼位补0
!!!无符号就是有unsigned前缀的!!!
!!!在vs中char==》signed char!!!
!!!不同编译器下char代表的可能是signed char也可能是signed char!!!
正数的整型提升
int main() { char a = 5 ; //5 - int类型 - 4字节 - 32bit //00000000 00000000 00000000 00000101 --- 5的补码 //00000101 - 5的补码被截断后的结果赋值给a char b = 127; //00000000 00000000 00000000 01111111 --- 127的补码 //01111111 - 127的补码被截断后赋值给b
截断原因:char的二进制位中只有八个比特位,而5和127是整型二进制位中有三十二个比特位,想要分别存入a和b中只能截断前面多余的二进制位 ==》一米的洞塞进四米的棍子
char c = a + b; //发生整型提升:a和b的二进制位发生改变 //00000000 00000000 00000000 00000101 ---- a整型提升后的补码 //00000000 00000000 00000000 01111111 ---- b整型提升后的补码 //00000000 00000000 00000000 10000100 ---- a + b 的补码 //10000100 - c (a+b的补码被截断后存储到c中) printf("%d\n",c); //打印c的时候还是要进行整型提升,因为%d是按照打印10进制的形式打印有符号的整数 //1111111 11111111 11111111 10000100 ---c整型提升后的结果(补码) //10000000 00000000 00000000 01111100 ---c的原码 //-124 -打印c的结果 }
负数的整型提升
请查看我的另一篇文章《数据在内存中的存储》:http://t.csdn.cn/mmPjb
里面附带了更多的习题能更好的帮你理解整型提升
2、算术转换
如果某个操作符操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则代码无法执行。
下面的层次体系称为寻常算术转换:
- long double
- double
- float
- unsigned long int
- long int
- unsigned int
- int
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外一个操作数的类型后执行运算。
3、问题表达式解析
①问题表达式一
a*b + c*d + e*f 我们通过操作符的属性,无法确定这个表达式的唯一计算路径!! //第一种运算顺序 //a*b 、c*d 、+ 、e*f、+ (只能说相邻的*的计算优先级比+高,但是e*f明显要离c*d还有一段距离所以可以在c*d之后进行+然后再进行e*f) //第二种运算顺序 //a*b、c*d、e*f、+、+ (同样的也可以先计算e*f然后再计算+) 如果abcdef是变量的情况还好可以用括号进行更改, 但是如果它们是多个表达式,那么就会出问题。
②问题表达式二
操作符的优先级只能决定自减--的运算在+的前面,但是我们无法得知,+操作符的左操作数的获取是在右操作数之前还是之后,所以结果不可预测!!!
int c = 5; c + --c //第一种运算顺序 --c、+、c 4 + 4 = 8 //第二种运算顺序 c、+、--c 5 + 4 = 9
③问题表达式三
函数的调⽤先后顺序⽆法通过操作符的优先级确定!!!
int fun() { static int count = 1; //static修饰局部变量,生命周期延长 return ++count; //count会有三个结果2、3、4 } //即fun被主函数三次调用时会调用2、3、4但是调用顺序未知 int main() { int answer; answer = fun() - fun() * fun(); printf("%d\n",answer); //虽然结果输出为-10,看起来是顺序调用2、3、4但是其实实际调用顺序依然未知。 return 0; }
4、总结
!!!即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的 计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别负责的表达式!!!