레트로의 유니티 게임 프로그래밍 에센스 - 5.3

Cosmos·2023년 4월 3일
0

학습 매체 : 책

책이름 : 레트로의 유니티 게임 프로그래밍 에센스

저자 : 이제민


본 내용은 해당 강의 내용을 공부하면서 정리한 글입니다.


5.3 참조 타입


  • 여기까지 진행했다면 어째서 int string 등의 '원시적인 타입'의 변수들은 new 키워드를 사용하지 않고 변수에 곧바로 값을 할당하는지 의문이 생길 수 있다.
int year = 1999;
string movie = "바람과 함께 사라지다.";
  • Animal 타입의 변수 Tom에 값을 할당할 때는 new 키워드를 사용하여 새로운 Animal 오브젝트를 생성하고 tom에 할당했다.
Animal Tom = new Animal();
  • 결론부터 말하면 클래스로 만든 변수는 참조(Reference) 타입이기 때문이다.
    참조 타입의 변수는 실체화된 오브젝트가 아니다.

  • 참조 타입의 변수를 선언하는 것만으로는 오브젝트가 생성되지 않기 때문에 new를 사용해 오브젝트를 개별적으로 생성해야 한다.

  • 즉, 변수 Tom은 Animal 오브젝트를 가리키는 참조이다.

  • 참조 타입값 타입에 대해서 살펴보자.


5.3.1 두 마리의 동물 오브젝트

  • Zoo 스크립트에 jerry라는 오브젝트를 추가해보자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Zoo : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Animal Tom = new Animal();
        Tom.name = "톰";
        Tom.sound = "냐옹!";

        Animal jerry = new Animal();
        Tom.name = "제리";
        Tom.sound = "찍찍!";

        Tom.PlaySound();
        jerry.PlaySound();
    }
}
  • Animal 타입의 변수 jerry를 선언하고 새로운 Animal 오브젝트를 생성하여 할당했다.

  • 그리고 jerry의 멤버 변수 name과 sound에 적절한 값을 할당했다.

  • 여기까지 코드를 실행했으면 프로그램의 메모리 상태는 다음과 같이 표현할 수 있다.

  • 그러면 위 코드를 이제 실행해보자.

지금까지 확인한 내용 정리

  1. 클래스는 오브젝트를 생성하는 틀
  2. new를 사용하여 클래스로부터 오브젝트를 생성
  3. 한 클래스로 여러 오브젝트를 만들 수 있다.

5.3.2

  • 기존 코드를 한 번 수정해보자. 변수 jerry에 변수 Tom의 값을 대입하자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Zoo : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Animal Tom = new Animal();
        Tom.name = "톰";
        Tom.sound = "냐옹!";

        Animal jerry = new Animal();
        jerry.name = "제리";
        jerry.sound = "찍찍!";

        jerry = Tom;
        jerry.name = "미키";

        Tom.PlaySound();
        jerry.PlaySound();
    }
}
  • 다음 두 줄을 추가했다.
jerry = Tom;
jerry.name = "미키";
  • 이 과정에서 Tom과 jerry에 할당된 Animal 오브젝트가 어떻게 변형될지 생각해보자.

  • 먼저 jerry = Tom;으로 jerry에 Tom의 값을 할당했다. 그러면 jerry의 멤버 변수들의 값이 Tom의 것으로 덮어쓰기 된다.

  • 그리고 jerry.name = "미키"를 실행했다. jerry의 멤버 변수 name의 값이 "미키"로 변경된다.

  • 하지만 Tom의 멤버 변수 name의 값은 변경되지 않고 "톰"으로 남아 있다. jerry만 수정하고 Tom을 수정하지 않았기 때문이다.

  • 그러면 이제 코드를 실행해보자.

  • 두 번째 로그에서 수정된 제리의 이름 jerry.name이 "미키"로 출력되는 것은 당연하다. 하지만, 첫 번째 로그에서 톰의 이름 Tom.name도 "미키"로 출력된다.

  • 사실 지금까지 작성한 내용과 설명은 잘못되었다. jerry = Tom;은 직관과 다르게 동작한다.


5.3.3 참조 타입의 동작

  • jerry = Tom;이 실행되기 직전의 메모리 상태

  • 그림에서 변수 Tom과 jerry는 Animal 오브젝트 그 자체가 아니라 Animal 오브젝트를 가리키는 참조값을 저장한다. 참조값은 실제 오브젝트의 메모리 주소에 대응되는 값이다.

