C#三维数组定义问题

4

我下面的代码编译出错了,

错误 1 无法将类型“TestArray1.Foo[,,*]”隐式转换为“TestArray1.Foo[][][]” C:\Users\lma\Documents\Visual Studio 2008\Projects\TestArray1\TestArray1\Program.cs 17 30 TestArray1

有没有人有什么想法?这是我的整个代码,我正在使用VSTS 2008 + Vista 64位。

namespace TestArray1
{
    class Foo
    {    
    }

    class Program
    {
        static void Main(string[] args)
        {
            Foo[][][] foos = new Foo[1, 1, 1];

            return;
        }
    }
}

编辑:第二版。我有另一份代码版本,但仍然存在编译错误。有什么想法吗?

Error   1   Invalid rank specifier: expected ',' or ']' C:\Users\lma\Documents\Visual Studio 2008\Projects\TestArray1\TestArray1\Program.cs 17  41  TestArray1
Error   2   Invalid rank specifier: expected ',' or ']' C:\Users\lma\Documents\Visual Studio 2008\Projects\TestArray1\TestArray1\Program.cs 17  44  TestArray1


namespace TestArray1
{
    class Foo
    {    
    }

    class Program
    {
        static void Main(string[] args)
        {
            Foo[][][] foos = new Foo[1][1][1];

            return;
        }
    }
}

编辑:第三版。我想要一个锯齿数组。在向同行们学习之后,这是我的代码修复,它可以在VSTS 2008中编译正常。我想要的是一个锯齿数组,目前我只需要一个元素。请问是否有人能够审查一下我的代码是否正确实现了我的目标?

namespace TestArray1
{
    class Foo
    {    
    }

    class Program
    {
        static void Main(string[] args)
        {
            Foo[][][] foos = new Foo[1][][];
            foos[0] = new Foo[1][];
            foos[0][0] = new Foo[1];
            foos[0][0][0] = new Foo();
            return;
        }
    }
}

thanks in advance, George


版本3看起来还不错,但人们不禁会想到size=1的事情。你是否意识到foos只是int foos的复杂版本? - H H
@Henk,实际上数组定义是从xsd工具自动生成的C#文件中生成的。 - George2
好的,但在返回之前,您可能希望添加1行代码:foos [0] [0] [0] = new Foo(); - H H
@Henk,好的谢谢!我为了完整性添加了这行代码到讨论串中。 :-) - George2
是的,那做了你想要的。不过,返回语句是多余的。 - Jon Skeet
谢谢Jon,我从C语言迁移过来,有些习惯难以忘怀。 :-) - George2
8个回答

7

关于您代码的第二个版本 - 不幸的是,您不能以那种方式指定锯齿数组。相反,您需要执行以下操作:

Foo[][][] foos = new Foo[1][][];
foos[0] = new Foo[1][];
foos[0][0] = new Foo[1];

您需要分别填充每个数组,Foo[][][] 表示“一个 Foo 数组的数组的数组”。这种初始化语句只能初始化一个数组。对于矩形数组,您最终仍然只有一个(多维)数组,这就是为什么new Foo[1,1,1] 是有效的原因。
顺便说一句,如果这是真正的代码,我建议您至少考虑其他设计决策。数组可以很有用,但您可能会遇到此类问题。数组的数组甚至更加麻烦。可能有更可读的方法来表达您感兴趣的内容。

@Jon,我已经按照您的评论添加了代码版本3。您能否帮忙审核一下我的代码是否正确实现了我的目标?谢谢! - George2
@Jon,你的建议非常有帮助。实际上,数组定义是从 xsd 工具自动生成的 C# 文件中生成的。 :-( - George2
@George2:已经对这个问题进行了评论。很遗憾它是自动生成的代码。你可能需要在周围加上一个更易读的外观。 - Jon Skeet
@Jon,“这意味着多维数组的边界检查(和索引一般)比较慢”--换句话说,这是因为每次访问多维数组中的一个元素时,我们需要检查包括非零基础边界情况在内的每个边界,但要访问(继续) - George2
@Jon,“选择最干净的设计”--同意。 - George2
显示剩余6条评论

