◆本章内容
(1)了解变量的基本概念
(2)掌握变量的声明及赋值
(3)熟悉变量的作用域
(4)掌握数值类型的概念及用法
(5)掌握引用类型的概念及用法
(6)熟悉枚举类型的概念及用法
(7)掌握常用的类型转换方式
(8)了解常量的概念及用法
◆本章简述
应用程序的开发离不开变量与常量的应用。变量本身被用来存储特定类型的数据,而常量则存储不变的数据值。本章详细地介绍变量的类型和基本操作,同时对常量也做了详细的讲解,讲解过程中为了便于读者理解结合了大量的举例。
◆知识框架
3.1 变量的基本概念
变量本身被用来存储特定类型的数据,可以根据需要随时改变变量中所存储的数据值。变量具有名称、类型和值。变量名是变量在程序源代码中的标识。变量类型确定它所代表的内存的大小和类型,变量值是指它所代表的内存块中的数据。在程序的执行过程中,变量的值可以发生变化。使用变量之前必须先声明变量,即指定变量的类型和名称。
3.2 变量的声明及赋值
变量在使用之前,必须进行声明并赋值,本节将对变量的声明及赋值,以及变量的作用域进行详细讲解。
3.2.1 声明变量
变量的使用是程序设计中一个十分重要的环节。为什么要定义变量呢?简单地说,就是要告诉编译器(compiler)这个变量是属于哪一种数据类型,这样编译器才知道需要配置多少空间给它,以及它能存放什么样的数据。在程序运行过程中,空间内的值是变化的,这个内存空间就称为变量。声明变量就是指定变量的名称和类型,变量的声明非常重要,未经声明的变量本身并不合法,也因此没有办法在程序当中使用。在C#中,声明一个变量是由一个类型和跟在后面的一个或多个变量名组成的,多个变量之间用逗号分开,声明变量以分号结束。
[例3.1]声明一个整型变量num, 然后再同时声明3个整型变量sum1、sum2和sum3,代码如下:
int num;//声明一个整型变量 int sum1, sum2, sum3;//同时声明3个整型变量
在第1行代码中,声明了一个名称为num的整型变量。在第2行代码中,声明了3个整型变量,分别为sum1、sum2和sum3。
声明变量时,还可以初始化变量,即在每个变量名后面加上给变量赋初始值的指令。
[例3.2]声明一个整型变量 a,并且赋值为927。然后,再同时声明3个整型变量,并初始化,代码如下:
int a = 927;//初始化整型变量a int x = 1, y = 2, z = 3;//初始化整型变量x,y和z
在声明变量时,要注意变量名的命名规则。C#的变量名是一种标识符,应该符合标识符的命名规则。变量名是区分大小写的,下而列出变量的命名规则。
◆变量名只能由数字、字母和下画线组成。
◆变量名的第一个符号只能是字母和下画线,不能是数字。
◆不能使用关键字作为变量名。
◆一旦在一个语句块中定义了一个变量名,那么在变量的作用域内都不能再定义同名的变量。
说明:在C#语言中允许使用汉字或其他语言文字作为变量名,如“int年龄=21”,在程序运行时并不出现什么错误,但建议读者尽量不要使用这些语言文字作为变量名。
3.2.2 变量的赋值
在C#中,使用赋值运算符“=”(等号)来给变量赋值,将等号右边的值赋给左边的变量。
[例3.3]声明一个变量,并给变量赋值,代码如下:
int sum;//声明一个变量 sum = 2019;//使用赋值运算符“=”给变量赋值
在3.2.1节介绍的初始化变量,其实是一种特殊的赋值方式,它在声明变量的同时给变量赋值。在给变量赋值时,等号右边也可以是一个已经被赋值的变量。
[例3.4]首先声明两个变量sum和num,然后将变量sum赋值为927,最后将变量sum赋值给变量num,代码如下:
int sum, num;//声明两个变量 sum = 927;//给变量sum赋值为927 num = sum;//将变量sum赋值给变量num
注意:在对多个同类型的变量赋同一个值时,为了节省代码的行数,可以同时对多个变量进行初始化:
int a, b, c, d, e; a = b = c = d = e = 0;
3.2.3 变量的作用域
由于变量被定义出来后只是暂存在内存中,等到程序执行到某一个点后,该变量会被释放掉,也就是说变量有它的生命周期。因此,变量的作用域是指程序代码能够访问该变量的区域,若超出该区域,则在编译时会出现错误。在程序中,一般会根据变量的“有效范围”将变量分为“成员变量"和“局部变量”。
1. 成员变量
在类体中定义的变量被称为成员变量,成员变量在整个类中都有效。类的成员变量又可分为两种,即静态变量和实例变量。
[例3.5]声明静态变量和实例变量,实例代码如下:
class Test { int x = 45; static int y = 90; }
其中,x为实例变量,y为静态变量(也称类变量)。如果在成员变量的类型前面加上关键字static,这样的成员变量称为静态变量。静态变量的有效范围可以跨类,甚至可达到整个应用程序之内。对于静态变量,除了能在定义它的类内存取,还能直接以“类名.静态变量”的方式在其他类内使用。
2. 局部变量
在类的方法体中定义的变量(方法内部定义,“{”与“}”之间的代码中声明的变量)称为局部变量。局部变量只在当前代码块中有效。
在类的方法中声明的变量,包括方法的参数,都属于局部变量。局部变量只有在当前定义的方法内有效,不能用于类的其他方法中。局部变量的生命周期取决于方法,当方法被调用时,C#编译器为方法中的局部变量分配内存空间,当该方法的调用结束后,则会释放方法中局部变量占用的内存空间,局部变量也将会销毁。
变量的有效范围如图3.1所示。
[例3.6]创建一个控制台应用程序,使用for循环将从0~20的数字显示出来。然后在for语句中声明变量i,此时i就是局部变量,其作用域只限于for循环体内,代码如下:
static void Main(string[] args) { //调用for语句循环输出数字 for(int i=0;i<=20;i++)//for循环内的局部变量i { Console.WriteLine(i.ToString());//输出0-20的数字 } Console.ReadLine(); }
程序运行结果如图。
3.3 数据类型
C#中的变量类型根据其定义可以分为两种:一种是值类型,另一种是引用类型。这两种类型的差异在于数据的存储方式,值类型的变量本身直接存储数据。而引用类型则存储实际数据的引用,程序通过此引用找到真正的数据,在以下内容中将会对这些类型进行详细讲解。C#中的数据类型结构如图3.2所示。
3.3.1 值类型
值类型变量直接存储其数据值,主要包含整数类型、浮点类型以及布尔类型等。值类型变量在栈中进行分配,因此效率很高,使用值类型主要目的是为了提高性能。值类型具有如下特性。
◆值类型变量都存储在栈中。
◆访问值类型变量时,一般都是直接访问其实例。
◆每个值类型变量都有自己的数据副本,因此对一个值类型变量的操作不会影响其他变量。
◆复制值类型变量时,复制的是变量的值,而不是变量的地址。
◆值类型变量不能为 null,必须具有一个确定的值。
值类型是从System.ValueType类继承而来的类型,下面详细介绍值类型中包含的几种数据类型。
1. 整数类型
整数类型用来存储整数数值,即没有小数部分的数值。可以是正数,也可以是负数。整型数据在C#程序中有3种表示形式,分别为十进制、八进制和十六进制。
十进制: 十进制的表现形式大家都很熟悉,如120、0、-127。
注意:不能以0作为十进制数的开头(0除外)。
八进制: 如0123(转换成十进制数为83)、-0123(转换成十进制数为-83)。
注意:八进制必须以0开头。
十六进制: 如0x25(转换成十进制数为37)、0Xb0le(转换成十进制数为45086)。
注意:十六进制必须以0X或0x开头。
在C#中内置的整数类型如表3.1所示。
表3.1 C#内置的整数类型 |
|||
类型 |
别名 |
说明(8位等于1字节) |
范围 |
sbyte |
System.SByte |
8位有符号整数 |
-128~127 |
short |
System.Int16 |
16位有符号整数 |
-32768~32767 |
int |
System.Int32 |
32位有符号整数 |
-2147483648~2147483647 |
long |
System.Int64 |
64位有符号整数 |
-9223372036854775808~9223372036854775807 |
byte |
System.Byte |
8位无符号整数 |
0~255 |
ushort |
System.UInt16 |
16位无符号整数 |
0~65535 |
uint |
System.UInt32 |
32位无符号整数 |
0~4294967295 |
ulong |
System.UInt64 |
64位无符号整数 |
0~18446744073709551615 |
byte类型以及short类型是范围比较小的整数,如果正整数的范围没有超过65535,声明为ushort类型即可,当然更小的数值直接以byte类型作处理即可。只是使用这种类型时必须特别注意数值的大小,否则可能会导致运算溢出的错误。
[例3.7]创建一个控制台应用程序,在其中声明一个int类型的变量a并初始化为927、一个byte类型的变量b并初始化为255,最后输出,代码如下:
static void Main(string[] args) { int a = 927;//声明一个int类型的变量a byte b = 255;//声明一个byte类型的变量b Console.WriteLine("a={0}", a);//输出int类型的变量a Console.WriteLine("b={0}", b);//输出byte类型的变量b Console.ReadLine(); }
程序运行结果为:
此时,如果将byte类型的变量b赋值为266,重新编译程序,就会出现错误提示。
主要原因是byte类型的变量是8位无符号整数,它的范围为0-255, 266已经超出了byte类型的范围,所以编译程序会出现错误提示。
注意:1、在定义局部变量时,要对其进行初始化。
2、在定义全局变量时,如果没有特定的要求不用对其进行初始化,整数类型默认初始化为0。
2. 浮点类型
浮点类型变量主要用于处理含有小数的数值数据,浮点类型主要包含float和double两种数值类型。
表3.2列出了这两种数值类型的描述信息。
表3.2浮点类型及描述 |
|||
类型 |
别名 |
说明 |
范围 |
float |
System.Single |
精确到7位数 |
±1.5×10-45~±3.4×1038 |
double |
System.Double |
精确到15~16位数 |
±5.0×10-324~±3.4×10308 |
如果不做任何设置,包含小数点的数值都被认为是double类型,例如9.27,没有特别指定的情况下,这个数值是double类型。如果要将数值以float类型来处理,就应该通过强制使用f或F将其指定为float类型。
[例3.8]下面的代码就是将数值强制指定为float类型。
float theMySum = 9.27f;//使用f强制指定为float类型 float theMuSums = 1.12F;//使用F强制指定为float类型
如果要将数值强制指定为double类型,则应该使用d或D进行设置,但加不加d或D没有硬性规定,可以加也可以不加。
[例3.9]下面的代码就是将数值强制指定为double类型。
double myDou = 927d;//使用d强制指定为double类型 double mudou = 112D;//使用D强制指定为double类型
注意:1、如果需要使用float类型变量时,必须在数值的后面跟随f或F,否则编译器会直接将其作为double类型处理。也可以在double类型的值前面加上(float), 对其进行强制转换。
2、在定义局部变量时,要对其进行初始化。
3、在定义全局变量时,如果没有特定的要求不用对其进行初始化,浮点类型默认初始化为0而不是0.0。
3. decimal类型
decimal类型表示128位数据类型,它是一种精度更高的浮点类型,其精度可以达到28位,取范围为±1.0×1028~±7.9×1028。
说明:由于decimal类型的高精度特性,它更合适于财务和货币计算。
如果希望一个小数被当成decimal类型,需要使用后缀m或M,例如:
decimal myMoney = 1.12m;
如果小数没有后缀m或M,数值将被视为double类型,从而导致编译器错误,例如,在开发环境中运行下面代码:
static void Main(string[] args) { decimal d = 3.14; Console.WriteLine(d); }
将会出现如图所示的错误提示。
从上图可以看出,3.14这个数如果没有后缀,直接被视为double类型,所以赋值给decimal类型的变量时,就会出现错误提示,应该将3.14改为3.14m或3.14M。
4. 布尔类型
布尔类型主要用来表示true和false值,C#中定义布尔类型时,需要使用bool关键字。例如:
bool x;
布尔类型通常被用在流程控制中作为判断条件。
一个布尔类型的变量,其值只能是true或者false,不能将其他的值指定给布尔类型变量,布尔类型变量不能与其他类型之间进行转换。
[例3.10]将927赋值给布尔类型变量x,代码如下。
bool x = 927;
这样赋值显然是错误的,编译器会返回错误提示,如图
布尔类型变量大多数被应用到流程控制语句当中,例如,循环语句或者if语句等。
注意:1、在定义局部变量时,要对其进行初始化。
2、在定义全局变量时,如果没有特定的要求不用对其进行初始化,布尔类型的默认初始化为false。
5. 闯关训练
开发财务系统时,通过值类型创建存储流动资金金额的临时性变量。
3.3.2 引用类型
引用类型是构建C#应用程序的主要对象类型数据。在应用程序执行的过程中,预先定义的对象类型以new创建对象实例,并且存储在堆中。堆是一种由系统弹性配置的内存空间,没有特定大小及存活时间,因此可以被弹性地运用于对象的访问。引用类型就类似于生活中的代理商,代理商没有自己的产品,而是代理厂家的产品,使其就好像是自己的产品一样。
引用类型具有如下特征。
◆必须在托管堆中为引用类型变量分配内存。
◆使用new关键字来创建引用类型变量。
◆在托管堆中分配的每个对象都有与之相关联的附加成员,这些成员必须被初始化。
◆引用类型变量是由垃圾回收机制来管理的。
◆多个引用类型变量可以引用同一对象,这种情形下,对一个变量的操作会影响另一个变量所引用的同一对象。
◆引用类型被赋值前的值都是null。
所有被称为“类”的都是引用类型,主要包括类、接口、数组和委托。下面通过一个实例来演示如何使用引用类型。
[例3.11]创建一个控制台应用程序,在其中创建一个类C,在此类中建立一个字段 Value,并初始化为0,然后在程序的其他位置通过new关键字创建对此类的引用类型变量,最后输出,代码如下:
class Program { class C //创建一个类C { public int Value = 0; //声明一个公共int类型的变量Value } static void Main(string[] args) { int v1 = 0;//声明一个int类型的变量v1,并初始化为0 int v2 = v1;//声明一个int类型的变量v2,并将v1赋值给v2 v2 = 927;//重新将变量v2赋值为927 C r1 = new C();//使用new关键字创建引用对象 C r2 = r1;//使r2=r1 r2.Value = 112;//设置变量r2的Value值 Console.WriteLine("值类型变量的值:{0},{1}", v1, v2);//输出变量v1和v2 Console.WriteLine("引用类型对象的值:{0},{1}", r1.Value, r2.Value);//输出引用类型对象的Value值 } }
程序运行结果如下。
3.3.3 值类型与引用类型的区别
从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。这两种类型存储在内存的不同地方。在C#中,必须在设计类型时就决定类型实例的行为。如果在编写代码时不能理解引用类型和值类型的区别,那么将会给代码带来不必要的异常。
从内存空间上看,值类型是在栈中操作,而引用类型则在堆中分配存储单元。栈在编译时就分配好内存空间,在代码中有栈的明确定义,而堆是程序运行中动态分配的内存空间,可以根据程序的运行情况动态地分配内存的大小。因此,值类型总是在内存中占用一个预定义的字节数。而引用类型的变量则在堆中分配一个内存空间,这个内存空间包含的是对另一个内存位置的引用,这个位置是托管堆中的一个地址,即存放此变量实际值的地方。
也就是说值类型相当于现金,要用就直接用,而引类型相当于存折,要用得先去银行取。
说明:C#的所有值类型均隐式派生自System.ValueType,而System.ValueType 直接派生于System.Object.即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Eouals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
下面以一段代码来详细讲解一下值类型与引用类型的区别,代码如下。
namespace ConsoleApp1 { class Program { static void Main(string[] args) { ReferenceAndValue.Demonstration();//调用ReferenceAndValue类中的Demonstation方法 Console.ReadLine(); } } public class Stamp//定义一个类 { public string Name { get; set; }//定义引用类型 public int Age { get; set; }//定义值类型 } public static class ReferenceAndValue//定义一个静态类 { public static void Demonstration()//定义一个静态方法 { Stamp Stamp_1 = new Stamp { Name = "Premiere", Age = 25 };//实例化 Stamp Stamp_2 = new Stamp { Name = "Again", Age = 47 };//实例化 int age = Stamp_1.Age; //获取值类型Age的值 Stamp_1.Age = 22;//修改值类型的值 Stamp guru = Stamp_2;//获取Stamp_2中的值 Stamp_2.Name = "Again Amend";//修改引用的Name值 Console.WriteLine("Stamp_1 中的 age 值:{0}", Stamp_1.Age);//显示Stamp_1中的Age值 Console.WriteLine("age 的值:{0}", age);//显示age值 Console.WriteLine("Stamp_2 中的 name 值:{0}", Stamp_2.Name);//显示Stamp_2中的Name值 Console.WriteLine("guru 中的 name 值:{0}", guru.Name);//显示guru中的Name值 } } }
运行结果如图。
从上图中可以看出,当改变了Stamp_1.Age的值时,age没跟着变,而在改变了Stamp_2.Name的值后,guru.Name却跟着变了,这就是值类型和引用类型的区别。在声明age值类型变量时,将Stamp_1.Age的值赋给它,这时,编译器在栈上分配了一块空间,然后把Stamp_1.Age的值填进去,二者没有任何关联,就像在计算机中复制文件一样,只是把Stamp_1.Age的值复制给age了。而引用类型则不同,在声明guru时把Stamp_2赋给它。前面说过,引用类型包含的只是堆上数据区域地址的引用,其实就是把Stamp_2的引用也赋给guru,因此它们指向了同一块内存区域。既然是指向同一块区域,不管修改谁,另一个的值都会跟着改变。就像信用卡跟亲情卡一样,用亲情卡取了钱,与之关联的信用卡账上也会跟着发生变化。
3.3.4 枚举类型
枚举类型是一种独特的值类型,它用于声明一组具有相同性质的常量,编写与日期相关的应用程序时,经常需要使用年、月、日、星期等日期数据,可以将这些数据组织成多个不同名称的枚举类型。使用枚举可以增加程序的可读性和可维护性。同时,枚举类型可以避免类型错误。
说明:在定义枚举类型时,如果不对其进行赋值,默认情况下,第一个枝举数的值为0,后面每个枚举数的值依次递增1。
在C#中使用关键字enum类声明枚举,其形式如下。
enum 枚举明 { list1 = value1, list2 = value2, list3 = value3, ... listN = valueN }
其中,大括号“{}”中的内容为枚举值列表,每个枚举值均对应一个枚举值名称,value1-valueN为整数数据类型,list1-listN则为枚举值的标识名称。下而通过一个实例来演示 如何使用枚举类型。
[例3.12]创建一个控制台应用程序,通过使用枚举米判断当前系统日期是星期几,代码如下。
class Program { enum MyDate //使用enum创建枚举 { Sun = 0, //设置枚举值名称Sun,枚举值为0 Mon = 1, //设置枚举值名称Mon,枚举值为1 Tue = 2, //设置枚举值名称Tue,枚举值为2 Wed = 3, //设置枚举值名称Wed,枚举值为3 Thu = 4, //设置枚举值名称Thu,枚举值为4 Fri = 5, //设置枚举值名称Fri,枚举值为5 Sat = 6 //设置枚举值名称Sat,枚举值为6 } static void Main(string[] args) { int k = (int)DateTime.Now.DayOfWeek;//获取代表星期几的返回值 switch (k) { //如果k等于枚举变量MyDate中的Sun的枚举值,则输出今天是星期日 case (int)MyDate.Sun: Console.WriteLine("今天是星期日"); break; //如果k等于枚举变量MyDate中的Mon的枚举值,则输出今天是星期一 case (int)MyDate.Mon: Console.WriteLine("今天是星期一"); break; //如果k等于枚举变量MyDate中的Tue的枚举值,则输出今天是星期二 case (int)MyDate.Tue: Console.WriteLine("今天是星期二"); break; //如果k等于枚举变量MyDate中的Wed的枚举值,则输出今天是星期三 case (int)MyDate.Wed: Console.WriteLine("今天是星期三"); break; //如果k等于枚举变量MyDate中的Thu的枚举值,则输出今天是星期四 case (int)MyDate.Thu: Console.WriteLine("今天是星期四"); break; //如果k等于枚举变量MyDate中的Fri的枚举值,则输出今天是星期五 case (int)MyDate.Fri: Console.WriteLine("今天是星期五"); break; //如果k等于枚举变量MyDate中的Sat的枚举值,则输出今天是星期六 case (int)MyDate.Sat: Console.WriteLine("今天是星期六"); break; } Console.ReadLine(); } }
程序运行的结果如图
查看程序运行的结果,因为当前日期是2021年5月3日星期一,所以输出的结果显示当天是星期一。程序首先通过enum关键字建立一个枚举,枚举值名称分别代表一周的七天,如果枚举值名称是Sun,说明其代表的是一周中的星期日,其枚举值为0,以此类推。然后,声明一个int类型的变量k,用于获取当前表示的日期是星期几。最后,调用switch语句,输出当天是星期几。
3.3.5 类型转换
类型转换就是将一种类型转换成另一种类型,转换可以是隐式转换,也可以是显式转换,本节将详细介绍这两种转换方式,并讲解有关装箱和拆箱的内容。
说明:要理解类型转换,读者可以这么想象,大脑前面是一片内存,源和目标分别是两个大小不同的内存块(由变量及数据的类型来决定),将源数据赋值给目标内存的过程,就是用目标内存块去套取源内存中的数据,能套多少算多少。
1. 隐式转换
所谓隐式转换就是不需要声明就能进行的转换。进行隐式转换时,编译器不需要进行检查就能自动进行转换。表3.3列出了可以进行隐式转换的数据类型。
表3.3隐式类型转换表 |
|
源类型 |
目标类型 |
sbyte |
short、int、long、float、double、decimal |
byte |
short、ushort、int、uint、long、ulong、float、double或decimal |
short |
int、long、float、double或decimal |
ushort |
int、uint、long、ulong、float、double或decimal |
int |
long、float、double或decimal |
uint |
long、ulong、float、double或decimal |
char |
ushort、int、uint、long、ulong、float、double或decimal |
float |
double |
ulong |
float、double或decimal |
long |
float、double或decimal |
从int、uint、long或ulong到float,以及从long或ulong到double的转换可能导致精度损失,但是不会影响其数量级。其他的隐式转换不会丟失任何信息。
这些类型按精度从“低”到“高”排列的顺序为byte>short>int>long>float> double,可对照图3.3,其中char类型比较特殊,它可以与部分int型数字兼容,且不会发生精度变化。
说明:当一种类型的值转换为大小相等或更大的另一类型时,则发生扩大转换;当一种类型的值转换为较小的另一种类型时,则发生收缩转换。
[例3.13]将int类型的值隐式转换成long类型,代码如下。
int i = 927;//声明一个整型变量i并初始化为927 long j = i;//隐式转换成long类型
2. 显式转换
显式转换也可以称为强制转换,需要在代码中明确地声明要转换的类型。如果要把高精度的变量的值赋给低精度的变量,就需要使用显式转换。表3.4列出了需要进行显式转换的数据类型。
表3.4显式类型转换表 |
|
源类型 |
目标类型 |
sbyte |
byte、ushort、uint、ulong或char |
byte |
sbyte和char |
short |
sbyte、byte、ushort、uint、ulong或char |
ushort |
sbyte、byte、short或char |
int |
sbyte、byte、short、ushort、uint、ulong或char |
uint |
sbyte、byte、short、ushort、int或char |
char |
sbyte、byte或short |
float |
sbyte、byte、short、ushort、int、uint、long、ulong、char或decimal |
ulong |
sbyte、byte、short、ushort、int、uint、long或char |
long |
sbyte、byte、short、ushort、int、uint、ulong或char |
double |
sbyte、byte、short、ushort、int、uint、ulong、long、float、char或decimal |
decimal |
sbyte、byte、short、ushort、int、uint、ulong、long、float、char或double |
由于显式转换包括所有隐式转换和显式转换,因此总是可以使用强制转换表达式从任何数值类型转换为任何其他的数值类型。
显式类型转换的一般形式为:
(类型说明符)表达式
其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。
例如,下面的代码用来把x转换为float类型:
(float)x;
[例3.14]创建一个控制台应用程序,将double类型的x进行显式类型转换,代码如下。
static void Main(string[] args) { double x = 19810927.0112;//声明double类型变量x int y = (int)x;//显示转换成整型变量y Console.WriteLine(y); //输出整型变量y Console.ReadLine(); }
程序运行结果为
3. 使用Convert类进行转换
上节中讲解了可以使用“(类型说明符)表达式”进行显式类型转换,使用这种方式实现将long型数据转换成int型数据:
long l = 3000000000;//声明long类型变量l int i = (int)l;//显示转换为int变量i Console.WriteLine(i);//输出变量i
程序运行结果为:
按照代码的本意,i的值应该是3000000000,但在运行这两行代码时,却发现i的值是-1294967296,这主要是由于int类型的最大值为2147483647,很明显,3000000000要比2147483647大,所以在使用上面代码进行显式类型转换时,出现了与预期不符的结果,但是程序并没有报告错误。如果在实际开发中遇到这种情况,可能会引起大的BUG,那么,在遇到这种错误时,有没有一种方式能够向开发人员报告错误呢?答案是肯定的。C#中提供了Convert类,该类也可以进行显式类型转换,它的主要作用是将一个基本数据类型转换为另一个基本数据类型。Convert类的常用方法及说明如表3.3所示。
表3.3 Convert类的常用方法 |
|
方法 |
说明 |
ToBoolean |
将指定的值转换为等效的布尔值 |
ToByte |
将指定的值转换为8位无符号整数 |
ToChar |
将指定的值转换为Unicode字符 |
ToDateTime |
将指定的值转换为DateTime |
ToDecimal |
将指定值转换为Decimal数字 |
ToDouble |
将指定的值转换为双精度浮点数字 |
Tolnt32 |
将指定的值转换为32位有符号整数 |
Tolnt64 |
将指定的值转换为64位有符号整数 |
ToSByte |
将指定的值转换为8位有符号整数 |
ToSingle |
将指定的值转换为单精度浮点数字 |
ToString |
将指定值转换为其等效的String表示形式 |
ToUInt32 |
将指定的值转换为32位无符号整数 |
ToUInt64 |
将指定的值转换为64位无符号整数 |
[例3.15]创建一个控制台应用程序,定义一个double类型的变量x,并赋值为198.99,使用Convert类将其显式转换为int类型,代码如下:
double x = 198.99;//声明double类型变量并初始化 int y = Convert.ToInt32(x);//使用Convert类的方法进行显示类型转换 Console.WriteLine(y);//输出变量y
程序运行结果为:
下面使用Convert类的ToInt32对本节开始的两行代码进行修改,修改后的代码如下:
long l = 3000000000;//声明long类型变量l int i = Convert.ToInt32(l);//显示转换为int变量i Console.WriteLine(i);//输出变量i
再次运行这两行代码,则会出现如图3.38所示的错误提示。
这样,开发人员即可根据图中的错误提示对程序代码进行修改,避免程序出现逻辑错误。
4. 装箱和拆箱
将值类型转换为引用类型的过程叫作装箱,相反,将引用类型转换为值类型的过程叫作拆箱,下面将通过例子详细介绍装箱与拆箱的过程。
(1)装箱。装箱允许将值类型隐式转换成引用类型,下而通过一个实例演示如何进行装箱操作。
[例3.16]创建一个控制台应用程序,声明一个整型变量i并赋值为2019,然后将其复制到装箱对象obj中,最后再改变变量i的值,化码如下。
static void Main(string[] args) { int i = 2019;//声明一个int类型变量i,井初始化为2019 object obj = i;//声明-个object类型obj,其初始化值为i Console.WriteLine("1、i的值为{0},装箱之后的对象为{1}", i, obj); i = 927;//重新将i赋值为927 Console.WriteLine("2、i的值为{0},装箱之后的对象为{1}", i, obj); Console.ReadLine(); }
程序的运行结果为:
从程序运行结果可以看出,值类型变量的值复制到装箱得到的对象中,装箱后改变值类型变量的值,并不会影响装箱对象的值。
(2)拆箱。拆箱允许将引用类型显式转换为值类型,下面通过一个示例演示拆箱的过程。
[例3.17]创建一个控制台应用程序,声明一个整型变量i并赋值为112,然后将其复制到装箱对象obj中,最后,进行拆箱操作将装箱对象obj赋值给整型变量j,代码如下。
static void Main(string[] args) { int i = 112;//声明-个int类型的变量i,并初始化为112 object obj = i;//执行装箱操作 Console.WriteLine("装箱操作: 值为{0},装箱之后对象为{1}", i, obj); int j = (int)obj;//执行拆箱操作 Console.WriteLine("拆箱操作:装箱对象为{0},值为{1}", obj, i); Console.ReadLine(); }
程序运行结果为:
查看程序运行结果,不难看出,拆箱后得到的值类型数据的值与装箱对象相等。需要读者注意的是,在执行拆箱操作时要符合类型一致的原则,否则会出现异常。
说明:装箱是将一个值类型转换为一个对象类型(object),而拆箱则是将一个对象类型显式转换为一个值类型。对于装箱而言,它是将被装箱的值类型复制一个副本来转换;而对于拆箱而言,需要注意类型的兼容性,例如,不能将一个值为 “string"的object类型转换为int类型。
3.4 声明隐式类型的局部变量
本章前面通过指定数据类型和标识符来声明变量,如下所示:
int myInt;
前面说过,变量使用前必须赋值。可在同一个语句中声明并初始化变量,如下所示:
int myInt = 99;
还可像下面这样做(假定myOtherInt是已初始化的整数变量):
int myInt = myOtherInt * 99;
赋给变量的值必须具有和变量相同的类型。例如,只能将int值赋给int变量。C#编译器可迅速判断变量初始化表达式的类型,如果和变量类型不符,就会明确告诉你。除此之外,还可要求C#编译器根据表达式推断变量类型,并在声明变量时自动使用该类型。为此,只需用var关键字代替类型名称,如下所示:
var myVariable = 99; var myOtherVariable = "Hello";
两个变量myVariable和myOtherVariable称为隐式类型变量。var关键字告诉编译器根据变量的初始化表达式推断变量类型。在本例中,myVariable 是int类型,而myOtherVariable是string类型。必须注意,var只是在声明变量时提供一些方便。但变量一经声明,就只能将编译器推断的那种类型的值赋给它。例如,不能再将float、double、string值赋给myVariable。还要注意,只有提供了初始化表达式,才能使用关键字var。
以下声明非法,会导致编译错误:
var yetAnotherVariable; //错误-编译器不能推断类型 }
3.5 常量
常量就是其值固定不变的量,而且常量的值在编译时就已经确定了。常量的类型只能为下列类型之一: sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string等。C#中使用关键字const和readonly定义常量,下面分别对这两种常量进行讲解。
3.5.1 const常量
在C#中提到常量,通常指的是const常量。const常量也叫静态常量,它在编译时就已经确定了值。const常量的值必须在声明时就进行初始化,而且之后不可以再进行更改。
[例3.18]声明一个正确的常量,同时再声明一个错误的常量,以便读者对比参考,代码如下。
const double PI = 3.1415926;//正确的声明方法 const int MyInt;///错误:定义常量时没有初始化
3.5.2 readonly常量
readonly常量是一种特殊的常量,也称为动态常量,从字面理解上看,readonly常量可以进行动态赋值,但需要注意的是,这里的动态赋值是有条件的,它只能在构造函数中进行赋值,例如,下面的代码:
class Program { readonly int Price;//定义一个reanonly常量 Program()//构造函数 { Price = 368;//在构造函数中修改reanonly常量的值 } static void Main(string[] args) { } }
如果要在构造函数以外的位置修改readonly常量的值,比如,在Main方法中进行修改,代码如下:
class Program { readonly int Price;//定义一个reanonly常量 Program()//构造函数 { Price = 368;//在构造函数中修改reanonly常量的值 } static void Main(string[] args) { Program p = new Program();//创建类的对象 p.Price = 365;//试图对readonly常量值修改 } }
这时再运行程序,将会出现如图所示的错误提示。
3.5.3 const常量与readonly常量的区别
const常量与readonly常量的主要区别如下:
◆const常量必须在声明时初始化,而readonly常量则可以延迟到构造函数中初始化。
◆ const常量在编译时就被解析,即将常量的值替换成了初始化的值,而readonly常量的值需要在运行时确定。
◆ const常量可以在类中或者方法体中定义,而readonly常量只能在类中定义。
与变量不同,常量在整个程序中只能被赋值一次。在为所有的对象共享值时,常量是非常有用的。下面通过一个例子演示常量与变量的差异。
[例3.19]创建一个控制台应用程序,首先声明一个变量MyInt并且赋值为927,然后再声明一个常量MyWInt并赋值为112,最后将变量MyInt赋值为1039,关键代码如下。
static void Main(string[] args) { int MyInt = 927;//声明一个整型变量 const int MyWInt = 112;//声明一个整型常量 Console.WriteLine("变量MyInt={0}", MyInt);//输出 Console.WriteLine("常量MyWInt={0}", MyWInt);//输出 MyInt = 1039;//重新将变量赋值为1039 Console.WriteLine("变量MyInt={0}", MyInt);//输出 Console.ReadLine(); }
执行程序,输出的结果为:
变量MyInt的初始化值为927,而常量MyWnt的值等于112,由于变量的值是可以修改的,所以变量MyInt可以重新被赋值为1039后输出。通过直看输出结果,可以看到变量Mylnt的值已经被修改,如果尝试修改常量MyWInt的值,编译器会出现错误信息,阻止进行这样的操作。
3.6 小结
本章重点讲解了变量和常量,通过大量的举例说明,使读者更好地理解所学知识的用法。在阅读本章时,要重点掌握值类型、引用类型和枚举类型的概念及用法,并且要了解如何进行类型转换。了解变量的基本知识后,要掌握如何对变量进行操作,了解变量的作用域以及如何为变量赋值。本章最后对常量进行了详细的叙述,包括常量的概念及常量的基本类型。
3.7 实践与练习
(1)尝试开发一个程序,在该程序中建立一个静态方法,在静态方法中声明一个局部变量,并对其赋值,然后输出。
(2)尝试开发一个程序,要求声明一个常量,然后试着更改这个常量的值,看会引发什么错误。