整数,就是没有小数部分的数值了,这有什么复杂的吗?也许没那么复杂,不过,我们还是从一个简单的算术运算说起吧。

很简单的计算问题,5除以3等于多少?在学习数学时,应该等于1.666……,6循环。但是,在C#代码中,结果可能和大家的习惯不太一样,如下面的代码(可以在Program.cs文件中进行测试)。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(5 / 3);
            Console.WriteLine(5 % 3);
            Console.WriteLine(5 / 3f);
        }
    }
}

本例中,我们使用了两个运算符,分别是除法运算符(/)和取余数运算符(%)。大家可以看到两个除法运算的区别,第二个除法运算中的除数定义为一个float类型,也就是单精度浮点类型。先看一下运算结果,如下图。

enter image description here

第一个运算是整数除以整数的运算,我们可以看到,其运算结果也是整数,只是将多出的部分舍弃了。

第二个运算是取余数运算,也称为取模运算。

第三个运算是一个整数和一个浮点数的运算,其运算结果是一个浮点数。也许这个才是我们比较熟练的除法运算结果。

通过这个示例,大家可以看到整数参与的除法相关运算的特点,即:

  • 整数除以整数,结果依然为整数。
  • 整数除以整数的余数还是整数。
  • 整数与浮点数参与的除法,运算结果是浮点数。

整数的除法的确有点复杂,而对于加、减、乘运算就相对简单多了;在日常的数学计算中,整数与整数的加、减、乘法运算结果都是整数,代码中也是这样,如下面的代码演示了int类型变量的运算。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 10, y = 99;
            Console.WriteLine(x + y);
            Console.WriteLine(x - y);
            Console.WriteLine(x * y);
        }
    }
}

代码执行结果如下图所示。

enter image description here

也许大家会想,除了除法运算,整数的计算也没什么特别复杂的;不过,实际开发中,还是有一些问题需要注意,下面主要讨论溢出和位运算;当然,如果你感觉这有些复杂,也可以先跳过,在后续的学习和工作中,如果需要应用本部分内容时再回过头来学习。

溢出

前一课,我们已经看到了不同数值类型的取值范围,那么,如果在运算过程中超出了这一范围会出现什么情况呢?答案就是溢出,我们来看下面一个例子。

static void Main(string[] args)
{
    Console.WriteLine(unchecked(int.MaxValue + 1));
}

代码中,int.MaxValue字段表示int类型可取的最大值(应该是个正数,对吧!?),那么,这个值再加1会是什么结果呢?结果是-2147483648,这是因为产生出溢出,也就是超出了int类型允许的取值范围。结果虽然看起来不太靠谱,但这是有规律的,稍后了解位运算相关内容后,也许大家就知道这个答案是怎么来的了。

实际开发中,溢出只是在一些极端情况下会出现,但我们应该知道如何处理这一情况。如上述的unchecked()就是指定圆括号中的表达式不进行溢出检查;而在默认情况下,是会进行溢出检查的。

与unchecked关键字对应的是checked,指定表达式或代码块需要进行溢出检查,如下面的代码。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            checked
            {
                int x = int.MaxValue;
                int y = x + 1;
                Console.WriteLine(y);
            }
        }
    }
}

本例中,我们使用checked定义了一个代码块,就是说明在这段代码中需要进行溢出检查,当产生溢出,也就是数据超出了类型的数值范围时,就会抛出异常,程序就会中断运行,如下图所示。

enter image description here

如果将checked关键字修改为unchecked,则代码不会抛出异常,如下图所示。 enter image description here

我们可以看到,溢出后的结果并不是随机的,下面,我们就来了解整数运算背后的基本原理。

位运算

毫无疑问,我们更熟悉十进制数的各种运算,但在计算机内部却是二进制,其取值只0和1,对应数字电路的断开和连通状态。下面,我们了解一下十进制和二进制之间的转换操作。