3

请下定决心 :)

你要么想要:

Foo[,,] foos = new Foo[1, 1, 1];

或者:

Foo[][][] foos = new Foo[1][1][1];

嗨 Jon,定义 foos 为 [,,] 和 [][][] 有什么区别吗?我认为它们是一样的! :-) - George2
第二个是不规则的,这意味着对于每一行,下一个维度可以有不同大小的数组。 - AaronLS
第一个是三维数组,即一个连续的内存块包含所有元素。另一种语法是数组的数组的数组,它具有非常糟糕的内存局部性,此外,单个数组的大小也可能不同。这完全取决于您需要什么。 - Joey
@Jon / @aaronls / @Johannes,我已经按照你们的评论进行了修改,但是仍然有编译错误。请查看我的原始帖子中版本2的代码和编译错误。有什么想法或评论吗? - George2
new Foo[1][1][1] 是无效的。你必须使用 new Foo[1][][],然后填充每个子数组。 - Keith
@Keith,我已经按照您的评论添加了代码版本3。您能否帮忙审核一下我的代码是否正确实现了我的目标?谢谢! - George2

2
您正在声明一个双重嵌套的数组(数组的数组),并且分配了一个三维数组。这两者是不同的。您可以更改您的声明为:
Foo[,,] foos = new Foo[1,1,1]

如果您想要一个真正的三维数组。在C#中,锯齿状数组([][][]类型)并不像在Java中那样必需。


@Johannes,让我们简单些吧,假设将维度从3更改为2,那么嵌套数组和二维数组之间的区别是什么? :-) - George2
Jon Skeet在这方面有一个不错的答案。基本上,n维数组就是一个具有n个维度的单一数组。而[][]变体则是一个数组的数组,即数组的每个元素本身都是一个数组,在支持第一种情况的语言中很少需要使用。 - Joey
@Johannes,谢谢!我了解到多维数组似乎比交错数组慢,因为需要进行GetLength边界检查?我很困惑,因为我认为交错数组也需要这样的边界检查,为什么交错数组更快呢? - George2
嗯,有趣。没想到会这样。无论如何:如果您认为性能是个问题,那就对其进行分析。试一下并找出瓶颈所在。如果边界检查成为了问题,您总可以将其包装在一个未经检查的块中。 - Joey
不,目前并没有。如果您自己进行了基准测试,并得出了相同的结论,您可以向其中一位C#编译器专家咨询。据我所知,Eric Lippert似乎非常负责任。 - Joey
显示剩余3条评论

2
最简单的答案是使用 标签。
Foo[,,] foos = new Foo[2, 3, 4];

这将为您提供一个连续的内存块,作为2*3*4=24个Foo的三维数组。

另一种选择如下:

Foo[][][] foos = new Foo[2][][];

for (int a = 0; a < foos.Length; a++)
{
  foos[a] = new Foo[3][];
  for (int b = 0; b < foos[a].Length; b++)
  {
     foos[a][b] = new Foo [4];

     for (int c = 0; c < foos[a][b].Length; c++)
        foos[a][b][c] = new Foo();
  }
}

虽然使用这种锯齿状(数组的数组)的方法需要多做一些工作,但它实际上更快。这是由于编译器的一个缺点,对于Foo [,,]情况下访问元素时它总是会进行范围检查,而在Foo [][][]场景下至少能够优化使用Length属性的for循环。
另请参见此问题

