天天看点

[毕业生的商业软件开发之路]C#数据类型

近期开始接触到在校学生、高校实习生和毕业生,在此说一下笔者对这些徘徊在职场门口的学生一些建议,希望能给这些初学者进入软件开发行业带来一些帮助,使得毕业生能更顺利的进入软件开发公司开始职场生涯,人生来一个完美的转弯。[袁永福版权所有]

--------------------------------------------------------------------

任何编程语言都有数据类型的概念,这些数据类型大体可分为字符串、文本、数字、日期等等。下图就是C#中数据类型的继承关系图。

<a target="_blank" href="http://blog.51cto.com/attachment/201107/100649242.gif"></a>

在这个结构图中所有的以“System”开头的都属于基础数据类型,其他的都是自定义数据类型。

<b>System.Object</b><b>类型</b>

       该类型表示C#数据类型体系中最为基础的类型,在C#中使用关键字“object”表示该类型。

C#的数据类型体系和其他编程语言的最大的不同就是实现了各种数据类型的统一,并提供了一个所有数据类型的基础类型,那就是在命名空间System下面的Object类型。

在其他编程语言,比如VB,其整数、浮点数等基础类型都是原子类型,不是从其他类型派生的,但C#是彻彻底底的实现了面向对象编程,即使这些技术类型都是从Object类型派生过来的,而Object类型就是最基础的类,再往上就没有类型了。

Object类型提供一些成员方法,最常见的有

Equals

带一个参数,用于对两个对象数据进行比较,若相等则返回True,否则返回False。

Finalize

在自动回收对象之前执行清理操作,该方法一般由.NET框架自动调用。

GetHashCode

生成一个与对象的值相对应的数字以支持哈希表的使用。

ToString

生成描述对象数据的字符串。一般供人阅读。

在Object类型上C#使用以下基础数据类型,而且有很多基础数据类型在C#有对应的关键字表示。

类型

对应的

C#关键字

说明

System.Boolean

bool

布尔类型,其值只能为true或false,该数据类型占用1个字节内存。

System.Byte

byte

表示一个从0到255的整数,该数据类型占用1个字节内存。

System.SByte

sbyte

表示一个从-127到127的整数,占用1个字节,

System.Char

char

表示一个字符数据,占用2个字节。和C语言类似,Char类型可以强制转换为整数。这个字符数据是采用Unicode编码格式。

System.Int16

short

表示一个从 -32768 到 +32767 的整数,占用2个字节。

System.UInt16

ushort

表示一个从0 和 65535的整数,占用2个字节。

System.Int32

int

表示一个从-2,147,483,648(约负21亿) 到 +2,147,483,647(约21亿)的整数,占用4个字节。

System.UInt32

uint

表示一个从0 到 4,294,967,295 之间的整数,占用4个字节。

System.Int64

long

表示一个从-9,223,372,036,854,775,808 到 +9,223,372,036,854,775,807的整数,占有8个字节。

System.UInt64

ulong

表示一个从0 到 18,446,744,073,709,551,615的整数,占用8个字节。

System.Single

float

表示一个从-3.402823e38 和 +3.402823e38 之间的单精度浮点数字,有7位有效数字。占用4个字节。

System.Double

double

表示一个-1.79769313486232e308 和 +1.79769313486232e308 之间的浮点数,有15位有效数字,占用8个字节。

System.Decimal

decimal

表示一个从+79,228,162,514,264,337,593,543,950,335 到 -79,228,162,514,264,337,593,543,950,335之间的数字。而且计算时尽量不进行舍入操作,这样能维护运算精度,比较适合财务运算。

System.DateTime

表示一个从公元(基督纪元)0001 年 1 月 1 日午夜 12:00:00 到公元 (C.E.) 9999 年 12 月 31 日晚上 11:59:59 之间的时间日期数据,精确到100纳秒。

在初始化日期数据的时候,可以传递年数、月份数和日数,也可以继续添加24小时制的小时数、分钟数和秒数。

