[音乐] 上一讲谈到在高级语言程序的表达式中的各类运算,
会被编译器转换成相应的运算指令,
程序运行时CPU执行这些指令,控制操作数在运算电路中被处理,
本讲主要介绍C语言程序中涉及的各类运算, 包括算术运算、
按位运算、 逻辑运算、 移位运算 位扩展和位截断运算的。
C语言当中最基本的运算就是算术运算,
有无符号数的算术运算,带符号整数的算术运算和浮点数的算术运算。
那么这些算术运算就是加减乘除取模等等这样运算, C语言程序当中还有一类运算是按位的逻辑运算,
按位运算主要是对位串进行各种逻辑运算, 比如说对位串实现掩码操作,
通过一个掩码来进行吸取某些位或者进行
其它的相或,或者是异或、 取反等等这样一些处理。
那么这些处理的对象主要是多媒体数据,比如说视频信息、 图像信息,
还有呢是输入输出控制器里面的一些状态控制信息,
在按位运算当中有四种操作
一种是按位或,还有一种是按位与,按位取反和按位异或,
比如说我们要从数据y当中提取低位字节
并且使高位字节等于0,那么这就是一种吸取操作,我们可以
用按位与的方式对一个特殊的掩码
进行相与,比如说这个地方 的掩码就是00FF,那么00FF和
y相与以后,我们就可以把y的高位变成0, 低位呢,把它吸取出来,比如说当y等于0
B2C的时候,我们得到的结果就是002C 就把低位吸取出来了,
在C语言当中涉及的操作运算,还有 移位运算,移位运算主要是用来提取
一个数据当中的部分信息,也就是多媒体数据当中的某些位或者是
状态信息当中的某些位,另外一个用途是用来进行
对一个数据进行乘以一些特殊的数, 或者除以一些特殊的2的幂次的这样的一些操作。
比如说乘2乘4乘8的时候,我们可以不用乘法运算,而用移位运算来实现,
我们这举个例子,比如说x、 y、
z是8位的带符号整数, 比如说是8位的,那么x是-81,
如果我们要计算x除2和
2乘x,这个时候我们可以不用除法操作,
和乘法操作,而只要用相对的移位运算就可以实现了,
那么-81它的二进制值就是负的10
10001,也就是81可以写成
1加上16 加上64,这个1后面有6个0,那么说明这移位
上的全是2的6次方,就是64加上2的4次方加上1,
就是等于81,因此它的这一个机器数就是后面的这个值,
机器数当中第一位的1实际上表示是一个负数,
后面个位是对这个数值部分,个位取反,末尾加1, 第一位就是1变成0,
这个是负数,所以是1,第一位的1变成0,
这是10111 最后一个1,个位取反末尾加1,那么最后加一个1,
所以最后的机器数是10101111.
这是-81的补码表示, y
是2分之x,我们可以用右移一位来实现,
也就是对这个机器数右移一位,因为它是补码表示的
带符号整数,所以是采用算术移位方式,高位补符号
因为符号位是1,所以高位呢就补个符号1 后面是所有的位向右移一位,
那么最低一位的1被移出去,高位补个符号,
而如果我们要算x乘2的话,我们只要左移一位就行了,左移一位的话,我们只要
把高位移出,低位补0,因此,左移以后得到的是这个值,
这是第二种用途,我们可以用移位方式实现乘除运算,
C语言当中的移位操作,左移是两个小于号,
右移是两个大于号,移多少位,我们只要在这个左移,右移的
操作符后面加上所移的位数,比如说移k位,我们只要把k写在后面就行了,
从这个运算符当中,我们可以看出左移、 右移
它是没有区分,是逻辑移位还是算术移位的,
逻辑移位操作的左移运算符和算术左移的运算符都是一样的,
右移也是一样的,那么到底最终采用的是逻辑移位,还是算术移位呢?
是由被移位的操作数的类型来确定的, 那么这个操作数的类型,如果是一个无符号数,unsigned
它采用的就是一种逻辑移位方式, 那么逻辑移位的时候很简单,
如果是逻辑左移的话,我们只要把左边的高位移出去,
低位补0就行了,如果是右移的话,向右边移的时候,把低位移出去,
高位补0就行了,在左移的时候可能会发生溢出,
因为左移的时候相当于是扩大倍数,左移一位乘以2, 乘以2以后,这个值可能变大,变大以后可能无法用
原来的位数表示,那这样的话就会发生溢出,怎么判断溢出呢?
通常是通过往左移的时候,移出的这个高位是不是1来判断溢出的,
如果移的是1的话,那么就发生了溢出了。
当x是带符号数的时候,
就采用的是算术移位方式,算术移位的时候,如果是左移,
那么跟逻辑移位一样,也是高位移出,低位补0,所以
算术左移和逻辑左移的操作是完全一样的,都是高位移出去,低位补0,
那么左移是相当于乘上2的 多少次方,因此可能发生溢出,
那么对于算术左移的溢出 判断,它是通过看每移一位
那么移位前和移位后,看符号位是否相等,
如果不相等,就发生溢出,相等就没有溢出, 这个也是很好判断的,右移的时候很简单,
低位移出去,高位补符号,因为是算术移位,高位是补符号的,
逻辑移位高位补0,算术移位是高位补符,右移
的时候,那么这个时候跟逻辑移位一样的,右移的时候, 低位有效数字可能会丢失,因为低位移出
移出的可能是1,可能是一个有效数字, 从这我们可以看到刚才我们讲的
这个例子当中,低位移出的是一个1,是一个有效数字, 所以它的值右移一位以后,
有效数字丢失了,所以右移以后的值,这个值是等于-41
这-41是不等于负 81除以2的,因为有有效数字的丢失,
所以移位操作可能结果不正确,
左移的话有可能发生溢出,我们可以看到
左移的时候需要判断是否溢出,判断溢出的方式是看
移位之前的符号位这是1,和移位之后的符号位这是0,
看它们是否相等,如果移出的这个位和新的符号位
不相等,就发生溢出,那显然这个地方是不相等的,移出的这个
位,也只是老的符号位是1,和新的符号位变成了0了。
不相等,因此发生溢出,所以94不等于2乘上负81
所以结果是溢出的,移位的以后的结果不正确
那下面我们可以举一个例子来看一看,移位操作和按位操作
它的这些是怎么样来进行处理的,假定有一个n位的变量x
那么我们要对它进行一些处理,使得它的结果变成这样的一些结果
第一个是说,x
通过一些操作,C语言当中的按位运算和 移位运算以后,使得x的最高有效字节不变
其余各位全变成0,这样的一个结果应该怎么来实现呢
这个x是位数大于8位
那么我们要使得最高有效字节不变 那也就是说,我们要把最高有效字节保留在结果里面
其他位变成0,所以我们先用右移
右移的时候,移的是n减8位,也就是说我们只保留最左边的8位
其他的全部移出去,然后再左移移回来,先右移
把最高的8位保留下来,然后再左移8位,再把这个8
位移回来,那么这样的话,最高的8位就不会发生变化
而左移回来的时候,低位
是补0的,所以其余各位就可以变成0 我们来举个例子看一下,比如说
我们有一个,358F,这样的一个
数字,它的n等于十六位,也就是说这个地方的位数是十六位
如果我们用刚才的那个方式的话,先右移八位
再左移八位,很显然
右移八位以后是等于35
最高有效字节移到右边,然后高位补0
然后呢,再左移八位的时候,我们就可以得到
3500,这样的话就保留了最高有效字节,其余各位都变成0
这种方式,好我们来看第二个
是要求x的最低有效字节不变,其余 各位全变0,那么显然这个应该是
刚才我们刚刚讲过了,应该是和FF,就是最低有效字节不变,那么我们用
这个掩码FF去吸取,其他的呢都用0 去相与,这样的话其他各位都是0,最低有效字节就是不变。
第三个是要求,最低有效字节全变成0,其余各位取反。
那么最低有效字节要变成 0,并且其余各位求反
求反我们通常可以用,异或操作来实现,我们
用1去异或一个值,我们可以看到
1去异或,比如说异或0的时候,它的结果应该是1
和1异或的时候,它的结果应该是0 所以,用1去异或一个
值,那么它应该是等于那个数的反,
0变成1,1变成0 因此我们各位取反,那么前面呢就是1
后面的八位呢是全0,所以x与这样的一个数
相异或以后,那么使得
最低有效字节不变,其余的前面的这些位全部取反
因为前面的这些位是和1异或的,因为这个取反以后就是11
1000后面是八个0,所以最低有效字节
和0异或以后,它还是那个值不变
前面的各位和1按位异或,那么就是各位取反,然后
这样的值再左移八位,很显然就可以把最低有效字节全变成0,其余各位呢不变,
这是第三个操作,第四个是最低有效字节全变成1,其余各位不变,就用
或操作就行了,因为最低有效字节变1,我们只要用1去或
上任何一个数,那它就变成1,其余不变我们用0去相或,做或操作
所以这个比较简单,C语言当中还有逻辑运算
那么逻辑运算其实很简单,就是用于关系表达式当中的运算,比如说
我们在C语言当中要算两个逻辑值
它们的逻辑关系,比如说and这个操作,我们可以用两竖表示或操作
两个&表示AND操作 还有取反操作,就是非运算
刚才我们讲过按位运算 现在讲的是逻辑运算,我们可以看到按位运算当中的
按位「与」 是这个符号,逻辑「与」是这个符号,按位「或」是这个符号,逻辑「或」是这个符号
所以我们可以看出,这两种不同的符号,它们的运算过程是不一样的
前面这种是按位,一位一位的逻辑运算
后面的这种操作是整体的逻辑运算,所以运算的结果,前面得到的是一个位串
后面得到的是一个真假值,这个是要注意的,就是它们容易混淆
还有一种运算呢,是没有运算符的一种运算,是一种
扩展和缩短运算,就是位扩展和位截断运算 这个通常是在类型转换的时候
需要进行的操作,因为它没有专门的操作运算符,是一种隐含的这种运算
扩展运算是一个短类型转换成长类型的时候要进行的,比如说我们把一个
short类型的变量赋给一个int类型的变量 这时候我们需要把十六位的数据
转换成三十二位的数据,因此前面要进行扩展,扩展的时候
如果是unsigned的变量,那么扩展的时候前面是补0,如果是一个signed的
比如说int啦,或者是short啦,这种操作数的话 那变量它应该在前面扩展符号,是一种带符号的
运算,截断运算是反过来,比如说把一个int型的变量赋给一个short型的变量,那- 么就要把
三十二位的值转换成十六位的值,就要把高位呢直接截断
高位就丢弃,因此这时候可能会发生溢出, 我们举个例子,比如说我们现在有四条赋值语句
在C语言当中,si是一个short型的变量十六位的
它的值赋的是负的32768,然后我们把这个si赋给unsigned的
这个无符号数,然后呢我们同时还把这个 si呢赋给一个带符号的三十二位的数
还要把十六位的无符号数再赋给一个三十二位的无符号数
这时候,我们在这个机器上,如果是个大端机,大端机器上
输出si,usi,i和ui这四个变量
它的十进制和十六进制,输出来的结果应该是什么呢?
很显然,si的十进制当然就是负的32768 那么si的机器数,也就是十六进制应该是什么呢?
这个应该是一个最小的负数,最小的负数,我们呢把这个结果
显示出来,这个是个最小的负数,实际上就是1后面十五个0
然后后面十五个0等于负的32768,然后这样的一个机器数
在机器内部,直接赋给一个usi
赋给它的时候,机器数是不变的,直接就是传送给它
但是我们按照十六进制打印的时候,我们用的是无符号数
按无符号数打印的,所以这个机器里面的机器数就是1后面有
十五个0,这个值它按 无符号数解释的时候,应该是等于2的15次方
2的15次方就等于32768 因此,按无符号数打印的时候,它就是32768
然后执行这个赋值语句的时候 我们把这个机器数进行扩展
因为要扩展成三十二位的数,我们是按符号扩展的,因为8000它的
机器数二进制的就是1后面十五个0,也就是第一位的符号位是1
因此,按符号扩展,前面扩展十六个1,就是FF FF
因为带符号数,所以是符号扩展 扩展成十六个1以后,我们打印出来的这个值
它是个带符号数,无码表示的,那么它的值还是 负的32768。
所以我们可以看到一个十六位的 这个带符号数,扩展成三十二位的带符号数
,它的值是不会发生变化的 然后我们把usi十六位的扩展成
三十二位的无符号数的时候,无符号数的扩展是
0扩展,前面补0,因此它的机器数还是等于32768
我们可以看到十六位的无符号数扩展成三十二位的无符号数
它的值也不会发生变化 这是扩展运算
我们知道这个带符号数是符号扩展,无符号数是0扩展
我们下面来举个例子,截断运算 在这有三个赋值语句
32768赋给三十二位的变量i 然后呢,我们对i进行类型转换
转换成一个十六位的带符号数si 再把si赋给一个三十二位的变量
我们来看一看,这个i 赋给了si,si再赋给j,这个i和j是不是相等
我们可以看到32768 刚才我们讲过了它的机器数是1后面15个0
这个i在机器里面,它是一个三十二位的数 因此高十六位呢,应该是0
那高十六位是0,因为机器数是100000.......
三十二位的话,那么前面的这些就都是0,符号位也是0
然后把这个三十二位的数,赋给十六位的一个变量
我们要进行截断,截断的时候就把高十六位直接丢掉 因此剩下来的低十六位就是80 00。
80 00我们打印出来,按照十进制打印的时候 它是带符号数。
这个带符号数我们可以看出它是一个最小的负数
十六位的最小负数,就是负的32768,负的32768
再把它赋给三十二位的一个变量,那么80 00这个机器数
进行符号扩展,扩展的时候最高位 是1,因此扩展十六个1出来就是FF
FF 那么这个机器数对应的增值应该是等于负的32768
我们可以看到i是正的32768,通过两次 强制类型转换就变成一个负数了。
所以这里面我们可以 看出对i截断的时候发生了"溢出"。
i等于32768 截断为十六位数的时候,就是这个三十二位数截断为这个十六位数的时候
那么实际上,超出了十六位能表示 的最大值了,因为这个32768
它用十六位的补码表示的时候是无法表示的
因为十六位补码表示的最大的这个数应该
是正的32767
就是011......1,正的 32767。
所以这个是个最大数 一截断以后呢,就会发生"溢出"
,这是截断的操作 [音乐]
[音乐]