@George2:我简单解释了一下,并添加了一个链接。 - H H
@Henk,我已经按照您的意见添加了代码版本3。您能否帮忙审核一下我的代码是否正确实现了我的目标?谢谢! - George2
@Henk,我读了你推荐的问题2个小时。似乎多维数组由于GetLength边界检查而变慢?我很困惑,因为我认为锯齿数组也需要这样的边界检查,为什么锯齿数组更快呢? - George2
@George2,当JIT编译器识别到一个带有i < a.Length条件的for循环时,它会执行一种特殊技巧。它也应该能够对i < GetLength(1)执行相同的操作,但实际上没有。否则,Foo[][][]将会更快。 - H H
@Henk,你认为哪些即时编译指令是不好的,导致多维数组比锯齿数组慢?能否引用一下以使其更清楚? - George2

1
如果有意义的话,仅针对C#声明:
int[,,] ary = new int[,,]
{
      { { 111, 112 }, { 121, 122 }, { 131, 132 } } 
    , { { 211, 212 }, { 221, 222 }, { 231, 232 } } 
    , { { 311, 312 }, { 321, 322 }, { 331, 332 } } 
    , { { 411, 412 }, { 421, 422 }, { 431, 432 } } 
};

1

根据您是否希望它们成为锯齿状:

//makes a 5 by 4 by 3 array:

string[,,] foos = new string[5,4,3];

http://msdn.microsoft.com/en-us/library/aa288453(VS.71).aspx

哦,这里是初始化值:

    char[, ,] blah = new char[2, 2, 2] { 
        {{ '1', '2' }, { '3', '4' }},
        {{ '5', '6' }, { '7', '8' }}
         };

请注意,这样做是行不通的:
Foo[][][] foos = new Foo[1][1][1];

因为您正在使用不允许定义嵌套数组大小的交错数组语法。相反,请执行以下操作:
Foo[,,] foos = new Foo[1][1][1];  //1 by 1 by 1

或者这个

Foo[][][] foos = new Foo[1][][];  //1 array allowing two nested levels of jagged arrays
foos[0] = new Foo[1];  //for the first element, create a new array nested in it
foos[0][0] = new Foo[1]; //create a third level new array nested in it

嗨aaronls,我想要一个三维数组。我已经尝试了,但是仍然有编译错误。请检查我的原始帖子中的代码版本2,查看我的新代码和编译错误。有任何想法或评论吗? - George2
@aaronls,我已经按照您的评论添加了代码版本3。您能否帮忙审核一下我的代码是否正确实现了我的目标?谢谢! - George2
版本3看起来正确,但请注意它只能保存单个值。 - AaronLS

1

这两种不同的数组声明创建了非常不同的东西。

让我们看一个更简单的例子:

Foo[,] twoDimensionArray = new Foo[5,5];

这个数组有两个维度 - 你可以把它想象成一张表格。你需要两个轴才能返回任何东西:

Foo item = twoDimensionArray[2,3]

索引始终具有相同的长度 - 在这种情况下为0-4。

锯齿数组实际上是一个数组的数组:

Foo[][] jaggedArray = new Foo[5][];

jaggedArray[0] = new Foo[2];
jaggedArray[1] = new Foo[4];
...

如果您只使用一个轴索引,它将返回一个数组:

Foo[] oneRow = jaggedArray[3];

如果您同时使用两者,则可以从子数组中进行选择:

Foo item = jaggedArray[3][2];

//would be the same as:
Foo item = oneRow[2];

这些子数组中的每一个都可以有不同的长度,甚至可能没有填充。


@Keith,你说的“even not be populated”是什么意思?能用其他的话来表达吗? - George2
我已经按照您的评论添加了我的代码的第三个版本。您能否帮忙检查一下我的代码是否正确实现了我的目标?谢谢! - George2
通过“未填充”我指的是子数组可以为null - 当你初始化它时它们为空。 - Keith

0

数组初始化

int[, ,] array3D = new int[,,] { { { 1, 2, 3 }, { 4, 5, 6 } }, 
                                { { 7, 8, 9 }, { 10, 11, 12 } } }

来源:C#编程指南:多维数组


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接