首先是十进制整数如何转换为二进制数,我们以数值19为例。用19除以2,商为9,余数为1;用9除以2,商为4,余数为1;用4除以2,商为2,余数为0;用2除以2,商为1,余数为0,此时结束计算。将最后一个商作为二进制数的最高位,然后向前取余数,最终得到10011,也就是19的二进制数。计算过程如下图所示。

enter image description here

反过来,如何将二进制数转换为十进制呢?如果二进制位上是1,就使用2n-1计算出这个数位上的十进制数,其中,n是从右向左数(从低位向高位数)第几位;最后,将所有数据相加,如10011,计算公式就是:24+21+20=16+2+1=19。

现在,我们应该知道十进制和二进制之间的基本转换关系,但这只是无符号整数的处理方式,对于有符号整数,二进制的最高位是符号位,当其为0时,表示0或正整数,此时数制转换规则与无符号整数相同。当最高位为1时,表示一个负整数,其后的数据是负整数绝对值的二进制数的补码。

一个二进制数的补码是其各位取反后加1的结果,如0010011的补码就是1101101,那么sbyte类型的-19就是11101101。

前面,我们讨论的整数的溢出,本质就是数据超出了数值类型允许的数据范围,导致二进制码转换为十进制数据时产生了歧义。

在C#中,还提供了一些关于二进制数据的运算,下面分别介绍位逻辑运算和位移运算。

位逻辑运算

位逻辑运算是将对应的二进制位进行以下运算后的结果:

  • 位与运算,使用&运算符,两个数都为1时结果为1,否则为0。
  • 位或运算,使用|运算符,两个数有一个为1时结果为1,都为0时结果为0。
  • 位取反运算,使用!运算符,1变0,0变1。
  • 位异或运算,使用^运算符,当两个数不同时结果为1,相同时为0。

位逻辑运算在快速处理标识类数据时非常高效,比如,我们看到过很多标识数据是0、1、2、4、8、……等,除了零表示没有数据,其它数据都是2的次方数。下面,我举个简单的例子。

using System;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int flag1 = 1;
            int flag2 = 2;
            int flag8 = 8;
            int flags = flag1 | flag8;
            //
            Console.WriteLine(flag1 & flags);
            Console.WriteLine(flag2 & flags);
            Console.WriteLine(flag8 & flags);
        }
    }
}

先看执行结果,如下图所示。

enter image description here

代码中,flag1、flag2和flag8的值分别是1、2和8,flags的值是通过位或运算(|)组成的组合值,包括1和8。判断指定的值是否在flags组合值中时,我们使用位与运算(&),如果flags包含某值,则运算结果就是这个值,如果不flags不包含某值,则返回0值。

通过二进制来看运算结果,可以先看一下1、2、8对应的二进制数,分别是0001、0010、0100;而flags变量的值就是0001|0100,运算结果是0101。判断flags是否包含某值时,如1,其运算就是0001&0101,结果为0001,即1;判断2值的计算就是0010&0101,结果为0000,即0;而判断8值的计算就是0100&0101,结果为0100,即8。

位移运算

位移运算包括位左移运算和位右移运算,分别使用<<和>>运算符。我们通过一个简单的示例来看一下位移运算的应用,如下面的代码。

static void Main(string[] args)
{
    Console.WriteLine(128 >> 3);
    Console.WriteLine(8 << 3);
    Console.WriteLine(-128 >> 3);
    Console.WriteLine(-8 << 3);
}

代码运行结果如下图。

enter image description here

从运算结果中,我们可以看到,位左移运算,如“x << n”实际执行的是x*2n运算,而“x >> n”执行的是x/2n运算。进行位移运算时,应注意数据的范围,如果过大或过小会造成溢出。

本课,我们讨论了有符号整数和无符号整数的不同,也介绍了整数的算术运算、位逻辑运算、位移运算和溢出问题,大家可以在后续的学习和工作中逐步掌握整数的特点,并能灵活、正确、高效地应用。

CHY软件小屋原创作品!