할당 연산자

할당 연산자

Pascal에서 := 연산자는 한 타입의 변수 값을 다른 변수에 할당하는 동작을 규정해요. Free Pascal에서는 이 할당 연산자를 직접 정의해서, 새로 만든 타입에 맞는 할당 동작을 지정할 수 있습니다.

출처: 문서

본문

할당 연산자는 한 타입의 변수를 다른 변수에 할당하는 동작을 정의해요. 이때 결과 타입은 할당문 왼쪽에 있는 변수의 타입과 일치해야 하고, 할당 연산자의 단일 매개변수는 할당문 오른쪽에 있는 표현식의 타입과 같아야 합니다.

이 체계를 이용하면 새로운 타입을 선언하고, 그 타입에 맞는 할당을 정의할 수 있어요. 예를 들어 새로 정의한 Complex 타입에 값을 할당하려면:

Var
  C,Z : Complex; // New type complex
begin
  Z:=C;  // assignments between complex types.
end;

다음과 같은 할당 연산자를 정의해야 합니다.

Operator := (C : Complex) z : complex;

이번에는 real 타입을 complex 타입에 할당하고 싶은 상황을 볼게요.

var
  R : real;
  C : complex;
begin
  C:=R;
end;

이 경우에는 다음 할당 연산자를 정의해야 합니다.

Operator := (r : real) z : complex;

이 선언에서 볼 수 있듯이, 연산자 오른쪽에 real 표현식, 왼쪽에 complex 표현식이 올 때 := 연산자의 동작을 규정합니다. 구현 예시는 이렇게 할 수 있어요.

operator := (r : real) z : complex;
begin
  z.re:=r;
  z.im:=0.0;
end;

예시에서 볼 수 있듯이 결과 식별자(여기서는 z)는 할당 결과를 저장하는 데 쓰여요. Delphi 모드나 ObjFPC 모드로 컴파일할 때는 특별한 식별자 Result를 쓸 수 있는데, z 대신 사용할 수 있으므로 위 코드는 다음처럼 쓸 수도 있습니다.

operator := (r : real) z : complex;
begin
  Result.re:=r;
  Result.im:=0.0;
end;

할당 연산자는 한 타입을 다른 타입으로 변환하는 데에도 사용돼요. 컴파일러는 오버로드된 할당 연산자들을 차례로 살펴보면서 왼쪽·오른쪽 표현식의 타입과 일치하는 것을 찾아요. 만약 일치하는 연산자가 없다면 "type mismatch" 오류가 발생합니다.

주의사항: 교환 법칙이 성립하지 않아요

할당 연산자는 교환적(commutative)이 아니에요. 컴파일러는 두 인자의 역할을 절대 뒤집지 않습니다. 즉 위에서 정의한 할당 연산자가 있다고 해도, 다음은 불가능해요.

var
  R : real;
  C : complex;
begin
  R:=C;
end;

반대 방향의 할당을 허용하려면 그 방향에 대한 할당 연산자를 따로 정의해야 합니다. (실수와 복소수 사이에서는 이렇게 되지 않아요.)

주의사항: 암시적 타입 변환

할당 연산자는 암시적 타입 변환에도 사용되기 때문에 원치 않는 결과가 생길 수 있어요. 다음 정의를 생각해 볼게요.

operator := (r : real) z : complex;
function exp(c : complex) : complex;

그런데 다음 할당은 type mismatch를 일으킵니다.

Var
  r1,r2 : real;
begin
  r1:=exp(r2);
end;

불일치가 생기는 이유는 이래요. 컴파일러가 complex 인자를 받는 exp 함수 정의를 만나면, r2를 암시적으로 complex로 변환해 그 exp 함수를 사용해 버려요. 그 함수의 결과는 complex라서 r1에 할당할 수 없으니 "type mismatch" 오류가 나는 거예요. 컴파일러는 올바른 인자를 가진 다른 exp를 더 찾아보지 않습니다. 이 특정 문제는 다음처럼 지정해서 피할 수 있어요.

  r1:=system.exp(r2);

명시적 typecast를 할 때도 할당 연산자가 있으면 컴파일러가 암시적 변환을 시도해요. 즉 다음 코드는

Var
  R1 : T1;
  R2 : T2;
begin
  R2:=T2(R1);

이런 연산자로 처리됩니다.

Operator := (aRight: T1) Res: T2;

그러나 Explicit 연산자를 정의하면, 컴파일러가 typecast를 만났을 때 그쪽을 대신 사용해요. 반대 방향은 성립하지 않아요. 즉 일반 할당에서는 컴파일러가 explicit 할당 연산자를 고려하지 않습니다.

다음 정의가 있다고 할게요.

uses
  sysutils;
type
  TTest1 = record
    f: LongInt;
  end;
  TTest2 = record
    f: String;
  end;
  TTest3 = record
    f: Boolean;
  end;

이제 할당 연산자를 만들 수 있어요.

operator := (aRight: TTest1) Res: TTest2;
begin
  Writeln('Implicit TTest1 => TTest2');
  Res.f := IntToStr(aRight.f);
end;
operator := (aRight: TTest1) Res: TTest3;
begin
  Writeln('Implicit TTest1 => TTest3');
  Res.f := aRight.f <> 0;
end;

typecasting 연산자도 정의할 수 있고요.

operator Explicit(aRight: TTest2) Res: TTest1;
begin
  Writeln('Explicit TTest2 => TTest1');
  Res.f := StrToIntDef(aRight.f, 0);
end;
operator Explicit(aRight: TTest1) Res: TTest3;
begin
  Writeln('Explicit TTest1 => TTest3');
  Res.f := aRight.f <> 0;
end;

그래서 다음 코드는

var
  t1: TTest1;
  t2: TTest2;
  t3: TTest3;
begin
  t1.f := 42;
  // Implicit
  t2 := t1;
  // theoretically explicit, but implicit op will be used,
  // because no explicit operator is defined
  t2 := TTest2(t1);
  // the following would not compile,
  // no assignment operator defined (explicit one won't be used here)
  //t1 := t2;
  // Explicit
  t1 := TTest1(t2);
  // first explicit (TTest2 => TTest1) then implicit (TTest1 => TTest3)
  t3 := TTest1(t2);
  // Implicit
  t3 := t1;
  // explicit
  t3 := TTest3(t1);
end.

다음 출력을 만들어 냅니다.

Implicit TTest1 => TTest2
Implicit TTest1 => TTest2
Explicit TTest2 => TTest1
Explicit TTest2 => TTest1
Implicit TTest1 => TTest3
Implicit TTest1 => TTest3
Explicit TTest1 => TTest3

더 알아보기