循环参数的类型

The type of a Loop Parameter

恐怕是初学者的问题。我需要记录数组中特定元素的位置(索引)。考虑以下因素:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

    Ten : constant Positive := 10;

    type ArrayIndex is new Positive range 1 .. Ten;

    type MyRecord is record
        firstItem  : Integer;
        secondItem : Integer;
    end record;

    TheRecords : array (1 .. Ten) of MyRecord;

    indexOfElement50  :  ArrayIndex := 1;

begin

    -- set the values in TheRecords

    for i in TheRecords'Range loop
        TheRecords(i).firstItem  := i * 10;
        TheRecords(i).secondItem := i * 20;
    end loop;

    -- find which element of TheRecords has a
    -- firstItem with a value of 50

    for i in TheRecords'Range loop
        if TheRecords(i).firstItem = 50 then
            -- this next line is horrible: I should
            -- not be required to do type casting
            -- in a strongly-typed language.

            indexOfElement50 := ArrayIndex(i);
            exit;
        end if;
    end loop;

    Put(ArrayIndex'image(indexOfElement50));

end Main;

一直到评论“查找 TheRecords 的哪个元素具有值为 50 的 firstItem”的所有内容都只是设置我遇到的问题(当然是在一个更大的程序中)。

虽然我来自 C 和 Python 世界,但我一直试图对 Ada 中的强类型保持虔诚。因此,我仔细定义了“indexOfElement50”,我希望它是 TheRecords 中元素的索引,该元素的 firstItem 为 50。注释下方开始的循环是搜索该元素的代码。并找到了!

但我必须将 i 转换为 ArrayIndex。在强类型的世界中,铸造是错误的。我试过使用 indexOfElement50 作为循环参数,但编译器没有任何参数。

所以,我似乎被迫要么将 indexOfElement50 声明为整数(这违反了尽可能限制范围的准则),要么执行类型转换(这在 C 中很棒,但我应该这样做不会使用强类型语言)。

或者,更有可能的是,我遗漏了一些非常明显的东西,专家们会热情地指出这一点。

我真的不知道这是否是你想要实现的。但也许它有帮助。

您可以使用 ArrayIndex 作为数组的索引:

TheRecords : array (ArrayIndex) of MyRecord;

但是你必须将 i 转换为整数:

TheRecords(i).firstItem  := Integer(i) * 10;
TheRecords(i).secondItem := Integer(i) * 20;

完整示例:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

    Ten : constant Positive := 10;

    type ArrayIndex is new Positive range 1 .. Ten;

    type MyRecord is record
        firstItem  : Integer;
        secondItem : Integer;
    end record;

    TheRecords : array (ArrayIndex) of MyRecord;

    indexOfElement50  :  ArrayIndex := 1;

begin

    -- set the values in TheRecords

    for i in TheRecords'Range loop
        TheRecords(i).firstItem  := Integer(i) * 10;
        TheRecords(i).secondItem := Integer(i) * 20;
    end loop;

    -- find which element of TheRecords has a
    -- firstItem with a value of 50

    for i in TheRecords'Range loop
        if TheRecords(i).firstItem = 50 then
            indexOfElement50 := i;
            exit;
        end if;
    end loop;

    Put(ArrayIndex'image(indexOfElement50));

end Main;

我倾向于稍微改变一下方法。

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

您真的不需要一个名为 Ten 且值为 10 的常量!如果你想晚 12 点怎么办?

   type MyRecord is record
      firstItem  : Integer;
      secondItem : Integer;
   end record;

我们想要一个记录数组,但让我们推迟决定需要多长时间...

   type Record_Array is array (Positive range <>) of MyRecord;

... 并定义一个测试数组,其大小恰好为 10 但可以是任何值。

   TheRecords : Record_Array (1 .. 10);

有效结果(对于此测试程序)只能在 TheRecords’Range 中,但让我们添加一个超出范围的值以指示“未找到”。

   subtype Possible_Index is Natural range 0 .. TheRecords'Last;
   indexOfElement50  : Possible_Index := 0;  -- indicates 'not found'

好的!

begin

   -- set the values in TheRecords

   for i in TheRecords'Range loop
      TheRecords(i).firstItem  := i * 10;
      TheRecords(i).secondItem := i * 20;
   end loop;

   -- find which element of TheRecords has a
   -- firstItem with a value of 50

   for i in TheRecords'Range loop
      if TheRecords(i).firstItem = 50 then
         indexOfElement50 := i;
         exit;
      end if;
   end loop;

   Put_Line (indexOfElement50'Image); -- legal in Ada2012

end Main;

您遇到的主要问题是您声明了一个新类型,然后却没有始终如一地使用它。

这并不奇怪,因为类型不再被很好地教授,但是他们通过实践来很好地利用它们来捕捉真正的错误,而不让它们妨碍。

你做得很好:如果你发现自己写了太多的类型转换(而不是强制转换),这表明设计是错误的(代码味道),你发现了。

现在我要把你的类型声明分成两部分,以说明我是如何处理这个问题的。

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

    type FunnyInteger is new Positive;
    subtype ArrayIndex is FunnyInteger range 1 .. 10;

Ada 提供两种类型和子类型。

类型不兼容(没有显式转换)。他们的工作是阻止您犯类别错误,例如添加脚和马。

子类型相互兼容,但可以表达限制,例如限制值的范围。

所以我介绍了一种新的类型,不要意外地与 Integer 混淆。

并且我已经命名了它的一个子类型,来定义数组的大小。这很重要:因为数组是用这个子类型索引的,所以任何与该子类型不兼容的东西都不能用来索引它……再见,Heartbleed。一个新类型会做同样的事情 : 但也需要在某处进行类型转换,正如您所注意到的。

现在,

type MyRecord is record
    firstItem  : Integer;
    secondItem : FunnyInteger;
end record;

一个记录字段与索引类型兼容;另一个不是,也不能不小心与它混淆。这个选择来自问题领域。如果将 Integers 与 ArrayIndex 混合使用没有坏处,请参见下面的第二个示例。

此外,在声明 ArrayIndex(子)类型后,始终如一地使用它...

TheRecords : array (ArrayIndex) of MyRecord;
indexOfElement50  :  ArrayIndex := 1;

begin

    for i in ArrayIndex loop
        TheRecords(i).firstItem  := Integer(i) * 10;
        TheRecords(i).secondItem := i * 20;
    end loop;

    for i in ArrayIndex loop
        if TheRecords(i).firstItem = 50 then
            indexOfElement50 := i;
            exit;
        end if;
    end loop;

    Put(ArrayIndex'image(indexOfElement50));

end Main;

请注意 firstItem,与我们的 ArrayIndex 不兼容,需要进行类型转换。这证明我们正在打破类型规则;审阅者会注意这一点,它会提醒下一个处理代码的人注意。

我认为这就像我的猫再次从沙发上掉下来时给我的“我打算那样做”的样子。

secondItem 是兼容的,不需要这样的转换(因为问题域允许我们这样做)。

如果没有理由将 ArrayIndexInteger 分开,只需将 ArrayIndex 设为 Integer 的子类型即可。它仍然是范围保护的,但不再是类型保护的。因此,您掌握了权力:选择您需要的保护级别。

还注意到范围是没有名称的子类型,我们可以简化为

with Ada.Text_IO; use Ada.Text_IO;

procedure Main2 is

    type MyRecord is record
        firstItem  : Integer;
        secondItem : Integer;
    end record;

    TheRecords : array (1 .. 10) of MyRecord;
   
    indexOfElement50  : Integer range TheRecords'range;

begin
    for i in TheRecords'range loop
        TheRecords(i).firstItem  := i * 10;
        TheRecords(i).secondItem := i * 20;
    end loop;

    for i in TheRecords'range loop
        if TheRecords(i).firstItem = 50 then
            indexOfElement50 := i;
            exit;
        end if;
    end loop;

    Put(Integer'image(indexOfElement50));
end Main2;

所有与索引相关的内容现在都直接从数组声明派生,并且受范围保护,(但这里假设从根本上与我们的 Integer 类型兼容)。

另请注意,我完整保留了 Simon 指出的错误:如果没有匹配项 return 1,这不是正确答案。初始化为超出范围的值:

indexOfElement50  : Integer range TheRecords'range := 0;

编译时出现警告;因为 indexOfElement50 是范围保护的,并且 运行 它产生:

./main2
raised CONSTRAINT_ERROR : main2.adb:12 range check failed

初始化时,显示范围保护。西蒙很好地解释了如何解决这个问题!