以下代码就定义了一个DateTime类型。

DateTime dtm = new DateTime( 1980 , 2 , 14 );

也可以为

DateTime dtm2 = new DateTime( 1980 , 2 , 14 , 16, 23 , 39 );

System.String

string

表示一段文本,采用UTF-16编码,可以包含字符“\0”。

System.Enum

enum

所有枚举类型的基础类型。

System.Deleate

delegate

所有委托类型的基础类型。

Syatem.Array

所有数组类型的基础类型。

C#支持数组,它包含着若干干相同类型的变量。在C#代码中,使用“type[] arrayName”的方式来定义数组,比如以下代码定义了几个数组

int[ ] ids = null;   // 定义了一个整数数组

string[ ] names = new string[ 100 ];  // 定义了一个字符串数组,并初始化为包含100个数据。

在初始化数组时,数组里的元素值也初始化了,对于bool类型的数组,其元素值初始化为false,数值型的初始化为0,字符串的初始化为null。

在C#中数组的下标是从0开始的,可以使用数组对象的Length属性获得数组的长度,比如以下代码就是遍历数组所有的元素。

string[ ] names = new string[100];

for (int iCount = 0; iCount &lt; names.Length; iCount++)

{

    string name = names[iCount];

}

也可以使用foreach语句来遍历数组中所有的元素。如下所示

foreach( string name in names )

     // 此处可以使用变量“name”的值

注意在for循环中可以通过修改“names[ iCount ]”的值来修改数组中存储的数据;但在foreach循环中,name不能修改,是只读的。

在初始化结构体类型的数组时,数组是有效的,而且数组中的元素也是有效的;而在初始化类类型的数组时,虽然数组是有效的,但其数组的元素全部为空引用。

例如以下代码定义了结构体类型MyPeopleStruct。

public struct MyPeopleStruct

    public string Code;

    public string Name;

针对该类型执行以下代码是不会出事的

MyPeopleStruct[] peoples = new MyPeopleStruct[100];

peoples[33].Name = "张三";

因为结构体数组初始化后,系统会自动创建数组元素对象。

若以下代码定义了类类型MyPeopleClass。

public class MyPeopleClass

    public string Code = null;

    public string Name = null;

若有以下代码

MyPeopleClass[] peoples = new MyPeopleClass[100];

peoples[3].Name = "张三";

这段代码可以编译通过,但运行时会在代码“peoples[3].Name = "张三"”处报空引用的程序错误。这是因为初始化了类类型数组,只是为数组分配了内存空间,但没有相应的创建对象类型,该数组的元素全部设置为空引用,对于空引用调用其成员就会报空引用错误。

空引用错误是C#程序中最常见的错误。

在程序开发中个,仅仅使用这些基础数据类型是不够的,还需要开发人员根据需要自定义数据类型。在C#中,自定义的数据类型有类、结构体、枚举和委托。

在C#中使用关键字“class”定义的类型为类类型,以下代码就定义了一个类类型。

public class PeopleClass

    public PeopleClass()

    {

    }

    private string _Name = null;

    public string Name

        get

        {

            return _Name;

        }

        set

            _Name = value;

    public override string ToString()

        return  _Name;

在代码“public class PeopleClass”中,关键字“public”说明该类型是公开的;“class”说明这是一个类类型;“PeopleClass”指定了类型的名称。

类类型是引用类型,比如执行了以下代码

PeopleClass p1 = new PeopleClass();

p1.Name = "张三";

People p2 = p1 ;

p2.Name = "李四";

则代码执行后,变量p1和p2指向的是同一个对象,也可以理解为指向同一个内存地址,很显然,通过变量p2修改值和通过p1修改其效果是一样的,这样p1.Name和p2.Name的值都等于“李四”。

C#支持结构体类型,以下代码就定义了一个结构体类型。

public struct PeopleStruct

    public bool Sex;

