4.3.2 内置的引用类型

引用类型的变量又称为对象,存储的是对实际数据的引用。引用类型的变量存储在托管栈中,而实际的数据存储在托管堆中,引用类型主要成员如下,如图4-7所示。

4.3.2 内置的引用类型 - 图1

图 4-7 C#语言中内置的引用类型

1.类

类是C#中最重要的类型,它使用关键字class进行声明。关于类的更多内容将会在第9章介绍,这里只简单介绍类的声明和实例化。

我们定义一个类,用以描述现实中的矩形,如图4-8所示。矩形本身就是一个类,它具有如下几个特征:

4.3.2 内置的引用类型 - 图2

图 4-8 类

❑长度

❑宽度

❑位置(这里通过左上角的坐标来表示)

通过确定这几个特征就可以确定一个矩形的长、宽以及在某一个平面上的位置,从而得到一个具体的矩形。长度、宽度以及位置不同的矩形有无穷多,所以我们说,矩形是一个类,而这三个要素所确定的每一个具体的矩形就是类的一个实例(或者叫做对象),如图4-9所示。实例4是一种特殊的矩形——正方形。至于是否应该将正方形归类到矩形,从面向对象软件设计的角度讲是有争议的,详情可以参考设计模式中的“里氏代换原则[1]"。

4.3.2 内置的引用类型 - 图3

图 4-9 矩形类及其实例

下面,我们看看类相关的语法。首先,我们使用如下语法声明一个类:


class TestClass

{

//方法,属性,字段,事件,委托

//以及内部类等,这些都是类的主体.

}


例如,我们声明一个SampleObject的类,它有一个字符串类型的sampleValue字段,代码如下:


class SampleObject

{

public string sampleValue;

}


一般情况都需要对类进行实例化,我们可以使用new关键词对类进行实例化,例如:


SampleObject sampleObject=new SampleObject();

sampleObject.sampleValue="sampleValue's scope";


2.接口

一个有关程序设计的最佳实践,就是要求面向接口编程,这样可以降低程序各部分间的耦合度。那么什么是接口呢?接口和类有什么区别?实现了接口的类必须实现接口规定的方法、属性等,可以说接口是一种约定,甚至是一种规定。接口和类的重要区别有如下两点:

❑接口可以继承多个基接口,而类只能继承一个类,但可以实现多个接口;

❑接口只能包含签名,不能含有实现,类无此限制。

接口能包含下列成员的签名:

❑方法

❑属性

❑事件

❑索引器

如图4-10所示,接口定义了两个方法:方法1和方法2,那么实现该接口的两个类都必须提供这两个方法的实现。除此之外,每个实现类都可以有独立的其他方法实现,接口对此并无限制,例如第一个类除了实现方法1和方法2之外还有方法3,另一个实现类还有方法4。

4.3.2 内置的引用类型 - 图4

图 4-10 接口

接下来我们定义一个接口,并且实现它:


interface TestInterface

{

void TestMethod();

}

class TestClass:TestInterface

{

public void TestMethod()

{

Console.WriteLine("Hello world!");

}

}


3.委托

委托[2]类似C++中的函数指针,但它是类型安全的,也就是说它能够引用函数。每一个委托都有一个签名,可以使用delegate关键字来声明一个委托。如图4-11所示,委托引用了方法1、方法2等,调用委托就相当于调用它所引用的方法1、方法2等,并且调用的顺序是按方法出现在委托中的顺序。

4.3.2 内置的引用类型 - 图5

图 4-11 委托

下面,我们通过以下代码来看一个委托实例:


class TestDelegate

{

delegate void OneDelegate();

void method1()

{

Console.WriteLine("I'm method1!");

}

void method2()

{

Console.WriteLine("I'm method2!");

}

void Main()

{

OneDelegate oneDelegate=method1;

oneDelegate+=method2;

oneDelegate();

}

}


4.字符串

字符串[3]可以说是使用最频繁也是最常见的类型了,想必大家对它并不陌生。C#语言中的字符串类型是string,它是.NET Framework中String的别名。string类型表示零或更多Unicode字符(char)组成的序列,既然是序列,也就意味着字符串是一个字符数组(char[]),你可以使用[]运算符来访问string中的每个字符。可以使用如下几种方法声明并初始化一个字符串类型的变量:


string str1="hello world!";

var str2="hello world!";


5.数组

数组是一种数据结构,它包含若干相同类型的变量,使用如下语法进行声明:


type[]arrayName;


这里的type可以是值类型,也可以是引用类型。其中,arrayName是变量名,它是个标识符。需要注意的是,声明数组时,方括号"[]"必须在类型type后面,而不能在标识符后面,这一点和C语言不同。

包含在数组中的变量叫做元素,元素的类型就是数组的类型。下面我们看看如何去读取数组中的元素,其语法是:


type arrayValue=arrayName[index];


这里的index是数组的下标,它从0开始,因此最大值比数组长度少1。举例说明,如果数组有10个元素,那么第一个元素的下标为0,最后一个元素的下标为9,以此类推。

数组示意图如图4-12所示。

4.3.2 内置的引用类型 - 图6

图 4-12 数组示意图

仅仅声明还不行,在使用它之前先要进行初始化,语法如下:


type[]arrayName=new type[num];


在上述代码中,声明以后立刻对数组进行了初始化。也可以把声明和初始化分开来做,如:


type[]arrayName;

arrayName=new type[num];


这里的num是指定数组的大小,比如,可以这样声明一个含有10个元素的int型数组,如代码清单4-10所示。

代码清单4-10 数组示例


using System;

namespace ProgrammingCSharp4

{

class Program

{

static void Main(string[]args)

{

int[]sampleArray=new int[10];

for(int i=0;i<sampleArray.Length;i++)

{

Console.WriteLine(sampleArray[i]);

}

}

}

}


这里声明并初始化了一个含有10个元素的数组,运行它,我们会得到如下结果:


0

0

0

0

0

0

0

0

0

0


在声明并初始化数组以后,在对每个元素赋值之前,它们的初始值就是相应类型type的默认值,这里是int类型的默认值0。如果是引用类型的数组,那么相应类型的默认值请参阅表4-1。

下面是另一种类型的初始化方式,如下:


int[]sampleArray={1,10,20,30,60};


关于数组的更多内容,请参阅第18章。

注意数组的大小一旦确定就无法再进行改变,即增加和减少元素数量都是不允许的。如果声明数组的时候没有指明数组大小,则必须在初始化时进行指定,或通过int[]sampleArray={1,10,20,30,60};由编译器自动确定。

6.object类型

C#里的object是.NET框架中Object的别名。在CTS中,所有类型包括:预定义类型、用户自定义类型、引用类型和值类型,都是直接或间接从Object继承的。因此,可以将任何类型的值赋给object类型的变量。第1章提到,将值类型的变量转换为对象的过程称为“装箱”,相反,将对象类型的变量转换为值类型的过程称为“拆箱”。

7.null类型

null关键字代表一个空值,用以表示一个引用没有指向任何对象或数组。它是引用类型变量的默认值,具体可参考表4-1。另外,null不可作为一个类型去声明变量,也不能作为一个目标类型以推断某参数的类型。

[1]里氏替换原则:任何基类可以出现的地方,子类一定可以出现。这里由于正方形的长度和宽度一样,因此在某些情况下可能就无法替换矩形,比如宽度小于长度作为条件的时候。此外,如果大家还不熟悉设计模式,强烈推荐《设计模式之禅》一书(书号:978-7-111-29544-0)。

[2]关于委托的更多内容,请参阅第15章。

[3]关于字符串的更多内容,请参阅第20章。