참조값은 메모리 주소값 그 자체는 아니다. 참조값은 C# 프로그램을 구동하는 CLR의 인덱스 테이블에 의해 메모리 주소와 대응되는 값이다.

  • 즉, Tom과 jerry에 할당된 참조값을 찾아가면 '진짜' 오브젝트가 있다. 그래서 jerry = Tom;을 실행하면 jerry의 모든 멤버 변수의 값이 Tom의 모든 멤버 변수의 값으로 덮어쓰기 된다고 착각하기 쉽다.

  • 실제로는 jerry에 할당된 참조값을 Tom에 할당된 참조값으로 덮어쓰기 한다.

  • Tom과 jerry가 같은 오브젝트를 가리킨다.

  • jerry에 할당된 참조값이 Tom의 참조값으로 변경되면서 jerry는 직전까지 가리키던 Animal 오브젝트를 더 이상 가리키지 않고, Tom이 가리키던 Animal 오브젝트를 가리키게 된다.

  • 결론적으로 변수는 Tom과 jerry 두 개 존재하지만 두 변수가 참조값을 통해 가리키는 Animal 오브젝트는 하나뿐이며 jerry를 통해 Animal 오브젝트를 수정하는 것은 Tom을 통해 Animal 오브젝트를 수정하는 것과 같은 의미가 된다.

  • 따라서 예제에서 jerry.name을 "미키"로 수정했을 때, Tom.name도 "미키"가 된 것이다.

  • 여기서 jerry가 Tom이 가리키던 오브젝트를 가리키게 되면서 jerry가 본래 가리키던 오브젝트는 아무도 가리키지 않는 '미아'가 되었다.

  • 이렇게 아무도 가리키지 않는 오브젝트는 사용할 방법이 없다. 해당 오브젝트를 부를 이름(변수)이 없어 접근할 방법이 없기 때문이다.

  • 이렇게 아무도 가리키지 않는 오브젝트는 C#의 가비지 컬렉터(Garbage Collector)가 틈틈이 자동으로 파괴하여 정리한다.


5.3.4 하나의 실체와 여러 개의 참조 변수

  • 변수에 실체가 아니라 실체로 향하는 참조가 할당되고, 변수에 접근하면 참조를 통해 실체에 접근하는 변수를 참조 타입이라고 한다. C#에서 클래스 타입의 변수는 참조 타입으로 동작한다.

  • 참조 타입은 '한 사람을 여러 개의 별명으로 부르는' 상황을 만들 수 있다. 즉, 오브젝트는 하나지만 그것을 여러 개의 참조 변수가 동시에 가리킬 수 있다.

  • 참조 타입이 중요한 이유는 '컴포넌트의 참조를 변수로 가져와서' 사용하는 것을 가능하게 만들기 때문이다.


5.3.5 값 타입과 참조 타입

  • 모든 변수가 참조로 동작하는 것은 아니다. float, int, string 등의 C# 내장 변수는 참조로 동작하지 않는다. 이런 타입을 값(Value) 타입이라고 한다.

  • 참조 타입 변수는 값(실체)으로 향하는 참조를 저장하고, 값 타입의 변수는 해당 변수 공간에 값 자체를 저장한다.

  • 값 타입은 여러 변수가 하나의 실체를 공유하는 상황이 생기지 않는다.

int a = 0;
int b = 10;
a= b; // a = 10, b = 10
b = 100; // a= 10, b = 100

  • a=b에서 값 타입과 참조 타입의 동작 차이다.

  • 앞으로 사용할 변수 타입을 구분하면 다음과 같다.

  • 몇 가지 예외가 있지만, class로 만든 대부분의 타입은 참조로 동작한다. C# 내장 변수 타입과 Vector3 같은 struct 타입을 제외하면 우리가 사용할 대부분의 변수는 참조 타입으로 동작한다.

string은 클래스로 선언되어 있지만 값 타입으로 동작한다. immutable(생성 후 변경 불가)로 선언되어 있기 때문이다.


다음 강의에서 계속~

profile
게임 개발을 목적으로 공부하고 있는 대학생입니다.

0개의 댓글