这段代码定义了一个名为People的结构体,它有“Code”、“Name”、“Sex”三个公开字段。我们可以使用一下的代码来使用这个结构体类型。

PeopleStruct myPeople = new PeopleStruct( );

myPeople.Code = “1000”;

myPeople.Name = “张三”;

结构体类型中可以定义字段、属性、方法和事件,但和类类型有着以下区别

●结构体中字段不能初始化,比如在结构体中需要写成“public string Code;”,而在类类型中可以写成“public string Code = null;”。

●结构体中不能定义默认的构造函数(无参数的构造函数)。

●结构体可以定义带参数的构造函数,但在函数体中在执行任何代码前必须对所有的字段进行赋值初始化。

●结构体对象在赋值时会重新创建一个对象并复制所有的字段值。对新结构体的数据的修改不会影响原始对象的内容。比如对于结构体类型People执行了以下代码

People p1 = new People();

p1.Name = "李四";

则p2.Name的值还是为“张三”。

●结构体不能继承自其他结构体类型,也不能派生新的结构类型。所有的结构体类型直接继承自类型System.ValueType。

●结构体可以实现接口。

C#支持枚举类型,以下代码就定义了一个枚举类型。

public enum BarcodeStyle

    Code128A,

    Code128B,

    Code128C

在C#中,枚举变量是可以转换为整数的,第一个枚举成员默认等于0,然后依次增加,不过也可以明确的设置枚举成员数值。例如上述代码等价于

    Code128A = 0 ,

    Code128B = 1,

    Code128C = 2

这个设置数值是比较自由的,可以任意指定,比如可以使用以下代码定义枚举类型。

public enum FlagStyle

    Flag1 = 1 ,

    Flag2 = 4,

    Flag3 = 64

所有的枚举类型都是从基础类型System.Enum派生的。System.Enum类型提供了一些能用于所有枚举类型的方法,常用的有

GetName

获得等于指定数据的枚举项目的名称。该函数是静态的,具有两个参数,第一个是枚举类型,第二个是某个常数。

