撰写了文章 更新于 2020-03-27 22:06:25
从C#开始的编程入门——索引器
我们现在又来重新审视一下数组。
var array = new int[]{1,2,3,4,5};
var n = array[-1]; // Exception!
这样的代码无法完成编译。因为数组的索引不能为负数。
实际上,现在有一些语言是支持负数作为索引的。一般的索引是从左往右从0开始,而通常针对负数索引的实现是从序列最后一个元素往左倒过来数,也就是-1个元素实际上就是最后一个元素。
那么我们能不能在C#中再现这种操作呢?你可能会想到运算符重载,确实,因为索引这个中括号也是一个运算符。C++中就可以直接重载[]。
事实上C#中实现重写索引的行为的方式确实类似于运算符重载。不过C#并不把[]当作和其它运算符一样看待,而是提供了一种特殊的方法来重写。C#把它称作索引器(indexer)。
实现索引器
我们来包装一下数组类型,让它能够支持负值索引。
首先定义一个类,直接包装了一个array。可以用一个数组去初始化它。
class MyArray
{
private int[] array;
public MyArray(int[] array)
{
this.array = array;
}
}
现在它什么也做不到,甚至无法访问数组中的元素。我们接下来就要实现我们自己的索引器。
首先我们写一个方法,把这种“相对索引”转换为“绝对索引”,也就是从左到右的索引。主要是针对负数的索引,我们把它转化为对应的从左到右的正索引。
private int AbsoluteIndex(int index)
{
if (index >= 0)
{
return index;
}
else
{
return index += array.Length;
}
}
不过注意,这里只是单纯计算而没有判断索引是否合法。
索引器的定义实际上有点像一个属性。以返回值开头,this代表自己,后面紧接着就是代表索引器的方括号。而参数是索引器可以接受的参数类型。这暗示了索引不一定是数组,在后面我们会见到索引器不为数字的例子。
public int this[int index]
{
get
{
index = AbsoluteIndex(index);
if (index < 0 || index >= array.Length)
{
throw new IndexOutOfRangeException();
}
return array[index];
}
set
{
index = AbsoluteIndex(index);
if (index < 0 || index >= array.Length)
{
throw new IndexOutOfRangeException();
}
array[index] = value;
}
}
get对应的操作实际上是var n = a[1];这样的操作,也就是获得在某个索引处的值,也就是提供索引,拿到对应元素。而set对应的是a[1]=1;这样的操作,也就是把值赋给某个位置。这样一来也就好理解了。
无论是get还是set,我们首先把索引转换为一般数组支持的正值索引。然后判断是否在合法范围内,也就是在转换后是否还未负或者大于最大索引(即Length-1)。如果超出范围我们抛出异常,这是主动出发异常的语句,这里暂时不展开,你只要知道代码要是执行到这里就会报错就行了。如果处理后的索引合法,那么就返回对应的值或者设置对应位置上的值。
使用索引器
一旦我们定义了索引器,使用时就可以像我们使用数组那样了。
MyArray array = new MyArray(new int[]{1,2,3,4,5});
System.Console.WriteLine(array[-5]);
array[-1] = 6;
只要索引在范围内,一切正常。
再看数组类型
C#为数组提供了特殊语法,实际上数组对应的类型是System.Array。这也是一个特殊类型,不允许显式继承。
多维数组
就像多个向量可以组合成一个矩阵一样。数组也可以是多维数组。
int[,] array = new int[2,3]();
int[,] array2D = new int[4, 2] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
array2D[1,1] = 2;
多维数组不同维度的长度用逗号隔开。使用直接初始化时用大括号把每个维度下的元素括起来。访问时需要提供不同维度的索引。
交错数组
所谓交错数组实际上就是“数组的数组”。交错数组的每个元素本身也是数组。
int[][] jaggedArray = new int[3][];
它的定义方式很直观,你可以这样分开看:(int[])[],也就是说定义一个数组,它的元素类型也是数组。
和多维数组不同,多维数组每个维度的元素形式必须一样(和矩阵的定义是一样的)。而交错数组每个位置上的数组长度可以不一样。
jaggedArray[0] = new int[5]; jaggedArray[1] = new int[4]; jaggedArray[2] = new int[2];
访问时也需要使用两个索引器:
jaggedArray[0][1] = 1;
使用范围索引
从C#8.0开始,索引器得到了改进。现在索引可以不再是一个“点”,现在可以是一个范围。实际上这个特性引入后我们也就不需要再像上面那样自己实现了。
索引器现在可以接受一种Range对象用于表示范围,由两个点组成的范围运算符就是Range的语法糖。而代表索引的类型Index实际上就支持我们上面实现的从右往左的相对索引,^就代表是从末尾开始的相对索引。
var array = new int[]{1,2,3,4,5};
var range = array[0..2]; // 1,2,3
var range1 = array[^1]; // 5
var range 2 = array[..3]; // 1,2,3,4
更多有关知识可以参见这里。

weiyun 1年前
黛黛冬优子 [作者] 1年前
发布