배열

배열 (Arrays)

배열은 Ada의 또 다른 기본 합성 타입 계열을 제공해요. 배열은 인덱싱으로 선택할 수 있는 요소들의 연속적인(contiguous) 컬렉션을 정의하는 데 사용돼요.

출처: 배열 문서

본문

배열 타입 선언 (Array type declaration)

간단한 예시를 볼게요:

with Ada.Text_IO; use Ada.Text_IO;

procedure Greet is
   type My_Int is range 0 .. 1000;
   type Index is range 1 .. 5;

   type My_Int_Array is
     array (Index) of My_Int;
   --                 ^ Type of elements
   --       ^ Bounds of the array
   Arr : My_Int_Array := (2, 3, 5, 7, 11);
   --                    ^ Array literal
   --                      (aggregate)

   V : My_Int;
begin
   for I in Index loop
      V := Arr (I);
      --        ^ Take the Ith element
      Put (My_Int'Image (V));
   end loop;
   New_Line;
end Greet;

먼저 주목할 점은 배열의 크기가 아니라 인덱스 타입을 지정한다는 것이에요. 여기서는 1부터 5까지의 Index라는 정수 타입을 선언했으므로, 각 배열 인스턴스는 5개 요소를 가지며 첫 요소는 인덱스 1, 마지막 요소는 인덱스 5에 위치해요.

이 예시는 인덱스로 정수 타입을 사용했지만 Ada는 더 일반적이에요. 열거 타입을 포함해 어떤 이산형 타입이든 배열을 인덱싱할 수 있어요. 이게 무엇을 뜻하는지 곧 볼 거예요.

또 하나 주목할 점은 주어진 인덱스에서 배열 요소를 조회하는 것이 함수 호출과 같은 구문을 사용한다는 것이에요. 즉 배열 객체 뒤에 괄호 안의 인덱스를 붙이는 거죠. 그래서 A (B) 같은 표현식을 보면 그것이 함수 호출인지 배열 첨자인지는 A가 가리키는 것에 따라 달라져요.

마지막으로 (2, 3, 5, 7, 11) 표현식으로 배열을 초기화하는 방식을 주목하세요. 이것은 Ada의 또 다른 애그리게이트이며, 어떤 의미에서 배열의 리터럴 표현식이에요. 3이 정수의 리터럴 표현식인 것과 마찬가지죠. 이 표기법은 매우 강력하고 나중에 소개할 여러 속성을 가져요. 자세한 개요는 애그리게이트 타입 표기법에 나와 있어요.

배열과 무관하게, 이 예시는 Ada.Text_IO의 두 프로시저도 보여줘요:

  • Put: 종료 개행 문자 없이 문자열을 표시해요.
  • New_Line: 개행 문자를 출력해요.

이제 어떤 이산형 타입이든 배열을 인덱싱할 수 있다는 것이 무엇을 의미하는지 자세히 볼게요.

C/C++와 달리, Ada에서 배열 객체는 전체 데이터 구조이고 단순히 핸들이나 포인터가 아니에요. 배열과 그 초기 요소에 대한 포인터 사이에 암묵적 동등성이 없어요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Array_Bounds_Example is
   type My_Int is range 0 .. 1000;

   type Index is range 11 .. 15;
   --                  ^ Low bound can
   --                    be any value

   type My_Int_Array is
     array (Index) of My_Int;

   Tab : constant My_Int_Array :=
           (2, 3, 5, 7, 11);
begin
   for I in Index loop
      Put (My_Int'Image (Tab (I)));
   end loop;
   New_Line;
end Array_Bounds_Example;

한 가지 효과는 배열의 경계가 어떤 값이든 될 수 있다는 점이에요. 첫 예시에서 첫 인덱스가 1인 배열 타입을 만들었지만, 위 예시에서는 첫 인덱스가 11인 배열 타입을 선언해요. Ada에서는 완전히 괜찮아요. 게다가 인덱스 타입을 범위로 사용해 배열 인덱스를 순회하므로, 배열을 사용하는 코드는 변경할 필요가 없어요.

이는 배열을 다루는 코드에 중요한 결과를 가져와요. 경계가 달라질 수 있으므로 배열을 순회/사용할 때 특정 경계를 가정하거나 하드코딩해서는 안 돼요. 아래 코드는 인덱스 타입을 쓰므로 좋지만, 아래 for 루프처럼 하드코딩한 것은 정확히 동작해도 나쁜 관행이에요:

for I in 11 .. 15 loop
   Tab (I) := Tab (I) * 2;
end loop;

어떤 이산형 타입이든 배열을 인덱싱하는 데 사용할 수 있으므로 열거 타입도 허용돼요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Month_Example is
   type Month_Duration is range 1 .. 31;
   type Month is (Jan, Feb, Mar, Apr,
                  May, Jun, Jul, Aug,
                  Sep, Oct, Nov, Dec);

   type My_Int_Array is
     array (Month) of Month_Duration;
   --       ^ Can use an enumeration type
   --         as the index

   Tab : constant My_Int_Array :=
   --    ^ constant is like a variable but
   --      cannot be modified
     (31, 28, 31, 30, 31, 30,
      31, 31, 30, 31, 30, 31);
   --  Maps months to number of days
   --  (ignoring leap years)

   Feb_Days : Month_Duration := Tab (Feb);
   --  Number of days in February
begin
   for M in Month loop
      Put_Line
        (Month'Image (M) & " has "
         & Month_Duration'Image (Tab (M))
         & " days.");
   --    ^ Concatenation operator
   end loop;
end Month_Example;

위 예시에서 우리는:

  • 월을 일수로 매핑하는 배열 타입을 만들어요.
  • 배열을 만들고, 월을 실제 일수로 매핑하는 애그리게이트로 인스턴스화해요.
  • 배열을 순회하며 월과 각 월의 일수를 출력해요.

열거 값을 인덱스로 쓸 수 있다는 것은 위와 같은 매핑을 만드는 데 매우 유용하며 Ada에서 자주 쓰이는 기능이에요.

인덱싱 (Indexing)

배열 요소를 선택하는 구문은 이미 봤어요. 하지만 주목할 점이 몇 가지 더 있어요.

첫째, Ada 일반에서 그렇듯 인덱싱 연산은 강한 타입이에요. 잘못된 타입의 값으로 배열을 인덱싱하면 컴파일 타임 오류가 나요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Greet is
   type My_Int is range 0 .. 1000;

   type My_Index   is range 1 .. 5;
   type Your_Index is range 1 .. 5;

   type My_Int_Array is
     array (My_Index) of My_Int;

   Tab : My_Int_Array := (2, 3, 5, 7, 11);
begin
   for I in Your_Index loop
      Put (My_Int'Image (Tab (I)));
   --                         ^ Compile time error
   end loop;
   New_Line;
end Greet;

둘째, Ada의 배열은 경계 검사(bounds check)가 돼요. 이는 배열 경계 밖의 요소에 접근하려 하면, 안전하지 않은 언어처럼 임의 메모리에 접근하는 대신 런타임 오류가 난다는 뜻이에요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Greet is
   type My_Int is range 0 .. 1000;
   type Index  is range 1 .. 5;

   type My_Int_Array is
     array (Index) of My_Int;

   Tab : My_Int_Array := (2, 3, 5, 7, 11);
begin
   for I in Index range 2 .. 6 loop
      Put (My_Int'Image (Tab (I)));
      --                      ^ Will raise an
      --                        exception when
      --                        I = 6
   end loop;
   New_Line;
end Greet;

더 간단한 배열 선언 (Simpler array declarations)

지금까지 예시에서 배열을 위해 항상 인덱스 타입을 명시적으로 만들었어요. 타입과 가독성을 위해 유용할 수 있지만, 때로는 값의 범위를 표현하고 싶을 뿐일 때도 있어요. Ada는 그것도 허용해요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Simple_Array_Bounds is
   type My_Int is range 0 .. 1000;

   type My_Int_Array is
     array (1 .. 5) of My_Int;
   --       ^ Subtype of Integer

   Tab : constant My_Int_Array :=
           (2, 3, 5, 7, 11);
begin
   for I in 1 .. 5 loop
   --       ^ Subtype of Integer
      Put (My_Int'Image (Tab (I)));
   end loop;
   New_Line;
end Simple_Array_Bounds;

이 예시는 range 구문으로 배열의 범위를 정의하는데, Integer의 익명 하위 타입을 지정하고 그것으로 배열을 인덱싱해요. 즉 인덱스의 타입이 Integer라는 뜻이에요. 마찬가지로 for 루프에서 익명 범위를 사용하면 반복 변수의 타입도 Integer여서 ITab을 인덱싱할 수 있어요. 배열 경계에 명명된 하위 타입을 사용할 수도 있어요.

범위 애트리뷰트 (Range attribute)

배열을 순회할 때 경계를 하드코딩하는 것이 나쁜 생각이라고 앞서 언급했고, for 루프에서 배열의 인덱스 타입/하위 타입을 사용해 그 범위를 순회하는 방법을 보여줬어요. 그러면 배열이 익명 범위를 경계로 가질 때는 어떻게 순회를 작성할지가 문제가 돼요. 범위를 참조할 이름이 없으니까요. Ada는 배열 객체의 여러 애트리뷰트로 이 문제를 해결해요:

with Ada.Text_IO; use Ada.Text_IO;

procedure Range_Example is
   type My_Int is range 0 .. 1000;

   type My_Int_Array is
     array (1 .. 5) of My_Int;

   Tab : constant My_Int_Array :=
           (2, 3, 5, 7, 11);
begin
   for I in Tab'Range loop
   --          ^ Gets the range of Tab
      Put (My_Int'Image (Tab (I)));
   end loop;
   New_Line;
end Range_Example;

더 세밀한 제어를 원하면 별도의 'First'Last 애트리뷰트를 사용할 수 있어요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Array_Attributes_Example is
   type My_Int is range 0 .. 1000;

   type My_Int_Array is
     array (1 .. 5) of My_Int;

   Tab : My_Int_Array :=
           (2, 3, 5, 7, 11);
begin
   for I in Tab'First .. Tab'Last - 1 loop
   --          ^ Iterate on every index
   --            except the last
      Put (My_Int'Image (Tab (I)));
   end loop;
   New_Line;
end Array_Attributes_Example;

이 예시들의 'Range, 'First, 'Last 애트리뷰트는 배열 인스턴스뿐 아니라 배열 타입 이름에도 적용할 수 있었어요. 위 예시에는 없지만 배열 인스턴스 A에 유용한 또 다른 애트리뷰트는 A'Length로, A가 포함하는 요소의 수예요.

요소를 포함하지 않는 "null 배열"을 갖는 것도 합법적이고 때로 유용해요. 상한이 하한보다 작은 인덱스 범위를 정의하면 이 효과를 얻을 수 있어요.

무제한 배열 (Unconstrained arrays)

이제 Ada 배열 기능의 가장 강력한 측면 중 하나를 살펴볼게요.

지금까지 정의한 모든 배열 타입은 고정 크기예요. 이 타입의 모든 인스턴스는 같은 경계를 가지므로 같은 수의 요소와 같은 크기를 가져요.

하지만 Ada는 경계가 고정되지 않은 배열 타입도 선언할 수 있게 해줘요. 이 경우 경계는 타입의 인스턴스를 만들 때 제공해야 해요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Unconstrained_Array_Example is
   type Days is (Monday, Tuesday, Wednesday,
                 Thursday, Friday,
                 Saturday, Sunday);

   type Workload_Type is
     array (Days range <>) of Natural;
   --  Indefinite array type
   --       ^ Bounds are of type Days,
   --         but not known

   Workload : constant
     Workload_Type (Monday .. Friday) :=
   --               ^ Specify the bounds
   --                 when declaring
      (Friday => 7, others => 8);
   --               ^ Default value
   --  ^ Specify element by name of index
begin
   for I in Workload'Range loop
      Put_Line (Integer'Image (Workload (I)));
   end loop;
end Unconstrained_Array_Example;

배열의 경계가 알려져 있지 않다는 사실은 Days range <> 구문으로 표시돼요. 이산형 타입 Discrete_Type이 주어졌을 때, 배열 타입에서 인덱스에 Discrete_Type을 사용하면 Discrete_Type이 인덱스의 타입이 되고 각 배열 인스턴스의 인덱스 값 범위를 구성해요. 인덱스를 Discrete_Type range <>로 정의하면 Discrete_Type이 인덱스의 타입이 되지만, 서로 다른 배열 인스턴스는 이 타입의 다른 경계를 가질 수 있어요.

Discrete_Type range <> 구문으로 인덱스가 정의된 배열 타입을 무제한 배열 타입(unconstrained array type) 이라고 하고, 위에서 보듯 인스턴스를 만들 때 경계를 제공해야 해요.

위 예시는 애그리게이트 구문의 다른 형태도 보여줘요. 화살표 연관(arrow association)의 왼쪽에 인덱스 값을 줘서 이름으로 연관을 지정할 수 있어요. 1 => 2는 "배열의 인덱스 1 요소에 값 2를 대입한다"는 뜻이에요. others => 8은 "이 애그리게이트에서 이전에 대입되지 않은 모든 요소에 값 8을 대입한다"는 뜻이에요.

주의: "박스(box)" 표기법(<>)은 Ada에서 와일드카드나 플레이스홀더로 흔히 쓰여요. "여기서 기대되는 것이 무엇이든 될 수 있다"는 의미일 때 자주 보게 될 거예요.

Ada의 무제한 배열은 C의 가변 길이 배열과 비슷해 보일 수 있지만 실제로는 훨씬 강력해요. 언어의 진정한 일급 값(First-class values)이기 때문이에요. 서브프로그램의 매개변수로 전달하거나 함수에서 반환할 수 있고, 값의 일부로 경계를 암묵적으로 포함해요. 즉 배열과 함께 경계나 길이를 명시적으로 전달할 필요가 없어요. 앞서 설명한 'First, 'Last, 'Range, 'Length 애트리뷰트로 접근 가능하니까요.

같은 무제한 배열 타입의 서로 다른 인스턴스가 다른 경계를 가질 수는 있지만, 특정 인스턴스는 생애 전체에 걸쳐 같은 경계를 가져요. 이 덕분에 Ada는 무제한 배열을 효율적으로 구현할 수 있어요. 인스턴스는 스택에 저장될 수 있고 Java 같은 언어처럼 힙 할당이 필요 없어요.

미리 정의된 배열 타입: String

우리의 Ada 타입 소개의 반복되는 주제는 Boolean이나 Integer 같은 중요한 내장 타입이 사용자에게도 제공되는 동일한 기능으로 정의된다는 점이었어요. 문자열도 마찬가지예요. Ada의 String 타입은 단순한 배열이에요. Ada에서 문자열 타입이 정의되는 방식:

type String is
  array (Positive range <>) of Character;

Ada가 문자열을 더 편리하게 만들기 위해 추가하는 유일한 내장 기능은 사용자 정의 리터럴이에요. 아래 예시에서 볼 수 있죠.

힌트: 문자열 리터럴은 애그리게이트의 구문 설탕이에요. 아래 예시에서 AB는 같은 값을 가져요.

package String_Literals is
    --  Those two declarations are equivalent
    A : String (1 .. 11) := "Hello World";
    B : String (1 .. 11) :=
        ('H', 'e', 'l', 'l', 'o', ' ',
         'W', 'o', 'r', 'l', 'd');
end String_Literals;
with Ada.Text_IO; use Ada.Text_IO;

procedure Greet is
   Message : String (1 .. 11) := "dlroW olleH";
   --        ^ Pre-defined array type.
   --          Component type is Character
begin
   for I in reverse Message'Range loop
      --    ^ Iterate in reverse order
      Put (Message (I));
   end loop;
   New_Line;
end Greet;

하지만 객체의 경계를 명시적으로 지정하는 것은 약간 번거로워요. 리터럴의 문자 수를 수동으로 세야 하거든요. 다행히 Ada가 더 쉬운 방법을 줘요. 초기화를 제공하면 무제한 배열 타입의 인스턴스를 만들 때 경계를 생략할 수 있어요. 초기화 표현식에서 경계를 추론할 수 있기 때문이에요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Greet is
   Message : constant String := "dlroW olleH";
   --                 ^ Bounds are automatically
   --                   computed from
   --                   initialization value
begin
   for I in reverse Message'Range loop
      Put (Message (I));
   end loop;
   New_Line;
end Greet;
with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   type Integer_Array is
     array (Natural range <>) of Integer;

   My_Array : constant Integer_Array :=
                (1, 2, 3, 4);
   --         ^^^^^^^^^^^^^^^^^^^^^
   --          Bounds are automatically
   --          computed from
   --          initialization value
begin
    null;
end Main;

주의: 위에서 보듯 표준 String 타입은 Ada에서 배열이에요. 그래서 배열의 장점과 단점을 공유해요. String 값은 스택에 할당되고 효율적으로 접근되며 경계가 불변(immutable)이에요. C++의 std::string에 가까운 것을 원한다면 Ada 표준 라이브러리의 Unbounded Strings를 사용할 수 있어요. 이 타입은 내용을 추가할 수 있는 가변적이고 자동 관리되는 문자열 버퍼에 더 가까워요.

제약 (Restrictions)

배열에 대한 매우 중요한 점: 인스턴스를 만들 때 경계를 반드시 알아야 해요. 예를 들어 다음은 불법이에요.

declare
   A : String;
begin
   A := "World";
end;

또한 배열 요소의 값을 바꿀 수는 있지만, 초기화된 후 배열의 경계(따라서 크기)는 바꿀 수 없어요. 그래서 이것도 불법이에요:

declare
   A : String := "Hello";
begin
   A := "World";       --  OK: Same size
   A := "Hello World"; --  Not OK: Different size
end;

이런 종류의 오류에 대해 이렇게 단순한 경우에는 경고를 기대할 수 있지만, 일반적인 경우 올바른 길이의 값을 대입하는지 컴파일러가 알 수는 없어요. 그래서 이 위반은 일반적으로 런타임 오류를 초래해요.

주의: 나중에 더 배우겠지만, 배열만이 컴파일 타임에 크기를 모를 수 있는 인스턴스의 타입이 아니라는 점을 아는 것이 중요해요. 그런 객체를 무한 하위 타입(indefinite subtype) 이라고 해요. 하위 타입의 크기가 컴파일 타임에 알려지지 않고 (런타임에) 동적으로 계산된다는 뜻이에요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Indefinite_Subtypes is
    function Get_Number return Integer is
    begin
        return Integer'Value (Get_Line);
    end Get_Number;

   A : String := "Hello";
   --  Indefinite subtype

   B : String (1 .. 5) := "Hello";
   --  Definite subtype

   C : String (1 .. Get_Number);
   --  Indefinite subtype
   --  (Get_Number's value is computed at
   --  run-time)
begin
   null;
end Indefinite_Subtypes;

여기서 'Value 애트리뷰트는 문자열을 정수로 변환해요.

무제한 배열 반환 (Returning unconstrained arrays)

함수의 반환 타입은 어떤 타입이든 될 수 있어요. 함수는 컴파일 타임에 크기를 알 수 없는 값을 반환할 수 있어요. 마찬가지로 매개변수도 어떤 타입이든 될 수 있어요. 예를 들어 무제한 String을 반환하는 함수:

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is

   type Days is (Monday, Tuesday, Wednesday,
                 Thursday, Friday,
                 Saturday, Sunday);

   function Get_Day_Name (Day : Days := Monday)
                          return String is
   begin
      return
        (case Day is
         when Monday    => "Monday",
         when Tuesday   => "Tuesday",
         when Wednesday => "Wednesday",
         when Thursday  => "Thursday",
         when Friday    => "Friday",
         when Saturday  => "Saturday",
         when Sunday    => "Sunday");
   end Get_Day_Name;

begin
   Put_Line ("First day is "
             & Get_Day_Name (Days'First));
end Main;

(이 예시는 설명 목적뿐이에요. 스칼라 타입용 내장 메커니즘인 'Image 애트리뷰트가 열거 타입의 어떤 요소의 이름을 String으로 반환해요. 예를 들어 Days'Image(Monday)"MONDAY"예요.)

가비지 컬렉터가 없는 언어에서 가변 크기 객체를 반환하는 것은 구현상 약간 복잡하기 때문에 C와 C++는 그것을 허용하지 않고 사용자의 명시적 동적 할당/해제에 의존해요. 문제는 사용하지 않는 메모리를 모으고 싶을 때 명시적 저장소 관리가 안전하지 않다는 점이에요. Ada의 가변 크기 객체 반환 능력은 동적 할당의 사용 사례 하나를 없애고, 따라서 프로그램에서 잠재적 버그의 원인이 하나 줄어들어요. Rust는 C/C++ 모델을 따르지만 안전한 포인터 의미론을 가져요. 그래도 동적 할당은 여전히 사용돼요. Ada는 어떤 모델이든 사용할 수 있으므로 잠재적 성능 이점을 얻을 수 있어요.

배열 선언 2 (Declaring arrays 2)

크기와 경계가 런타임에 결정되는 배열 타입을 가질 수는 있지만, 배열의 컴포넌트 타입은 유한하고 제약된(constrained) 타입이어야 해요. 따라서 예를 들어 문자열 배열을 선언해야 한다면, 컴포넌트로 사용되는 String 하위 타입은 고정 크기여야 해요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Show_Days is
   type Days is (Monday, Tuesday, Wednesday,
                 Thursday, Friday,
                 Saturday, Sunday);

   subtype Day_Name is String (1 .. 2);
   --  Subtype of string with known size

   type Days_Name_Type is
     array (Days) of Day_Name;
   --       ^ Type of the index
   --                ^ Type of the element.
   --                  Must be definite

   Names : constant Days_Name_Type :=
     ("Mo", "Tu", "We", "Th", "Fr", "Sa", "Su");
   --  Initial value given by aggregate
begin
   for I in Names'Range loop
      Put_Line (Names (I));
   end loop;
end Show_Days;

배열 슬라이스 (Array slices)

우리가 다룰 Ada 배열의 마지막 기능은 배열 슬라이스예요. 배열의 슬라이스(요소의 연속적인 시퀀스)를 이름이나 값으로 취하고 사용하는 것이 가능해요.

with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
    Buf : String := "Hello ...";

    Full_Name : String := "John Smith";
begin
    Buf (7 .. 9) := "Bob";
    --  Careful! This works because the string
    --  on the right side is the same length as
    --  the replaced slice!

    --  Prints "Hello Bob"
    Put_Line (Buf);

     --  Prints "Hi John"
    Put_Line ("Hi " & Full_Name (1 .. 4));
end Main;

위에서 보듯 슬라이스를 대입의 왼쪽에 사용해 배열의 일부만 바꿀 수 있어요. 배열의 슬라이스는 배열과 같은 타입이지만 슬라이스의 경계로 제약된 다른 하위 타입이에요.

주의: Ada에는 다차원 배열이 있지만 이 과정에서는 다루지 않아요. 슬라이스는 1차원 배열에서만 동작해요.

이름 변경 (Renaming)

지금까지 이름을 바꿀 수 있는 요소들이 있었어요: 서브프로그램, 패키지, 레코드 컴포넌트. renames 키워드로 객체도 이름을 바꿀 수 있어요. 이는 이 객체들에 대한 대체 이름을 만들 수 있게 해줘요. 예시를 볼게요:

package Measurements is

   subtype Degree_Celsius is Float;

   Current_Temperature : Degree_Celsius;

end Measurements;

with Ada.Text_IO;  use Ada.Text_IO;
with Measurements;

procedure Main is
   subtype Degrees is
     Measurements.Degree_Celsius;

   T : Degrees
         renames Measurements.Current_Temperature;
begin
    T := 5.0;

    Put_Line (Degrees'Image (T));
    Put_Line (Degrees'Image
      (Measurements.Current_Temperature));

    T := T + 2.5;

    Put_Line (Degrees'Image (T));
    Put_Line (Degrees'Image
      (Measurements.Current_Temperature));
end Main;

위 예시에서 Measurements 패키지의 Current_Temperature 객체를 이름 변경해 변수 T를 선언해요. 실행하면 Current_Temperature와 그 대체 이름 T가 같은 값을 가짐을 볼 수 있어요. 첫째, 5.0을 보여주고, 덧셈 후 7.5를 보여줘요. 본질적으로 같은 객체를 두 개의 다른 이름으로 참조하기 때문이에요.

위 예시에서 DegreesDegree_Celsius의 별칭으로 사용하고 있다는 점에 주의하세요. 이 방법은 이전에 다뤘어요.

이름 변경은 더 복잡한 배열 인덱싱의 가독성을 높이는 데 유용할 수 있어요. 배열의 특정 위치에 접근할 때마다 명시적으로 인덱스를 사용하는 대신, 그 위치들을 이름 변경해 더 짧은 이름을 만들 수 있어요. 다음 예시를 볼게요:

package Colors is

   type Color is (Black,
                  Red,
                  Green,
                  Blue,
                  White);

   type Color_Array is
     array (Positive range <>) of Color;

   procedure Reverse_It (X : in out Color_Array);

end Colors;

package body Colors is

   procedure Reverse_It (X : in out Color_Array)
   is
   begin
      for I in X'First ..
               (X'Last + X'First) / 2
      loop
         declare
            Tmp     : Color;
            X_Left  : Color
              renames X (I);
            X_Right : Color
              renames X (X'Last + X'First - I);
         begin
            Tmp     := X_Left;
            X_Left  := X_Right;
            X_Right := Tmp;
         end;
      end loop;
   end Reverse_It;

end Colors;

with Ada.Text_IO; use Ada.Text_IO;

with Colors; use Colors;

procedure Test_Reverse_Colors is

   My_Colors : Color_Array (1 .. 5) :=
     (Black, Red, Green, Blue, White);

begin
   for C of My_Colors loop
      Put_Line ("My_Color: "
                & Color'Image (C));
   end loop;

   New_Line;
   Put_Line ("Reversing My_Color...");
   New_Line;
   Reverse_It (My_Colors);

   for C of My_Colors loop
      Put_Line ("My_Color: "
                & Color'Image (C));
   end loop;

end Test_Reverse_Colors;

위 예시에서 Colors 패키지는 배열의 두 위치에 새 이름을 선언함으로써 Reverse_It 프로시저를 구현해요. 실제 구현은 읽기 쉬워져요:

begin
   Tmp     := X_Left;
   X_Left  := X_Right;
   X_Right := Tmp;
end;

이름 변경이 없는 대안 버전과 비교해 보세요:

begin
   Tmp                      := X (I);
   X (I)                    := X (X'Last +
                               X'First - I);
   X (X'Last + X'First - I) := Tmp;
end;

더 알아보기 (Learn more)