数组(Array)
T[] 是定长数组:长度固定、元素可读写、赋值与传参共享同一份存储。array<T> 是它的具名别名,两写法同型。
创建
三条路径:无注解的集合字面量、带注解的目标构造、按长度分配:
val xs = [1, 2, 3];
print(xs, xs.Length, xs.Count);
val buf = new int[3];
buf[2] = 7;
print(buf, buf[0]);
val names = new string?[2];
names[0] = "甲";
print(names);[1, 2, 3] 3 3
[null, null, 7] 0
[甲, null]无注解的 [1, 2, 3] 推断为数组,这是集合字面量的默认形态。new T[n] 分配 n 个零值槽:数值槽读 0、布尔槽读 False,引用元素请写可空元素 new T?[n],或用集合字面量、工厂形填充。
工厂形分配
new T[n](f) 用函数值 f 逐槽填充,绕开零值分配——每槽恰好调用一次,各槽独立不共享:零参形每槽得 f(),单 int 参的索引形槽 i 得 f(i)。f 不允许 fail / uses / async,返回类型须可赋元素类型:
val boxes = new Box[2](() => new Box());
val squares = new int[5](i => i * i);
print(boxes);
print(squares);[Main.Box, Main.Box]
[0, 1, 4, 9, 16]泛型函数里同样受约束:new T[n](T 是类型参数)编译期直接拒,也需要使用工厂形创建实例:
func Fill<T>(make: func() -> T, n: int) -> T[]
{
return new T[n](make);
}
val xs = Fill<int>(() => 0, 3); // [0, 0, 0]
val cs = Fill<Box>(() => new Box(), 2); // 两个独立实例引用共享
数组赋值、传参不拷贝,两边看到同一个对象:
val a = xs;
a[1] = 99;
print(xs[1]);99a 与 xs 是同一个数组,改 a[1] 就是改 xs[1]。要独立副本可以使用 ToList() 再转回来。
读写与成员
xs[0] = 9;
print(xs[1], xs.Contains(99));
val alias: array<int> = xs;
print(alias[1], alias.Contains(99));
alias.Set(2, 30);
print(alias, alias.ToSortedList(), alias.Reversed());99 True
99 True
[9, 99, 30] [9, 30, 99] [30, 99, 9]索引器读写之外,方法面有 Get(i) / Set(i, v) / Contains(v) / ToList() / Reversed()(反转拷贝) / ToSortedList()(排序拷贝)。Length 与 Count 都是属性。
切片恒产新数组
xs[起..止] 从数组或列表切出一段,结果是独立的新数组,与源不再共享:
print(xs[1..3]);
val tail = xs[1..3];
tail[0] = -1;
print(xs[1], tail[0]);[99, 30]
99 -1对 tail[0] 的写不影响原数组,反过来原数组后续变化也不反映进切片。区间越界或倒序一律裁剪(xs[2..1] 取空),只有单索引越界才报错。
与 List 互转
数组与列表没有隐式互转,互赋报 MS3101。显式转换是 lst.ToArray() 与 arr.ToList(),两边都是新容器:
val ls: list<int> = [4, 5];
ls.Add(6);
val back = ls.ToArray();
back[0] = 40;
print(ls[0], back[0]);
val again = back.ToList();
again.Add(1);
print(again, again.Count);4 40
[40, 5, 6, 1] 4集合字面量的目标构造仍然成立:目标类型是 list<int> 就产列表,是 int[] 就产数组。
注意
for n in xs直接遍历元素。int[]与array<int>随意混用,两种写法等价,建议只选择其中一种使用。- 数组判等是引用同一性,按内容比较用
ContentEquals,见判等与哈希。
