zoukankan      html  css  js  c++  java
  • Essential.C#第二章 数据类型

        从第一章的Helloworld程序中,你已经对C#的结构,基本语法有了解,并学会了如何编写简单的程序。本章将继续讨论C#的重要议题,C#类型。

    image

         直到现在你还一直使用原始数据类型做简单的表达。在C#中,存在大量的类型,你可以组合这些类型以创建新的类型。在C#中的一些类型,是用相对简单的类型组建而成的。这些相对简单类型都是预定义类型或原始类型。C#语言的原始类型包括八种整数类型,两种浮点类型,一个高精度类型,一个布尔型,和一个字符型。本章将讨论这些类型,并更多的着眼于字符串类型,还要介绍一下数组。

       

    基本的数字类型

        在C#中为基本的数字类型分配了关键字。他们是,整型integer,浮点型,和用于存储高精度的大数字的类型decimal。

    Integer

        C#中有八种整数类型。你可以为数据选择最节约资源的类型。表2.1类型列出了每个整数类型。

    表2.1 整形

    类型

    长度

    范围

    BCL名

    符号位

    sbyte 8 bits -128 - 127 System.SByte Yes
    byte 8 bits 0 - 255 System.Byte No
    short 16 bits -32768 – 32767 System.Int16 Yes
    ushort 16 bits 0 – 65635 System.U.Int32 No
    int 32 bits –214483648 - 2147486647 System.Int32 Yes
    uint 32 bits 0 - 4294967295 System.UInt32 No
    long 64 bits -9223372036854775808 -9223372036854775807 System.Int64 Yes
    ulong 64 bits 0 – 18446744073709551615 System.UInt64 No

        表2.1、2.2、2.3有一列是每个类型的全名。在C#中所有基本的类型都有简称和全名。全名是在基本类库(BCL)中的名字。这个名字是跨语言的并且在一个程序集中是唯一标识此类型。由于原始类型是常用的,C#也提供了简称或缩写的关键字。从编译器的角度看,这些名字都是完全一样的。都会产出一样的代码。an examination of the resultant CIL code would provide no  indication of which name was used

    语言对比:C++ short类型
    在C/C++中,short类型是短整型的缩写。在C#中,short是一个表示他自身意思的数据类型。

    浮点类型(float,double)

        浮点数字是可变位数的小数。如果你将0.1读成一个浮点数。它可能被表示成0.099999999999999999、0.1000000000000000001或者别的十分接近0.1的数字。还有一种选择,对于一个大数字,比如阿伏加德罗常数6.02E23,能被表示成9.9E9,它是很接近6.02E23了( a large number such as Avagadro’s number, 6.02E23, could be off by 9.9E9, which is something also exceptionally close to 6.02E23, considering its size By definition, the accuracy of a floating-point number is in proportion to the size of the number it contains. Accuracy, therefore, is determined by the number of significant digits, not by a fixed value such as ±0.01)浮点数的精度是它所包含数字的长度。所以精确度就是测定主要数字的位数,而不是大小的值比如±0.01。

        C#支持两种浮点类型。

    将二进制作为十进制表示是为了人类阅读方便。The number of bits (binary digits) converts to 15 decimal digits, with a remainder that contributes to a sixteenth decimal digit as expressed in Table 2.2。特别是,在1.7 * 10^307和1*10^308之间的数字只有15位。然而,数字范围在1*10^308到1.7*10^308就有16位。对于decimal类型存在这两种相似的位数空间。

    表2.2 浮点类型

    类型

    长度

    范围

    BCL名

    符号位

    float 32 bits

    ±1.5 × 10•45
    to
    ±3.4 × 1038

    System.Single 7
    double 64 bits

    ±5.0 × 10•324 to
    ±1.7 × 10308

    System.Double 15 - 16


    decimal类型

        C#包含了一个有128位精度的数字类型。这适合大数字的精确计算,常用在金融计算中。

    表2.3 decimal类型

    类型

    长度

    范围

    BCL名

    符号位

    decimal

    128 bits

     

    1.0 × 10•28 to
    approximately
    7.9 × 1028

     

    System.Decimal

    28–29

        不像浮点数字, decimal类型保持十进制数字的精度。对于decimal来说,0.1就是0.1,而不是0.10000001。尽管decimal类型比浮点有更高的精度。但它的范围小。所以,将浮点类型转换成decimal类型结果可能会溢出。还有,用decimal类型计算会比较慢。

    浮点类型和Decimal

        除非他们都越界,Decimal数字表达十进制数更精确。浮点表达大数字会有误差。这两种类型的不同是,decimal类型的指数是一个十进制,浮点类型的指数是二进制。

    decimal的指数是±N*10K

    • N是96位的整数
    • k是 -28<=k<=0

    浮点是任意整数±N*2k

    • N是一个固定位数的整数(float是24位,double是53位)
    • k是 float范围在-149到104。double范围在-1075到970的任意整数。

    常量值

        常量值是源码中固定不变的值。比如,如果你想用System.Console.WriteLine()输出整数42和double数1.618034.

    清单3.1
    ——————————————————————
      System.Console.WriteLine(42);

      System.Console.WriteLine(1.618034);
    ——————————————————————

    输出2.1
    ——————————————————————
    42
    1.618034
    ——————————————————————

    小心使用硬编码

        将值直接写入源码叫做硬编码,因为改变这个值就意味着重编译代码。开发者必须很小心的再硬编码值和从外部源中获取值(比如,从配置文件中获取值就不用重新编译)间做认真的取舍。

        当你指定一个有小数点的常量数字。编译器默认将其解释成double类型。相反的,一个没有小数位常量值,默认指定成一个整型。假设这个值不是太大就会存储在integer中。如果值太大,编译器将解释成long。此外,C#编译器常用numeric来赋值。假设常量都被指定给了合适的类型 short s = 42,byte b =77。在没有恰当的语法时b = s是不合适的。数据类型的转化会再以后讨论。

        C#中有许多不同的数字类型。在清单2.2中,C#代码中有一个常量值,默认时,小数会作为double类型输出。在输出2.2中1.61803398874989,最后一位5被忽略了。

    清单2.2
    ————————————————————————————————
    System.Console.WriteLine(1.618033988749895);
    ————————————————————————————————

    输出2.2
    1.61803398874989

    为了完全显现输出。你必须添加m(M)来明确声明常量为decimal类型。

    清单2.3
    ————————————————————————————————
    System.Console.WriteLine(1.618033988749895m);
    ————————————————————————————————

    输出2.3
    1.618033988749895

        现在输出了正确的数字,double用d表示。在金融计算中会常用m表示decimal。

        用f表示float,用d表示double。对于整型数据,后缀是u,l,lu,ul。你可以按下面方法检查录入的整数。

    • 没有后缀,就按这个顺序int ,uint,long,ulong
    • 后缀是u,先用uint,再考虑ulong。
    • 后缀是l,先选long在考虑ulong。
    • 如果后缀是ul或lu,类型就是ulong

        注意常量的后缀是不区分大小写的。然而,对于long,会常用大写形式,因为小写的l和数字1太像了。

        在某些方案中,你希望使用科学计数法代替小数点后一长串的零。

    清单2.4
    ————————————————————————————————
    System.Console.WriteLine(6.023E23f);
    ————————————————————————————————

    输出2.4
    6.023E+23

    十六进制计数法

        日常工作中,使用基于十进制的数字表达。就是说,在数字中每位都对应有十个符号(0~9)。如果数字用十六进制显示那么就是基于16位的数字。意思是使用0~9,A~F(常用小写显示)16个符号。所以,0x000A对应的十进制数是10。而0x002A对于德十进制数是42。

        每个十六进制的数字都是四位的。所以一个字节就能表达两个十六进制的数字。

    清单2.5
    ————————————————————————————————
    System.Console.WriteLine(0x002A);
    ————————————————————————————————

    输出2.5
    42

    将数字格式化成十六进制

        为了将数字以十六进制显示,就需要使用格式符号x或X。这个符号将决定十六进制的字母是大写还是小写。

    清单2.6
    ————————————————————————————————
    System.Console.WriteLine(“0x{0:X}”,42);
    ————————————————————————————————

    输出2.7
    0x2A

    更多类型

    boolean 类型

        C#中另一个原始类型就是布尔了,它可以接受关键字true和false。比如,为了以区分大小写方式比较两个字符串的。你就需要调用string.Compare()方法并输入true常量。

    清单2.8
    ————————————————————————————————
    string option;

    int comparsion = string.Compare(option,”/Help”,true);
    ————————————————————————————————
    理论上讲,一为能保存一个布尔,布尔的长度就是一字节(Although theoretically a single bit could hold the value of a Boolean,the size of bool is a byte)。

    字符型

        一字节就是16位字符,这能表示unicode字符集。技术上,Char和长度和无符号整型是一个样的。

    Unicode标准

        Unicode是一个表达人类语言字符集的国际标准。

    对于Unicode字符集十六位太小了

        不幸的是,不是所有的Unicode字符都在16为字符中。当unicode刚刚开始,设计者相信16为足够了。但是随着支持更多的语言,它显然不在正确。.The cumbersome result is that some Unicode characters are composed of surrogate char pairs totaling 32 bits

        在单引号中录入一个字符比如'A',这可以容纳所有键盘上的字符

        有一些字符不能直接键入,需要用替代方式.在字符前添加前缀\后跟指定的字符码.这就叫做转义字符.比如,'\n'代表新行.而你要录入'\'时,也就需要用'\\'来转义.

    表2.4 转义字符

    转义字符 名字 Unicode码
    \' 单引号  
    \" 双引号  
    \\ 斜杠  
    \0  
    \a 系统beep声  
    \b 回退  
    \f 制表符  
    \n 行符  
    \r 回行  
    \t 水平制表符  
    \v 垂直制表符  
    \uxxxx 十六进制  
    \x[]][][]n    
    \Uxxxxxx    

    字符串

        字符串包含一些特定的字符集,这可能让别的语言开发者不明白。The characteristics include a string verbatim prefix character, @,并且字符串是不可改变的。

    常量

        字符串是由字符组成,所以转义字符也能被包含在里面。

        在清单2.11中,比如,两行文本显示。用在System.Console.Write()中录入\n代替System.Console.Writeline()换行。

    清单2.11
    ——————————————————————————————————————
    class DuelOfWits
    {
      static void Main()
    {
    System.Console.Write(“\",Truly,you have a dizzying intellect.\””)

    System.Console.Write(“\n\”Wait ‘til I Get Going \”\n”);

    }

    ——————————————————————————————————

    输出2.9
    "Truly, you have a dizzzying intellect.”
    "Wait ‘til I Get Going!”

        在C#中,还可以在字符串前面使用@符号,这样其中的斜杠将不解释为转义字符了。显示结果会是字面的样子,空白也会保留。看看清单2.12把

    清单2.12
    ————————————————————————————————
    class Triangle
    {

    static void Main()
      {
                 /\
                /      \
               /         \
              /            \
             /________\
    end");
      }
    }
          System.Console.Write(@"begin

    OUTPUT 2.10:
    begin
                 /\
                /  \
               /    \
              /      \
             /????????\
    end

    ——————————————————————

    如果没有@,这个代码将不能编译。

    字符方法

        字符串类型,就像System.Console 类型。包含了许多方法。

        Format()方法和Console.Write方法很像。一个是向控制台输出结果,另一个返回结果。

    新行

        当要输出一个新行时,换行符将依赖于你的代码所在的操作系统。视窗系统的换行符是\r和\n的组合。UNIX是\n.为了弥补这种跨平台的不足,你可以再输出是使用Console.WriteLine()来输出空行。实际上,在不同平台运行时,多数时候并需要输出,但仍然要换行,这时你就需要使用 Systemj.Environment.NewLine. 比如你可以这样用:System.Console.WriteLine(“hello world”) 可以System.Console.Write(“Hello World”+System.Environment.NewLine)替代。

    C#属性

        技术上讲,下面提到的成员Length并不是一个方法。在调用它时,其后不用跟随一个圆括号。Length是一个字符串型的属性,C#允许通过成员变量访问。另一个方面,属性都有一组特定的方法setter和getter。但这是通过field来调用的。

       CIL将属性的两个方法实现为:set_<PropertyName>和get_<PropertyName>.

    字符串的长度

        用string成员方法Length能得到字符串的长度。这是一个只读属性。清单2.13演示了如何使用Length属性

    清单2.13

    class PalindromeLength
    {
        static void Main(string[] args)
        {
            string palindrome;
     
            System.Console.Write("Enter a palindrome: ");
            palindrome = System.Console.ReadLine();
     
            System.Console.WriteLine(
                "The palindrome,\"{0}\" is {1} characters.",
                palindrome, palindrome.Length);
     
        }
    }

    输出2.13

    Enter a palindrome: Never odd or even
    The palindrome, "Never odd or even" is 17 characters.

    字符串的Length是不能直接设置,它是通过计算字符串中字符的数量得到的。而且,字符串的长度的不可改变的。因为字符串的不变的。

    字符串是不可改变的

        string类型最典型的特质就是不可改变。一个stirng变量能分配一个新的值,当由于性能原因,其内容是不可修改的。所以就不可能将一个字符串全部转换成大写字母。也许你会考虑为了将字符串弄成大写而创建一个新的变量是很没有意义的。咱来瞅瞅下面的实例:

    清单2.14

    class Uppercase
    {
        static void Main(string[] args)
        {
            string text;
     
            System.Console.Write("Enter text: ");
            text = System.Console.ReadLine();
     
            //这样并不能将text转换成大写
             text.ToUpper();
            System.Console.WriteLine(text);
     
        }
    }

    输出2.12

    Enter text: This is a test of the emergency broadcast system.
    This is a test of the emergency broadcast system.

        在上例,它希望text.ToUpper()能够将text内的字符转换成大写。然而,字符串是不可变的,所以text.ToUpper()并没有修改text本身。它是返回了一个新的字符串,这就需要将其保存到新的变量中,或直接通过System.Console.WriteLine()输出。清单2.15是正确的写法

    清单2.15

    class Uppercase
    {
        static void Main(string[] args)
        {
            string text, uppercase;
     
            System.Console.Write("Enter text: ");
            text = System.Console.ReadLine();
     
            //将返回的新字符串给uppercase
            uppercase = text.ToUpper();
            System.Console.WriteLine(uppercase);
        }
    }

    输出2.13

    Enter text: This is a test of the emergency broadcast system.
    THIS IS A TEST OF THE EMERGENCY BROADCAST SYSTEM.

        为了改变text的值,通过ToUpper将新值重新分配给text
       text = text.ToUpper();

    System.Text.StringBuilder

        如果想要修改字符串,比如当多次构建一个字符串,你就要使用System.Text.StringBuilder类型。它的方法(比如,Append(),AppendFormat(),Insert(),Remove(),Replace())都是在修改变量中的数据,而不是想string类型那样只是返回一个新的字符串。

    null 和 void    和类型紧密相关的两个附加关键字是null和void。null是一个常量用来指示数据类型(特指,引用型)没有分配任何东西。void指示没有类型或没有值(void is used to indicate the absence of a type or the absence of any value altogether)。

    null

        null是字符串类型的常态。null指示了一个变量没有设置任何东西。只有引用型能够分配null值。到目前为止本书只引入了一个引用型是string。第五章将会详细讨论引用类型话题。引用类型包含一个指针、一个地址或引用的不同数据所在的一块内存位置。在代码中给一个变量指定null值就是明确指明没有引用任何东西。事实上,也可以检查一个引用类型是否指向null。清单2.16演示了给一个string类型的变量分配null值

    清单2.16

    static void Main(string[] args)
    {
        string faxNumber;
        // ...
        // Clear the value of faxNumber.
        // ...
        faxNumber = null;
    }

         需要特别注意的是,给引用类型分配null值和不做任何分配是有很大不同的。换句话说,给变量分配了null就是表明它已经被赋值。而不做任何分配就表明没有赋值。这将有可能会造成编译器错误。

        为字符串分配null值和分配空字符"” 是有很大不同的。null指示了这个变量没有值。"”指示了它已经有了一个值:一个空字符。区分出这种差别是很有意义的。比如,程序逻辑可以将为null的faxNumber变量解释成传真号码未知。而将faxNumber设成"”就表示没有传真号码。

    不标准的void

        有时C#语法上需要指定一个数据类型,但并不要数据。比如,一个方法不需要返回值,这时C#就使用void来作为数据类型。就像Helloworld程序中的Main声明那样。在那些情况下,数据类型就是void。将方法的返回值指定为void就表明此方法不需要返回任何数据,并告诉编译器不要等待返回值。void 不是预定义的数据类型。但是它在一定程度上表示没有数据类型。

    语言对比:C++——void是一个数据类型
    在C++中,void是一种数据类型,通常这样使用void**。在C#中,void不作为数据类型考虑,它只是用来标识方法没有返回值


    隐式类型变量

        作为补充,C#3.0中包含了一个上下文关键字var。它声明一个隐式类型的变量。如果在代码初始化时声明一个变量,C#3.0允许变量的数据类型是隐式的。相对于明确指定变量的数据类型,隐式类型变量使用上下文关键字var来声明,看看清单2.17

    清单2.17

    class Uppercase
    {
        static void Main(string[] args)
        {
            System.Console.Write("Enter text: ");
            // Return a new string in uppercase
            System.Console.WriteLine(uppercase);
            var text = System.Console.ReadLine();
            var uppercase = text.ToUpper();
        }
    }

         此段代码和清单2.15有两处不同,

    • 第一,没有明确使用string类型来声明变量。这里使用var。而CIL的的结果会明确指定使用stirng。不管怎样,var指示编译器从值上(System.Console.ReadLine())检测数据类型。
    • 第二,变量text和uppercase都是在声明的同时被分配。这样做并不会产生编译器错误。如前所说,via指示编译器通过右边的表达式检测变量的数据类型。就想程序员已经明确的为其指定类型一个样。

        尽管,使用var还不如明确指定类型,最好的做法是,当知道数据的类型时还是不要使用var。一旦我们知道了text和uppercase 都是string类型。最好为变量明确指定为string类型。这不仅仅是为了代码的可读性,而且还可以核实右侧表达式的返回值。

        var支持后来C#3.0添加的匿名类型。匿名类型是在方法中动态声明的数据类型,而不是通过定义一个类。

        清单2.18演示了为隐式变量分配匿名类型。这个类型提供了C#3.0支持的关键功能joining(联合)类型,这也减少了特定类型的长度,节约了数据元素reducing the size of a particular type down to fewer data elements。

    清单2.18

    class Program
    {
        static void Main(string[] args)
        {
            var patent1 =
                new
                {
                    Title = "Bifocals",
                    YearOfPublication = "1784"
                };
            var patent2 =
                new
                {
                    Title = "Phonograph",
                    YearOfPublication = "1877"
                };
            System.Console.WriteLine("{0} ({1})",
                         patent1.Title, patent1.YearOfPublication);
            System.Console.WriteLine("{0} ({1})",
                patent2.Title, patent1.YearOfPublication);
        }
    }

    输出2.14

    Bifocals (1784)
    Phonograph (1877)

    类型的分类

        所有的类型都可以分成两个类型:值型和引用型。这种类型的不同点在于内存的位置:值型是存储在栈中,而引用型是存储在堆中。

    值型

        除了string类型之外,到目前为止本书所有已定义的类型都是值型。值型就是直接容纳值。换句话说,变量的引用的内存位置和值得存储位置是一样的。所以,当为不同变量分配同一个值时,分别将原始值的内存分别复制到新的变量位置。两个变量不可能引用同一块内存空间。因此,改变其中一个变量的值将不影响别的变量。图2.1就演示了number1在内存中的值是42,之后我们将number1分配给number2.这时两个变量的值都是42了。然而,修改任意一个变量都不会影响别的变量。


    image

        同样的,将一个值型传入方法比如Console.WriteLine()也是对内存的拷贝。方法内部任何对参数值得改变都不会影响原始值。定义一个值型,通常会消耗很小一片内存(至少16字节)

        这片值型的内存空间是在编译期确定的,在运行期是不可改变的。存储值型的固定内存空间被称为栈。

    引用类型

        引用类型的变量是数据存储位置的指针。引用类型存储数据的引用位置(即内存地址)而不是数据本身。所以,为了访问数据,在运行时期将读取变量的内存地址,然后跳向内存位置所指向的数据。引用类型所指向的数据内存空间叫堆!

        从访问引用类型的数据开始就有堆参与其中,有时这个举动会有点慢。然而一个引用类型不会像值型那样拷贝内存的,只会拷贝内存的地址,在一定条件下这会获得较高的效率。引用类型的内存拷贝总是拷贝地址长度(32位处理器上拷贝32位,64位处理器上拷贝64位)。很显然,相对于值型不拷贝数据,对于大数据尺寸引用类型会更快。

        一旦引用类型拷贝了一个数据地址,那么两个不同的变量就会指向同一个数据。此外,改变其中一个变量的数据另一个变量也会同时改变。因此,方法将能影响引用类型的数据。a method can affect the data of a reference type back at the caller

    image

        除了string和任何自定义类比如前例子中的Program。到目前为止所以讨论的类型都是值型。然而,大多说类型都是引用类型,尽管自定义值型也是可能的,但这对于自定义引用类型的数量是太少见了。

    可空的修饰符

        正如我前面提到的,值型不能分配null值。它们不能引用任何东西。然而,在现实世界会有这样一种问题,缺少值。当定义了count,比如,如果你不知道count,你应该录入什么呢?有一种解决方法就是想象一个值。比如0或int.Max,虽然这是合法的整型。在一定程度上,为值型分配null值是最合适的,因为它们没有一个合适整型。

        为了将null存储在你定义的变量中需要使用可空修饰符,,在C#2.0中就包含了这一个特性。看实例2.19

    清单2.19

    static void Main(string[] args)
    {
        int? count = null;
        do
        {
            // ...
        }
        while (count == null);
    }

         为值型分配null值尤其对数据库编程最具吸引力。在数据库表中列常常允许null。在检索这样的列,并将其分配给C#代码中相应的字段时,就会产生问题。除非这些字段也支持null。幸运的是,可空修饰符就是设计用来处理这样特定问题的。

    数据类型间的转化

        在CLI的各种实现中有大量预定义类型,并且还可以编写定义无数类型。将一种类型转换成另一种类型也具有很重要的意义。最常见的类型转换时casting。

        现在考虑在两个数字类型间转换:将变量从long转换成int。long型的变量最大值是9,223,372,036,854,775,808。而int型的最大值只有2,147,483,647。因此转换结果可能会丢失数据。比如如果long型变量的值比int变量值大。任何可能丢失数据的转换过程都需要显式转换。相反的,一个转换操作不会丢失精度,并且不会抛出任何异常就是隐式转换

    显示转换
        在C#中,使用转换操作符进行转换。将你想转换的指定类型置入圆括号中,这样你就指定了一个显示转换,它可能会丢失数据的精度,或者引发结果异常。清单2.20中将long转换成int,并明确告诉系统这样做。

    清单2.20
    image

        程序员用转换操作符告诉编译器“相信我,我知道我在做什么,我知道这个转换过程可能不合适,但是我依然需要这样做”一旦这样做了编译器会允许转换。然而,对于显示转换,可能有错误村子,如果没有成功转换,将引发一个异常。因此,程序员有责任保证能成功转换数据,或者为提供必要的程序逻辑处理异常。

    转换时的检查和非检查(Checked、Unchecked)

        C#提供了一个特定的关键字,用于当目标数据类型太小,无法包含数据。默认时,如果目标数据类型包含所分配的数据,这个数据将被截去一段。比如清单2.21这样

    清单2.21

    class Program
    {
        static void Main(string[] args)
        {
            // int.MaxValue equals 2147483647
            int n = int.MaxValue;
            n = n + 1;
            System.Console.WriteLine(n);
        }
    }

    输出2.15

    -2147483648


        清单2.21向控制台输出了-2147483648。你可以将代码置入checked块,或者把编译器的checked选项打开。当运行时有异常就会抛出System.OverflowException。checked块的语法是使用checked关键字。

    清单2.22

    class Program
    {
        static void Main(string[] args)
        {
            checked
            {
                // int.MaxValue equals 2147483647
                int n = int.MaxValue;
                n = n + 1;
                System.Console.WriteLine(n);
            }
        }
    }

    输出2.16

    Unhandled Exception: System.OverflowException: Arithmetic operation
    resulted in an overflow at Program.Main() in ...Program.cs:line 12

    这个结果抛出了一个异常。在运行期,checked块中引发了一个算术运算导致溢出。

        C#编译器还提供了命令行选项,用来将unchecked置成checked。C#还支持unchecked块,这将直接截取数据而不是抛出异常。

    清单2.23

    class Program
    {
        static void Main(string[] args)
        {
            unchecked
            {
                // int.MaxValue equals 2147483647
                int n = int.MaxValue;
                n = n + 1;
                System.Console.WriteLine(n);
            }
        }
    }

    输出2.17

    -2147483648

    Even if the checked option is on during compilation, the  unchecked keyword in the preceding code will prevent the runtime from throwing an exception during execution(这句意思好像是,如果编译器打开了checked选项,那么代码中定义的unchecked块将不起作用,依然会抛出异常。不知道我理解反了没有)

        你不能简单的将一个类型转换成另一个类型,因为你需要使用显示转换操作符。编译器将总是检查操作是否合法。比如,你不能将long转换成bool。由于没有定义这样的转换操作符,编译器将拒绝这样转换

    语言对比:将数字转换为布尔
    在大多数语言中,你会很惊讶的发现将数字类型转换成布尔类型是不合法的。之所以不这样转换,在C#中是为了避免歧义。比如-1对应的是true和false呢?更重要的是,你在下一章将会看到,这将能减少在等号操作符中使用分配操作符的机会。

    隐式转换

        还有一个例子,比如将int类型转换成long类型。这样做不会丢失精度,并且类型值也没有重大的改变。因此,代码只要指定分配操作符进行隐式的转换。换句话说,编译器能够检测这类转换是正确的。清单2.24是使用简单的分配操作符将int类型转换成long类型。

    清单2.24
    ——————————————————————
    int intNumber = 31416;
    long longNumber = intNumber;
    ——————————————————————

       对于隐式转换不包含转换操作符也是可以的。下面是包含转换操作符的例子

    清单2.25
    ——————————————————————
    int intNumber = 31416;
    long longNumber = (long)intNumber;
    ——————————————————————

    不用转换操作符的类型转换

        隐式或显示转换都不能将字符串类型转换成数字类型,所以就需要一个方法比如Parse()。每个数字类型都包含一个Parse()函数,用来保证能将一个字符串转换成数字类型。

    清单2.26
    ——————————————————————————
    string text = "9.11E-31";
    float kgElectronMass = float.Parse(text);
    ——————————————————————————

        System.Convert类型就是专门用于从一种类型转换到另一种类型的。

    清单2.27
    ————————————————————————————————
    string middleCText = "278.4375";
    double middleC = System.Convert.ToDouble(middleCText);
    bool boolean = System.Convert.ToBoolean(middleC);

    ————————————————————————————————

    System.Convert支持一些预定义类型,并且不能扩展。它允许从任何基本类型转换到别的基本类型(bool,char,sbyte,short,int,long,ushort,uint,ulong,float,double,decimal,DataTime,string)

        此外,所以的类型都有ToString()方法,这提供了用字符串描述一个类型。清单2.28演示了如何使用这个方法.

    清单2.28
    ————————————————————
    bool boolean = true;
    string text = boolean.ToString();
    // Display "True"
    System.Console.WriteLine(text);

    ————————————————————

        对于多数类型,ToString()方法将返回数据类型的名字,而不是数据的字符串形式。除非这个类型显示实现了ToString()方法。这就使自定义转换方法成为可能。在运行期类有许多这样的方法。

    TryParse()

       从C#2.0开始,所有的数字类型都包含一个静态方法TryParse()(在C#1.0中,只有double类型包含这个方法)
    这个方法是Parse()方法十分相似。除了它会再转换失败时抛出一个异常,而TryParse()方法只会返回false。

    清单2.29

    class Program
    {
        static void Main(string[] args)
        {
            double number;
            string input;
            System.Console.Write("Enter a number: ");
            input = System.Console.ReadLine();
     
            if (double.TryParse(input, out number))
            {
                // Converted correctly, now use number
                // ...
            }
            else
            {
                System.Console.WriteLine(
                    "The text entered was not a valid number.");
            }
        }
    }

    输出2.19

    Enter a number: forty-two
    The text entered was not a valid number.

        在Parse()和TryParse()最大的不同在于当失败时,后者不会抛出异常。

    数组

        数组提供了为一个变量声明一组相同类型的数据集合。数组中的每条记录都被指定了唯一的整形值,叫索引!在C#中,使用0访问数组的第一个记录。程序员必须十分小心指定索引,它总是小于数组长度。

    声明一个数组

        在C#中,使用方括号声明一个数组。首先,你为数组中的项目指定类型。添加左右方括号。然后写个名字。

    清单2.30
    ——————————————————
    string[] languages;
    ——————————————————

        很显然,数组声明的第一部分是数组元素的类型。声明部分的方括号定义了维度,或叫尺寸。在本例中,数组是一维。

        清单2.30定义了一个一维的数组。方括号中的逗号用来增加维度。清单2.31,定义了二维数组。这有可能是个棋类游戏,或是填字游戏。

    清单2.31

    //    |   |   
    // ---+---+---
    //    |   |   
    // ---+---+---
    //    |   |   
    int[,] cells;

         在清单2.31中,数组时二维的。第一维是横行,第二维是竖行。使用逗号可以添加数组的维度。注意数组中出现的维度不属于变量声明。当创建了一个数组,将自动为每个元素分配空间。

       

    实例化数组

        一旦声明了一个数组,你就要立刻为其填充值。清单2.32声明了一个字符串数组,并且在花括号中分配了九种语言名。

    清单2.32

    string[] languages = { "C#", "COBOL", "Java",
            "C++", "Visual Basic", "Pascal",
            "Fortran", "Lisp", "J#"};

         清单2.32显示的分配语法只适用于声明和分配值在一个语句内。为了声明之后再分配值,需要使用关键字new,并匹配相应的数据类型。

    清单2.33

    string[] languages;
    languages = new string[]{"C#", "COBOL", "Java",
            "C++", "Visual Basic", "Pascal",
            "Fortran", "Lisp", "J#" };

         C#也允许在声明语句中使用new关键字

    清单2.34

    string[] languages = new string[]{
            "C#", "COBOL", "Java",
            "C++", "Visual Basic", "Pascal",
            "Fortran", "Lisp", "J#"};

    new关键字在运行期为数据类型分配内存。它指示在运行期实例化数据类型——在本例中,是一个数组。

        不管怎么样,new关键字是分配数组的一部分。你也可以在花括号内位数组指定长度。清单2.35演示了这种语法

    清单2.35

    string[] languages = new string[9]{ 
            "C#", "COBOL", "Java",
            "C++", "Visual Basic", "Pascal",
            "Fortran", "Lisp", "J#"};

        在初始化语句中的数组长度和花括号中包含的元素数量必须一致。因此就可以不为数组指定初始化值。

    清单2.36

    string[] languages = new string[9];

        分配数组但不初始化任何值,那么每个元素都是初始化状态。

    • 引用类型都被初始化成null
    • 数字类型被初始化为零
    • 布尔类型被初始化为false
    • char被初始化成\0

    由此看出,在使用以前并不需要分配每个数组元素

        从C#2.0开始,使用default()操作符来检测数据类型的默认值。default()需要数据类型作为参数 比如default(int) 返回0 而default(char)返回\0

        由于数组长度并不是数组声明的一部分。就可能在运行期指定长度。比如,在清单2.37中,创建了一个由Console.ReadLine()指定长度的数组

    清单2.37

    string[] groceryList;
    System.Console.Write("How many items on the list? ");
    int size = int.Parse(System.Console.ReadLine());
    groceryList = new string[size];
    // ...

         C#初始化多维数组和一维类似。逗号分隔了维度。清单2.38初始化了一个填字板

    清单2.38

    int[,] cells = int[3,3];

         还有另一种方式,你可以再指定的位置初始化填字板。

    清单2.39

    int[,] cells = {
                        {1, 0, 2},
                        {1, 2, 0},
                        {1, 2, 1}
                    };

        这里初始化了一个有三个元素的整数型数组。每个元素的长度都是三。注意,每个int[]元素的维度必须一样。像清单2.40的声明是不合法的。

    清单2.40

    // ERROR:  Each dimension must be consistently sized.
    int[,] cells = {
            {1, 0, 2, 0},
            {1, 2, 0},
            {1, 2}
            {1}
        };

        在填字中每个位置并不需要数字。有一种选择就是为每个游戏者分出一个虚拟的板子。每个板子包含了一个bool值用来指示游戏者已经选择的位置。清单2.41是一个三维度的填字板。

    清单2.41

    bool[, ,] cells;
    cells = new bool[2, 3, 3] 
          {
             // Player 1 moves                //  X |   |
             {   {true, false, false},        // ---+---+---
                 {true, false, false},        //  X |   |   
                 {true, false, true} },       // ---+---+---
                                              //  X |   | X
             // Player 2 moves                //    |   | O
             {   {false, false, true},        // ---+---+---
                 {false, true, false},        //    | O |   
                 {false, true, true} }        // ---+---+---
                                              //    | O |   
          };

         本例子中,板子已经初始化完毕,并显式定义了每一维度的长度。除了每个表达式都标示了长度,还为这个数组提供了常量值。类型bool[,,]被拆分成两个bool[,]类型的数组。长度都是3×3的。每个二维数组都三个一维bool数组组成。长度也是3.

        前面提到过,多维数组的每一个维度必须有一样的长度。不过,你还可以定义锯齿数组(微软称之为交错数组),这是数组的数组。锯齿数组和多维数组有略微的不同。此外锯齿数组不需要同样的长度。所以,可以像清单2.42那样初始化一个锯齿数组。

    清单2.42

    int[][]cells = {
        new int[]{1, 0, 2, 0},
        new int[]{1, 2, 0},
        new int[]{1, 2},
        new int[]{1}
        };

         锯齿数组不能使用逗号来定义新的维度。锯齿数组定义了一个数组的数组。在清单2.42中,int[]类型后被放置了一个[].因此这就是声明一个int[]的数组。

        注意,锯齿数组需要在每个数组内部实例化数组。本例中,你需要在锯齿数组内使用new来实例化元素。忘记实例化将会引发编译器错误。

    使用数组

        访问数组中特定元素所使用的方括号标记,被称为数组访问符。将索引指定为0就可获取数组的第一个元素。清单2.43,获取了变量languages的第五个元素(索引是4)。

    清单2.43

    string[] languages = new string[9]{ 
            "C#", "COBOL", "Java",
            "C++", "Visual Basic", "Pascal",
            "Fortran", "Lisp", "J#"};
    // Retrieve 5th item in languages array (Visual Basic)
    string language = languages[4];

         方括号标记也用来向数组中保持数据。清单2.44交换了"C++”和"Java”的顺序

    清单2.44

    string[] languages = new string[9]{ 
            "C#", "COBOL", "Java",
            "C++", "Visual Basic", "Pascal",
            "Fortran", "Lisp", "J#"};
    // Save "C++" to variable called language.
    string language = languages[3];
    // Assign "Java" to the C++ position.
    languages[3] = languages[2];
    // Assign language to location of "Java".
    languages[2] = language;

        对于多维数组,每个维度的元素都有一个索引。

    清单2.45

    int[,] cells = {
            {1, 0, 2},
            {0, 2, 0},
            {1, 2, 1}
        };
    // Set the winning tic-tac-toe move to be player 1.
    cells[1, 0] = 1;

         锯齿数组元素分配有些不一样,因为这需要符合锯齿数组声明。数组的数组的第一个元素是一个数组(绕口令啊,奶奶的这作者学过相声)

    清单2.46

    int[][] cells = {
            new int[]{1, 0, 2},
            new int[]{0, 2, 0},
            new int[]{1, 2, 1}
            };            
    cells[1][0] = 1;
    // ...

    长度

        你也可以像清单2.47那样获得数组的长度

    清单2.47

    Console.WriteLine("There are {0} languages in the array.",
    languages.Length);

        数组的长度总是固定的,除非重建数组,否则它的边界是不可改变的。因此,数组越界访问将引发一个错误。这种情况通常出现在使用索引访问没有元素的数组中。当你使用数组的长度作为索引也会引发这个错误。

    清单2.48

    string languages = new string[9];
    //...
    // RUNTIME ERROR: index out of bounds – should 
    // be 8 for the last element
    languages[4] = languages[9];  

        用Length代替数组长度的硬编码是很好做法,就是使用Length作为索引。比如,将Length减1来避免越界错误。

    清单2.49

    string languages = new string[9];
    //...
    languages[4] = languages[languages.Length - 1];

         Length是返回数组元素的总数。所以如果你有一个多维数组比如bool cells[,,] ,它的长度就是2*3*3,Length返回的总数就是18

        对于锯齿数组,Length返回的是第一个数组元素的总数——锯齿数组时数组的数组,所以Length就是最外层数组的元素数量,而不管内部数组的元素数量。

    和数组相关的方法

       数组包含了一些附加处理元素的方法。包含Sort(),BinarySearch(),Reverse(),Clear().

    清单2.50

    class Program
    {
        static void Main(string[] args)
        {
            string[] languages = new string[]{
              "C#", "COBOL", "Java",
              "C++", "Visual Basic", "Pascal",
              "Fortran", "Lisp", "J#"};
            searchString = "COBOL";
            System.Console.WriteLine(
                "The wave of the future, {0}, is at index {1}.",
                searchString, index);
            System.Console.WriteLine();
            System.Console.WriteLine("{0,-20}\t{1,-20}",
                "First Element", "Last Element");
            System.Console.WriteLine("{0,-20}\t{1,-20}",
                "-------------", "------------");
            System.Console.WriteLine("{0,-20}\t{1,-20}",
                languages[0], languages[languages.Length - 1]);
            System.Console.WriteLine("{0,-20}\t{1,-20}",
                languages[0], languages[languages.Length - 1]);
            // Note that this does not remove all items 
            // from the array. Rather, it sets each item to the 
            // type's default value.
            System.Console.WriteLine("{0,-20}\t{1,-20}",
                languages[0], languages[languages.Length - 1]);
            System.Console.WriteLine(
                "After clearing, the array size is: {0}",
                languages.Length);
            System.Array.Sort(languages);
            index = System.Array.BinarySearch(
                languages, searchString);
            System.Array.Reverse(languages);
            System.Array.Clear(languages, 0, languages.Length);
     
        }
    }

    输出2.20

    The wave of the future, COBOL, is at index 1.
    First Element           Last Element
    -------------           ------------
    C#                      Visual Basic
    Visual Basic            C#
    After clearing, the array size is: 9

        这些方法都包含在System.Array类中,其中多数方法是自解释的,不过还有两个方法需要注意。

    • 在使用BinarySearch()方法前,它是针对排序的数组。如果值没有按升序排列,那么返回的索引可能不正确。如果查找的元素不存在,那么会返回负值(使用按位求补运算符,~index,会返回第一个索引,如果返回别的值,说明数组中没有比查询数值更大的)。
    • Clear()方法不能删除数组中的元素,并且不能将数组长度设成0.数组的长度是固定的,不可修改的。所以,Clear()方法只是将数组中的每个元素置成默认值(false,0,null)。这就解释了当数组调用Clear()方法之后Console.WriteLine()会创建一块空行。

    数组实例化的方法

        和字符串一样,数组也有一些实例成员,它们不是从数据类型中访问,而是直接通过变量调用。Length就是这样一个实例化成成员,因为需要通过数组变量调用Length,而不是通过类。另一个重要的实例成员就是GetLength(),Rank,Clone().

        取得特定维度的长度,不需要通过Length属性。使用GetLength()实例方法就可以取得特定维度的尺寸。当调用这个方法时,需要指定维度,而后就会返回长度

    清单2.51

    bool[, ,] cells;
    cells = new bool[2, 3, 3];
    System.Console.WriteLine(cells.GetLength(0));   // Displays 2

    之所以显示2,是由于这是第一个维度的元素数量。

        还可以用Rank成员来获取数组的维度。比如,对于上个例子。cells.Rank。就会返回3.

        通常,将一个数组变量赋值给另一个数组变量,只是拷贝的数组引用,而不是数组中的每一个元素。为了将整体拷贝到新的数组中,需要使用数组Clone()方法。Clone()方法将返回一个数组拷贝。改变新数组中的任何元素是不会影响原始数组的元素。

    把字符串作为数组

        字符串类型的变量可以作为字符数组访问。比如,可以获得字符串palindrome的第四个字符palindrome[3]。注意,由于字符串是不可改变的,是不可能给字符串分配指定字符。当palindrome作为字符串声明时,C#是不允许这样操作palindrome[3] = ‘a’,清单2.52使用数组访问符来检测命令行的选项是否可用。这里定义了一个破折号作为第一个字符。

    清单2.52

    string[] args;
    //...
    if(args[0][0]=='-')
    {
        //This parameter is an option
    }

         其中if语句将在第三章讲解。另外,这里展示了一个很有趣的例子,你可以使用数组访问符来获取字符串数组的第一个元素。而且同时取得字符串的第一个字符。这段代码和下面代码时一个意思。

    清单2.53

    string[] args;
    //...
    string arg = args[0];
    if(arg[0] == '-')
    {
        //This parameter is an option
    }

         注意,只有字符串能使用数组访问符来单独获取,不过,也可以将整个字符串作为字符数组处理,使用字符串的ToCharArray()方法。使用了这个方法,你能够使用System.Array.Reverse()方法来反转字符串。

    清单2.54

    class Program
    {
        static void Main(string[] args)
        {
            string reverse, palindrome;
            char[] temp;
            System.Console.Write("Enter a palindrome: ");
            palindrome = System.Console.ReadLine();
            // Remove spaces and convert to lowercase
            reverse = palindrome.Replace(" ", "");
            reverse = reverse.ToLower();
            // Convert to an array
            temp = reverse.ToCharArray();
            // Reverse the array
            System.Array.Reverse(temp);
     
            // Convert the array back to a string and
            // check if reverse string is the same.
            if (reverse == new string(temp))
            {
                System.Console.WriteLine("\"{0}\" is a palindrome.",
                     palindrome);
            }
            else
            {
                System.Console.WriteLine(
                    "\"{0}\" is NOT a palindrome.",
                    palindrome);
            }
            Console.Read();
        }
    }

    输出2.22

    Enter a palindrome: NeverOddOrEven
    "NeverOddOrEven" is a palindrome.

    这个例子中,使用了new关键字。它从反转字符数组中创建了一个新的字符串。

    常见错误

        本节作者列出了一些程序员易犯错误,就不翻译了饿。

  • 相关阅读:
    【hive】时间段为五分钟的统计
    【git】提交到github不显示贡献小绿点问题的解决
    【hive】关于用户留存率的计算
    【hive】求日期是星期几
    【hive】数据仓库层次设计
    【hive】count() count(if) count(distinct if) sum(if)的区别
    转载:几种 hive join 类型简介
    丑小鸭和白天鹅没有区别
    好好照顾自己,就像照顾你爱的人那样;
    灵光一闪(最近更新于2020/8/23)
  • 原文地址:https://www.cnblogs.com/yaoshi641/p/1544619.html
Copyright © 2011-2022 走看看