4.3.2 内置的引用类型
引用类型的变量又称为对象,存储的是对实际数据的引用。引用类型的变量存储在托管栈中,而实际的数据存储在托管堆中,引用类型主要成员如下,如图4-7所示。
图 4-7 C#语言中内置的引用类型
1.类
类是C#中最重要的类型,它使用关键字class进行声明。关于类的更多内容将会在第9章介绍,这里只简单介绍类的声明和实例化。
我们定义一个类,用以描述现实中的矩形,如图4-8所示。矩形本身就是一个类,它具有如下几个特征:
图 4-8 类
❑长度
❑宽度
❑位置(这里通过左上角的坐标来表示)
通过确定这几个特征就可以确定一个矩形的长、宽以及在某一个平面上的位置,从而得到一个具体的矩形。长度、宽度以及位置不同的矩形有无穷多,所以我们说,矩形是一个类,而这三个要素所确定的每一个具体的矩形就是类的一个实例(或者叫做对象),如图4-9所示。实例4是一种特殊的矩形——正方形。至于是否应该将正方形归类到矩形,从面向对象软件设计的角度讲是有争议的,详情可以参考设计模式中的“里氏代换原则[1]"。
图 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-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-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-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章。