例如对于上面的BarcodeStyle枚举类型,执行代码“Enum.GetName( typeof( BarcodeStyle ) , 0 ) ”,就返回字符串“Code128A”;执行“Enum.GetName( typeof( BarcodeStyle” , 1 )”就返回字符串“Code128B”。

GetNames

获得枚举类型的所有枚举项目的名称组成的字符串数组。该函数时静态的,参数就是枚举类型变量。

例如对于BarcodeStyle枚举类型,执行代码“Enum.GetNames(typeof( BarcodeStyle ))”就返回一个字符串数组,数组元素是“Code128A”、“Code128B”、“Code128C”。

GetValues

获得枚举类型的所有枚举项目组成的数组。该函数是静态的,参数就是枚举类型变量。

例如对于BarcodeStyle类型,执行代码“Enum.GetValues( typeof( BarocdeStyle ))”就返回一个数组,数组元素就是“BarcodeStyle.Code128A”、“BarcodeStyle.Code128B”、“BarcodeStyle.Code128C”。

Parse

解析字符串并转化为枚举类型,若转化失败则会抛出异常。该函数时静态的,参数是指定的枚举类型和要解析的字符串,此外还有第三个布尔类型的可选参数,用于指明是否区分大小写。

例如对于BarcodeStyle类型,执行代码“Enum.Parse( typeof( BarcodeStyle ) , “Code128A” )”就返回“BarcodeStyle.Code128A”。

执行代码“Enum.Parse( typeof( BarcodeStyle ) , “code128a”, true)”也返回“BarcodeStyle.Code128A”。

注意,当解析失败时,该函数会抛出异常。

TryParse

解析字符串并试图将其转化为枚举类型,如转化失败则不抛出异常,该函数返回转化是否成功的布尔值。该函数是静态的,第一个参数就是要转化的字符串,第二个可选参数就是转化时是否区分大小写,第三个参数就是保存转化结果的枚举变量。

例如对于“BarcodeStyle”,调用代码“Enum.TryParse( “Code128A” , out value )”,则函数返回true而且value值被设置成“BarcodeStyle.Code128A”;调用代码“Enum.TryParse(“abc” , out value )”,则函数返回false,表明转化失败。

返回表示枚举值的字符串,一般为其所表示的枚举项目的名称。

在C#中使用关键字“interface”定义的数据类型为接口,以下代码就定义了一个接口类型。

public interface IMyInterface

    string Value

        get;

        set;

    int Sum( int a , int b );

在这段代码中,“public”说明该类型是公开的;“interface”说明该类型是接口类型;“IMyInterface”指定了类型名称,一般的约定成俗,接口类型的名称是以大写字母“I”开头的;“string Value { get ; set ;}”定义了一个属性;“int Sum( int a , int b )”定义了一个方法。

接口用于定义一组属性、方法和事件,但不能包含字段,接口的成员无需修饰,全部是公开的。

接口不能实例化,仅仅是一个空洞的模板让人敬仰,但毫无作用;它必须深入群众,灵魂附在其他类或者结构体中才能发挥作用。这个灵魂附体的过程就是实现接口。

实现接口的方式不复杂,就是在定义类型的时候声明是从接口类型派生的,并将接口中定义的所有的属性、方法一个不拉的实现,从这个的角度上看接口类型有点像类类型。

例如对于上面的IMyInterface类型,使用以下代码就定义了一个实现该接口的类。

public class MyClass2 : IMyInterface

    private string _Value = null ;

    public string Value

            return _Value ;

            _Value = value ;

    public int Sum(int a, int b)

        return a + b ;

当接口成员比较多难于记忆时,VS.NET的C#代码编辑器会提供一些帮助功能来编写实现接口的代码,如下图所示,文本光标移动到方框所在的“IMyInterface”,此时其左下角出现一个智能标签,鼠标点击这个智能标签会显示一个菜单。

<a target="_blank" href="http://blog.51cto.com/attachment/201107/100711838.jpg"></a>

点击“实现接口”IMyInterface””菜单项目则自动的往“MyClass2”的代码块中插入能实现IMyInterface的代码,此时自动生成的代码如下

    #region IMyInterface 成员

        {

            throw new NotImplementedException();

        throw new NotImplementedException();

    #endregion

而点击“显式实现接口”IMyInterface””菜单项目则自动生成以下代码

    string IMyInterface.Value

            throw new NotImplementedException();

    int IMyInterface.Sum(int a, int b)

实现接口和显式实现接口是有区别的。对于实现接口,可以通过以下代码的方式访问它的成员。

MyClass2 instance = new MyClass2();

instance.Sum(3, 4);

也可以通过以下方式访问成员。

IMyInterface instance = new MyClass2();

或者

instance2.Sum(3, 4);

而对于显式实现接口只能使用第二种方式访问。

在实际开发中,这两种实现接口的方式还可以混用,不过显式实现接口用得比较少。

在C#中,类只能继承另外一个类,但可以实现多个接口。以此不同的是C++可以继承自多个类和多个接口。

<b>委托</b>

委托就是一个指向成员方法的对象,C语言中有一个函数指针的概念,委托就可以看做面向对象的函数指针。其定义语法为“delgate 返回值 委托名( 参数类别 )”,例如以下代码就定义了一个委托类型

public delegate int Int2Handler( int a , int b );

在这段代码中,“public”指明的该类型是公开的,“delegate”说明正在定义一个委托类型,“int”指明委托的返回类型,“Int2Handler”是委托类型的名称,“int a , int b”就是该委托使用的参数列表。

委托代表一个类型成员方法,而且该方法的返回值和参数列表必须和委托声明的一模一样。例如在某个类中定义了以下三个成员方法。

private int Sum(int a, int b)

    return a + b;

private int Mul(int a, int b)

    return a * b;

private double Sum2(double a, double b)

则方法Sum和Mul的签名和委托Int2Handler的签名完全吻合,因此Int2Handler委托可以指向这两个方法;而方法Sum2的签名不对,因此Int2Handler委托不能指向该方法。

以下代码演示了使用委托

Int2Handler handler = null;

handler = new Int2Handler(Sum);

int result = handler(3, 4); // 返回7

handler = new Int2Handler(Mul);

result = handler(3, 4);  // 返回12

在这段代码中,创建委托对象实例传入的参数就是方法名称,因此同样执行代码“handler( 3 , 4 )”,由于第一次委托指向方法Sum,计算结果为7;而第二次委托执行方法Mul,计算结果为12。这样委托就能实现非常好的灵活性。

C#中可以实现匿名委托,以下代码就演示了匿名委托。

Int2Handler handler = delegate(int a, int b)

};

int result = handler(3, 4);

handler = delegate(int a, int b)

result = handler(3, 4);

可以看出可以使用“委托类型 委托变量名 = delegate(参数列表) { 方法功能代码 };”的语句创建一个匿名委托,而且无需编写独立的方法来放置功能代码。因此使用匿名委托能写出比较精巧的代码,但注意匿名委托也有缺点,那就是不利于调试修改。

VS.NET提供断点调试时修改程序代码的功能,VB也有类似的功能。但当修改了包含匿名委托的方法中的代码时,VS.NET的这个功能就无效,需要重新编译重新运行程序时才能应用代码的改变。

<b>泛型</b>

泛型是一种比较新的软件技术,对于开发者来说就是一套类型代码模板,在开发的时候使用某种尚未确定的数据类型,就用一种虚拟的类型来替代;在运行的时候可以将其操作的类型替换成指定的具体类型。

在开发中最常用的泛型类型为“System.Collections.Generic.List&lt;&gt;”和“System.Collections.Generic.Dictionary&lt;&gt;”。泛型类型在实例化时必须指明其采用的数据类型,而且实例化后的数据类型是特定的不能修改。

例如对于类型“System.Collections.ArrayList”和“System.Collections.Generic.List&lt;&gt;”功能类似,在此进行对比。

例如以下代码使用了ArrayList类型。

// 没有使用泛型,可以添加任意类型的元素

System.Collections.ArrayList list1

    = new System.Collections.ArrayList();

list1.Add(new MyPeopleClass());

list1.Add(new MyPeopleStruct());

// 列表元素必须要强制类型转换

((MyPeopleClass)list1[0]).Name = "张三" ;

在这段代码中,开发者可以在ArrayList列表中添加任何类型的对象,而且调用列表中元素的属性时需要进行强制类型转换,这个过程比较灵活,但很不安全,很容易导致类型转换错误的运行时错误。

以下代码使用了泛型列表“List&lt;&gt;”

// 使用泛型

System.Collections.Generic.List&lt;MyPeopleClass&gt; list2

    = new System.Collections.Generic.List&lt;MyPeopleClass&gt;( );

list2.Add(new MyPeopleClass());

//list2.Add(new MyPeopleStruct()); 这样写有编译错误

接着代码调用列表对象的Add方法向列表中添加成员,由于在初始化的时候已经限制列表元素为MyPeopleClass类型,因此Add方法只能接受MyPeopleClass类型的参数,若使用其他数据类型的参数则会报编译错误。

由于已经限制了列表元素成员数据类型,因此无需进行强制数据类型转换就能调用列表元素的方法,代码“list2[0].Name = "张三"”就展示了这种技术。

使用泛型能将很多运行时错误转换为编译时错误,这可以将很多程序错误消灭在萌芽中,提高程序的开发速度,同时还能提高点程序运行性能,因此在开发中应尽量采用泛型。

     本文转自xdesigner 51CTO博客,原文链接:http://blog.51cto.com/xdesigner/622515,如需转载请自行联系原作者