C#数据类型
值类型和引用类型区别
在C#语言中,值类型变量存储的是指定数据类型的数据,值类型变量的值(或实例)存储在栈(Stack)中,赋值语句是传递变量的值。引用类型(例如类就是引用类型)的实例,也叫对象,不存在栈中,而存储在可管理堆(Managed Heap)中,堆实际上是计算机系统中的空闲内存。引用类型变量的值存储在栈(Stack)中,但存储的不是引用类型对象,而是存储引用类型对象的引用,即地址,和指针所代表的地址不同,引用所代表的地址不能被修改,也不能转换为其它类型地址,它是引用型变量,只能引用指定类对象,引用类型变量赋值语句是传递对象的地址。见下例:
using System;
class MyClass//类为引用类型
{ public int a=0;
}
class Test
{ static void Main()
{ f1();
}
static public void f1()
{ int v1=1;//值类型变量v1,其值1存储在栈(Stack)中
int v2=v1;//将v1的值(为1)传递给v2,v2=1,v1值不变。
v2=2;//v2=2,v1值不变。
MyClass r1=new MyClass();//引用变量r1在栈中存储MyClass类对象的地址
MyClass r2=r1;//r1和r2都代表是同一个MyClass类对象
r2.a=2;//和语句r1.a=2等价
}
}
存储在栈中的变量,当其生命周期结束,自动被撤销,例如,v1存储在栈中,v1和函数f1同生命周期,退出函数f1,v1不存在了。但在堆中的对象不能自动被撤销。因此C和C++语言,在堆中建立的对象,不使用时必须用语句释放对象占用的存储空间。.Net系统CLR内建垃圾收集器,当对象的引用变量被撤销,表示对象的生命周期结束,垃圾收集器负责收回不被使用的对象占用的存储空间。例如,上例中引用变量r1及r2是MyClass类对象的引用,存储在栈中,退出函数f1,r1和r2都不存在了,在堆中的MyClass类对象也就被垃圾收集器撤销。也就是说,CLR具有自动内存管理功能。
1.4.1 值类型变量分类
C#语言值类型可以分为以下几种:
l 简单类型(Simple types)
简单类型中包括:数值类型和布尔类型(bool)。数值类型又细分为:整数类型、字符类型(char)、浮点数类型和十进制类型(decimal)。
l 结构类型(Struct types)
l 枚举类型(Enumeration types)
C#语言值类型变量无论如何定义,总是值类型变量,不会变为引用类型变量。
1.4.2 结构类型
结构类型和类一样,可以声明构造函数、数据成员、方法、属性等。结构和类的最根本的区别是结构是值类型,类是引用类型。和类不同,结构不能从另外一个结构或者类派生,本身也不能被继承,因此不能定义抽象结构,结构成员也不能被访问权限控制字protected修饰,也不能用virtual和abstract修饰结构方法。在结构中不能定义析构函数。虽然结构不能从类和结构派生,可是结构能够继承接口,结构继承接口的方法和类继承接口的方法基本一致。接口的概念见以后章节。下面例子定义一个点结构point:
using System;
struct point//结构定义
{ public int x,y;//结构中也可以声明构造函数和方法,变量不能赋初值
}
class Test
{ static void Main()
{ point P1;
P1.x=166;
P1.y=111;
point P2;
P2=P1;//值传递,使P2.x=166,P2.y=111
point P3=new point();//用new生成结构变量P3,P3仍为值类型变量
}//用new生成结构变量P3仅表示调用默认构造函数,使x=y=0。
}
1.4.3 简单类型
简单类型也是结构类型,因此有构造函数、数据成员、方法、属性等,因此下列语句int i=int.MaxValue;string s=i.ToString()是正确的。即使一个常量,C#也会生成结构类型的实例,因此也可以使用结构类型的方法,例如:string s=13.ToString()是正确的。简单类型包括:整数类型、字符类型、布尔类型、浮点数类型、十进制类型。见下表:
保留字 | System命名空间中的名字 | 字节数 | 取值范围 |
sbyte | System.Sbyte | 1 | -128~127 |
byte | System.Byte | 1 | 0~255 |
short | System.Int16 | 2 | -32768~32767 |
ushort | System.UInt16 | 2 | 0~65535 |
int | System.Int32 | 4 | -2147483648~2147483647 |
uint | System.UInt32 | 4 | 0~4292967295 |
long | System.Int64 | 8 | -9223372036854775808~9223372036854775808 |
ulong | System.UInt64 | 8 | 0~18446744073709551615 |
char | System.Char | 2 | 0~65535 |
float | System.Single | 4 | 3.4E-38~3.4E+38 |
double | System.Double | 8 | 1.7E-308~1.7E+308 |
bool | System.Boolean |
| (true,false) |
decimal | System.Decimal | 16 | 正负 1.0*10-28 到7.9*1028之间 |
C#简单类型使用方法和C、C++中相应的数据类型基本一致。需要注意的是:
l 和C语言不同,无论在何种系统中,C#每种数据类型所占字节数是一定的。
l 字符类型采用Unicode字符集,一个Unicode标准字符长度为16位。
l 整数类型不能隐式被转换为字符类型(char),例如char c1=10是错误的,必须写成:char c1=(char)10,char c='A',char c='\x0032';char c='\u0032'。
l 布尔类型有两个值:false,true。不能认为整数0是false,其它值是true。bool x=1是错误的,不存在这种写法,只能写成x=true 或x=false。
l 十进制类型(decimal)也是浮点数类型,只是精度比较高,一般用于财政金融计算。
1.4.4 枚举类型
C#枚举类型使用方法和C、C++中的枚举类型基本一致。见下例:
using System;
class Class1
{ enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
//使用Visual Studio.Net,enum语句添加在[STAThread]前边
static void Main(string[] args)
{ Days day=Days.Tue;
int x=(int)Days.Tue;//x=3
Console.WriteLine("day={0},x={1}",day,x);//显示结果为:day=Tue,x=3
}
}
在此枚举类型Days中,元素的默认类型为int,默认第1个元素的值为0,其后元素按1递增,本例Sat=0,Sun=1,Mon=2,…依此类推。也可以直接给枚举元素赋值。例如:
enum Days{Sat=6,Sun=7,Mon=1,Tue,Wed,Thu,Fri};
在此枚举Days中,Sat=6,Sun=7,Mon=1,Tue=2,…Fri=5,等等。和C、C++中不同,C#枚举元素类型可以是byte、sbyte、short、ushort、int、uint、long和ulong类型,但不能是char类型。见下例:
enum Days:byte{Sun,Mon,Tue,Wed,Thu,Fri,Sat};//元素为字节类型
1.4.5 值类型的初值和默认构造函数
C#语言要求所有变量都必须有初值,如没有赋值,采用默认值。对于简单类型,sbyte、byte、short、ushort、int、uint、long和ulong默认值为0,char类型默认值是(char)0,float为0.0f,double为0.0d,decimal为0.0m,bool为false,枚举类型为0,在结构类型和类中,数据成员的数值类型变量设置为默认值,引用类型变量设置为null。
可以显示的赋值,例如int i=0。而对于复杂结构类型,其中的每个数据成员都按此种方法赋值,显得过于麻烦。由于数值类型都是结构类型,可用new语句调用其构造函数初始化数值类型变量,例如:int j=new int()。请注意,用new语句并不是把int变量变为引用变量,j仍是值类型变量,这里new仅仅是调用其构造函数。所有的数值类型都有默认的无参数的构造函数,其功能就是为该数值类型赋初值为默认值。对于自定义结构类型,由于已有默认的无参数的构造函数,不能再定义无参数的构造函数,但可以定义有参数的构造函数。
1.4.6 引用类型分类
C#语言中引用类型可以分为以下几种:
l 类:C#语言中预定义了一些类:对象类(object类)、数组类、字符串类等。当然,程序员可以定义其它类。
l 接口。
l 代表。
C#语言引用类型变量无论如何定义,总是引用类型变量,不会变为值类型变量。C#语言引用类型对象一般用运算符new建立,用引用类型变量引用该对象。本节仅介绍对象类型(object类型)、字符串类型、数组。其它类型在其它节中介绍。
1.4.7 对象类(object类)
C#中的所有类型(包括数值类型)都直接或间接地以object类为基类。对象类(object类)是所有其它类的基类。任何一个类定义,如果不指定基类,默认object为基类。继承和基类的概念见以后章节。C#语言规定,基类的引用变量可以引用派生类的对象(注意,派生类的引用变量不可以引用基类的对象),因此,对一个object的变量可以赋予任何类型的值:
int x =25;
object obj1;
obj1=x;
object obj2= 'A';
object关键字是在命名空间System中定义的,是类System.Object的别名。
1.4.8 数组类
在进行批量处理数据的时候,要用到数组。数组是一组类型相同的有序数据。数组按照数组名、数据元素的类型和维数来进行描述。C#语言中数组是类System.Array类对象,比如声明一个整型数组:int[] arr=new int[5],实际上生成了一个数组类对象,arr是对数组类对象的引用(地址),只能通过arr[索引]方法使用数组元素。请注意定义方法和C语言的不同。在C#语言中,数组元素的索引是不允许越界的,否则将产生异常。
在C#中数组可以是一维的也可以是多维的,也支持数组的数组,即数组的元素还是数组。一维数组最为普遍,用的也最多。我们先看一个一维数组的例子:
using System;
class Test
{ static void Main()
{ int[] arr=new int[3];//用new运算符建立一个3个元素的一维数组
for(int i=0;i<arr.Length;i++)//arr.Length是数组类属性,表示数组元素个数
arr[i]=i*i;//数组元素赋初值,arr[i]表示第i个元素的值
for (int i=0;i<arr.Length;i++)//数组第一个元素的下标为0
Console.WriteLine("arr[{0}]={1}",i,arr[i]);
}
}
这个程序创建了一个int类型3个元素的一维数组,初始化后逐项输出。其中arr.Length表示数组元素的个数。注意数组定义不能写为C语言格式:int arr[]。程序的输出为:
arr[0] = 0
arr[1] = 1
arr[2] = 4
上面的例子中使用的是一维数组,下面介绍多维数组:
string[] a1;//一维string数组类引用变量a1
string[,] a2;//二维string数组类引用变量a2
a2=new string[2,3];//用new建立二维数组对象,a2引用这个对象
string[,,] a3;//三维string数组类引用变量a3
string[][] j2;//数组的数组,即数组的元素还是数组
string[][][][] j3;
在数组声明的时候,可以对数组元素进行赋值。看下面的例子:
int[] a1=new int[]{1,2,3};//一维数组,有3个元素。
int[] a2=new int[3]{1,2,3};//此格式也正确
int[] a3={1,2,3};//相当于int[] a3=new int[]{1,2,3};
int[,] a4=new int[,]{
{1,2,3},{4,5,6}};//二维数组,a4[1,1]=5int[,] a5={
{1,2,3},{4,5,6}};//和上条语句等价int[][] j2=new int[3][];//定义数组j2,有三个元素,每个元素都是一个数组
j2[0]=new int[]{1,2,3};//定义第一个元素,是一个数组
j2[1]=new int[]{1, 2, 3, 4, 5, 6};//每个元素的数组可以不等长
j2[2]=new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
1.4.9 字符串类(string类)
C#还定义了一个基本的类string,专门用于对字符串的操作。这个类也是在命名空间System中定义的,是类System.String的别名。字符串应用非常广泛,在string类中封装了许多方法,下面的一些语句展示了string类的一些典型用法:
l 字符串定义
string s;//定义一个字符串引用类型变量s
s="Zhang";//字符串引用类型变量s引用字符串"Zhang"
string FirstName="Ming";
string LastName="Zhang";
string Name=FirstName+" "+LastName;//+表示连接两个字符串
string SameName=Name;
char[] s2={'计','算','机','科','学'};
string s3=new String(s2);
l 字符串搜索
string s="ABC科学";
int i=s.IndexOf("科");
搜索"科"在字符串中的位置,规定第一个字符索引为0,所以"A"索引为0,"科"索引为3,因此这里i=3,如没有搜索到指定字符串i=-1。
l 字符串比较函数
string s1="abc";
string s2="abc";
int n=string.Compare(s1,s2);//n=0
n=0表示两个字符串相同,n小于零,s1<s2,n大于零,s1>s2。此方法区分大小写。也可用如下办法比较字符串:
string s1="abc";
string s="abc";
string s2="不相同";
if(s==s1)//还可用!=。虽然String是引用类型,但这里比较两个字符串的值
s2="相同";
l 判断是否为空字符串
string s="";
string s1="不空";
if(s.Length==0)
s1="空";
l 得到子字符串或字符
string s="取子字符串";
string sb=s.Substring(2,2);//从索引为2开始取2个字符,sb="字符",s内容不变
char sb1=s[0];//sb1='取'
Console.WriteLine(sb1);//显示:取
l 字符串删除函数
string s="取子字符串";
string sb=s.Remove(0,2);//从索引为0开始删除2个字符,sb="字符串",s内容不变
l 插入字符串
string s="计算机科学";
string s1=s.Insert(3,"软件");//s1="计算机软件科学",s内容不变
l 字符串替换函数
string s="计算机科学";
string s1=s.Replace("计算机","软件");//s1="软件科学",s内容不变
l 把字符串转换为字符数组
string S="计算机科学";
char[] s2=S.ToCharArray(0,S.Length);//属性Length为字符类对象的长度
l 其它数据类型转换为字符串
int i=9;
string s8=i.ToString();//s8="9"
float n=1.9f;
string s9=n.ToString();//s8="1.9"
其它数据类型都可用此方法转换为字符类对象
l 大小写转换
string s="AaBbCc";
string s1=s.ToLower();//把字符转换为小写,s内容不变
string s2=s.ToUpper();//把字符转换为大写,s内容不变
l 删除所有的空格
string s="A bc ";
s.Trim();//删除所有的空格
string类其它方法的使用请用帮助系统查看,方法是打开Visual Studio.Net的代码编辑器,键入string,将光标移到键入的字符串string上,然后按F1键。
1.4.10 类型转换
在C#语言程序中,经常会碰到类型转换问题。例如整型数和浮点数相加,C#会进行隐式转换。详细记住哪些类型数据可以转换为其它类型数据,是不可能的,也是不必要的,遇到类型转换,程序员掌握的基本原则是类型转换后不能导致信息丢失。C#语言中类型转换分为:隐式转换、显示转换、加框(boxing)和消框(unboxing)等三种。
一.隐式转换
隐式转换就是系统默认的、不需要加以声明就可以进行的转换。例如从int类型转换到long类型就是一种隐式转换。在隐式转换过程中,转换一般不会失败,转换过程中也不会导致信息丢失。例如:
int i=10;
long l=i;
二.显示转换
显式类型转换,又叫强制类型转换。与隐式转换正好相反,显式转换需要明确地指定转换类型,显示转换可能导致信息丢失。下面的例子把长整形变量显式转换为整型:
long l=5000;
int i=(int)l;//如果超过int取值范围,将产生异常
三.加框(boxing)和消框(unboxing)
加框(boxing)和消框(unboxing)是C#语言类型系统提出的核心概念,加框是值类型转换为object(对象)类型,消框是object(对象)类型转换为值类型。有了加框和消框的概念,对任何类型的变量来说最终我们都可以看作是object类型。
1 加框操作
把一个值类型变量加框也就是创建一个object对象,并将这个值类型变量的值复制给这个object对象。例如:
int i=10;
object obj=i;//隐式加框操作,obj为创建的object对象的引用。
我们也可以用显式的方法来进行加框操作,例如:
int i =10;
object obj=object(i);//显式加框操作
值类型的值加框后,值类型变量的值不变,仅将这个值类型变量的值复制给这个object对象。我们看一下下面的程序:
using System
class Test
{ public static void Main()
{ int n=200;
object o=n;
o=201;//不能改变n
Console.WriteLine("{0},{1}",n,o);
}
}
输出结果为:200,201。这就证明了值类型变量n和object类对象o都独立存在着。
2. 消框操作
和加框操作正好相反,消框操作是指将一个对象类型显式地转换成一个值类型。消框的过程分为两步:首先检查这个object对象,看它是否为给定的值类型的加框值,如是,把这个对象的值拷贝给值类型的变量。我们举个例子来看看一个对象消框的过程:
int i=10;
object obj=i;
int j=(int)obj;//消框操作
可以看出消框过程正好是加框过程的逆过程,必须注意加框操作和消框操作必须遵循类型兼容的原则。
- 3. 加框和消框的使用
定义如下函数:
void Display(Object o)//注意,o为Object类型
{ int x=(int)o;//消框
System.Console.WriteLine("{0},{1}",x,o);
}
调用此函数:int y=20;Display(y);在此利用了加框概念,虚参被实参替换:Object o=y,也就是说,函数的参数是Object类型,可以将任意类型实参传递给函数。