2.1 认识数据类型
C#在对数据进行操作之前,必须指定其属于什么类型。C#基本上完全保留了C++中的所有数据类型。除了指针以外,同时还吸收了Visual Basic和Java中的一些数据类型。关于C#的数据类型关系,如图2.1所示。
图2.1 C#的数据类型
警告:C#同样支持指针类型,但是只能在“不安全代码”中才可以直接使用指针类型,其他地方几乎不能使用。
2.1.1 值类型
所谓值类型,就是一种由类型的实际值表示的数据类型。例如可以把数字“3”看做是整数型,而把3.00看做是小数型一样。在计算机程序里,通常把小数型叫做浮点型,浮点的意思可以理解为小数点的位置不固定。C#的值类型可以分为简单类型、枚举类型和结构类型。下面就结合实例来分别介绍这些值类型。
简单类型包含整数类型、字符类型、实数类型、布尔类型。小数类型包含在了实数类型中,小数点后面必须是十进制的数。顾名思义,整数类型变量的值为整数,数学上的整数可以从负无穷大到正无穷大。但是由于计算机的存储单元是有限的,所以计算机语言提供的整数类型值总是在一定的范围之内,C#中有9种整数类型,即int、uint、long、ulong、char、short、ushort、sbyte、byte,它们占用的空间和取值范围如表2.1所示。
注意:每种值类型都有一个隐式的构造函数,这个构造函数可以理解为值类型本身为自己服务的一种功能,用来初始化类型的默认值。
表2.1 C#值类型数据的占用字节数和范围
【范例2-1】整数类型的定义和运算。本例中定义了两个班级变量,并计算两个班级人数的总和,如示例代码2-1所示。
示例代码2-1
01 using System; //包含基本类和基类 02 …… //还有其他命名空间,也是系统生成的,在此省略 03 namespace Ex_2_1 //程序的命名空间,也是项目名称,如Ex_2_1 04 { 05 class Program 06 { 07 static void Main(string[] args) //主函数,也是入口函数 08 { 09 int a = 50; //定义a班,有50个同学 10 int b = 40; //定义b班,有40个同学 11 Console.WriteLine(a+b); //输出总人数 12 Console.ReadLine(); //加上这行才能看到运行结果,否则一闪而过 13 } 14 } 15 }
【运行结果】选择“调试”|“启动调试”选项运行程序,结果如图2.2所示。
图2.2 整数实例运算结果
【代码解析】在本例中,首先定义了两个int类型的变量a和b。在此处,它们分别代表a班的人数和b班的人数,在实际的开发过程中,也可以表示具有整数类型的量。分别给a、b赋值为50和40,然后在第11行处输出a班和b班人数的总和。
提示:Console.WriteLine与Console.Write都可以输出,两者的区别在于Console.WriteLine输出后换行,而Console.Write只是输出。有的读者可能在编译的时候窗口一闪而过了,无法看到运行结果,在程序的最后加上Console.ReadLine()就能看见运行结果了。
计算机处理的信息除了数字以外,主要就是字符了。字符包括数字字符、英文字母、表达符号等。字符类型在C#中被看做是一种特殊的整数类型,它们都采用Unicode字符编码。在使用字符常量时,必须用单引号引上常量的值。不包含任何字符的字符串,就称为空字符串,例如:
char myname = ’Z’;
除了单个字符外,还有一串字符组成的字符串,在C#语言中,字符串通过关键字string来定义。例如定义一个字符串变量可以这样:
string sName=”I Love C#”;
字符串在计算机里存储,实际上是按单个字符的方式连续组成起来的,其实质就是字符数组。这里可以理解为一连串存储在邻近位置的字符,它的存储方式如图2.3所示。
图2.3 I Love C#字符串的存储形式
警告:其实,计算机在为每个字符串分配空间时,都会在其后加上一个转义字符“\0”,用于表示字符串的结束。这样,人们用肉眼看时,看到的字符串长度比实际上的要小1。
2.1.2 引用类型
引用类型又称为对象。引用类型的变量是不直接存储变量值的,它是指向要存储的值,实际上就是存储数据引用值的地址。class、interface、delegate可以用于声明引用类型。C#的引用类型有4种,即类、代表、数组、接口。
特别要注意区别值类型和引用类型。值类型在内存中存放的是变量的值。而引用类型在内存中存放的是变量所指向的地址。它没有真正存在的值,是引用别的值。这个过程类似于生活中的代理产品一样,代理商就像引用类型,其没有自己的产品,而是代理厂家的产品,使其就像是自己的产品一样。
警告:引用类型存储的实际是引用值的地址,初学者常把它和其他数据类型一同理解。它是没有实际值的。
【范例2-2】下面演示引用类型的使用方法。用一个变量指向一个有实际内容的变量空间,并且输出,如示例代码2-2所示。
示例代码2-2
01 using System; //包含基本类和基类 02 …… //还有其他命名空间,也是系统生成的,在此省略 03 namespace Ex_2_2 //程序的命名空间,也是项目名称,如Ex_2_2 04 { 05 class Program 06 { 07 static void Main(string[] args) //主函数,也是入口函数 08 { 09 demo n1 = new demo(); //将引用类型demo实例化 10 n1.a = 10; //为成员a赋值 11 n1.b = 15; 12 demo n2 = n1; //只是赋值指针,将指针指向n1.a。注意这里是关键 13 Console.WriteLine(n2.a); //输出结果,其值实际上还是ni存储单元中的 14 Console.WriteLine(n2.b); //输出结果,是ni中的成员b的值 15 Console.Read(); //获取输入焦点,为了看运行结果 16 } 17 class demo //引用类型的声明 18 { 19 public int a; //成员变量 20 public int b; //成员变量 21 } 22 } 23 }
【运行结果】选择“调试”|“启动调试”选项运行程序,结果如图2.4所示。
图2.4 引用类型
【代码解析】在本例中演示了引用类型的使用。在第17行代码处用class声明了引用类型,并在第19行和第20行代码处定义了两个成员变量。就相当于给这个引用类型定义了两个性质一样,如定义了人的身高和体重。在第09行处实例化了demo对象,就像从人的概念中实实在在地创造了一个活生生的人。在第10行和第11行中为其成员赋值,类似于给人取了名,或者说这个人有了身高。值得注意的是,在第12行,用n2指向n1的指针,而不是为其赋值,这里是关键。
注意:引用类型只是将指针指向内存中的存储单元。
2.1.3 创建和使用数组
数组是引用类型中的自描述类型,是指由同一种数据类型组成的集合。最常见的数组如矩阵数组,它包含了一维数组和多维数组。数组能够存储多种数据类型,但是在同一个数组中的数据必须都是同一种类型。定义数组时,可以在“[]”中定义元素的个数和数据,如int [] array=new [5]。使用数组的下标来获取数组的元素。下标从0开始,一直到元素和个数减1。例如在上述的数组中,array[0]就是第一个元素。
【范例2-3】下面来演示一维数组的使用方法。本例中用for循环输出5 个数字,这5个数字存在一个有5个元素的一维数组中,如示例代码2-3所示。
示例代码2-3
01 using System; //包含基本类和基类 02 …… //还有其他命名空间,也是系统生成的,在此省略 03 namespace Ex_2_3 //程序的命名空间,也是项目名称,如Ex_2_3 04 { 05 class Program 06 { 07 static void Main(string[] args) //主函数,也是入口函数 08 { 09 int[] array = new int[5]; //定义一个一维数组,它有5个元素 10 for (int i = 0; i < array.Length; i++) //循环计算i的值,最大长度是数组的长度 //array.Length 11 { 12 array[i] = i + 10; //将i的值加上10再赋给第i个数组元素 13 Console.WriteLine(array[i]);//输出第i个数组元素的值 14 } 15 Console.ReadLine(); //获取输入焦点,为了看运行结果,放在循环 //体之外 16 } 17 } 18 }
【运行结果】选择“调试”|“启动调试”选项运行程序,结果如图2.5所示。
图2.5 数组演示结果
【代码解析】上述代码首先定义了一个长度为5的int类型数组array。接着通过for循环实现将变量i的值循环赋值给数组的每一个元素。值得注意的是array.Length,它表示数组的长度。
注意:数组的下标是从0开始的,而不是从1开始。很多初学者对此很不理解,其实在计算机中,很多计数都是从0开始的,它就像人们生活中,数数是从1开始一样。
值类型和引用类型这两种类型存储在内存的不同地方。值类型存储在堆栈中,而引用类型存储在托管堆上,这种存储位置的不同对数据操作会有不同的影响。关于各个类型是如何相关联的,如图2.6所示。
2.1.4 类型转换
类型转换是相对数据类型而言的,就是不同数据类型之间的转换。C#的数据类型转换分为隐式转换和显式转换。隐式转换就是将低精度数值转换为高精度数值,直接赋值而不用转换。例如int i=100;double d=i;显式转换就是将高精度数值转换为低精度数值,使用显式转换时,必须指明转换目标类型。
C#数值类型包括byte、short、int、long、float、double等。按照这个排列的顺序,各种类型可以依次向后自动进行转换。例如,把一个int型的数据赋值给一个long型的变量,int值会自动转换成long型值,然后再赋给long型变量。
图2.6 各个类型的关联关系
【范例2-4】下面来演示如何进行显式转换,它是从高精度转换成低精度,如示例代码2-4所示。
示例代码2-4
01 using System; //包含基本类和基类 02 …… //还有其他命名空间,也是系统生成,在此省略 03 namespace Ex_2_4 //程序的命名空间,也是项目名称,如Ex_2_4 04 { 05 class Program 06 { 07 static void Main(string[] args) //主函数,也是入口函数 08 { 09 double d = 100.5; //定义并初始化一个双精度型变量d 10 int i; //定义了一个整数型,用于接收转换后的值 11 i = (int)d; //进行显式转换,从double(双精度)到int(低精度) 12 Console.WriteLine(i); //输出结果 13 Console.ReadLine(); //获取输入焦点,为了看运行结果 14 } 15 } 16 }
【运行结果】选择“调试”|“启动调试”选项运行程序,结果如图2.7所示。
图2.7 显式转换运行结果
【代码解析】上述代码首先定义了double类型变量d,并赋值100.5。再定义了一个int型变量i用于接收转换后的数据结果。接着通过(int)把double型转换成int型,是从高精度转向低精度。读者可以从这个实例中体会显式与隐式的区别。