학습 매체 : 책
책이름 : 레트로의 유니티 게임 프로그래밍 에센스
저자 : 이제민
본 내용은 해당 강의 내용을 공부하면서 정리한 글입니다.
int year = 1999;
string movie = "바람과 함께 사라지다.";
Animal Tom = new Animal();
결론부터 말하면 클래스로 만든 변수는 참조(Reference) 타입이기 때문이다.
참조 타입의 변수는 실체화된 오브젝트가 아니다.
참조 타입의 변수를 선언하는 것만으로는 오브젝트가 생성되지 않기 때문에 new를 사용해 오브젝트를 개별적으로 생성해야 한다.
즉, 변수 Tom은 Animal 오브젝트를 가리키는 참조이다.
참조 타입과 값 타입에 대해서 살펴보자.
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에 적절한 값을 할당했다.
여기까지 코드를 실행했으면 프로그램의 메모리 상태는 다음과 같이 표현할 수 있다.
지금까지 확인한 내용 정리
- 클래스는 오브젝트를 생성하는 틀
- new를 사용하여 클래스로부터 오브젝트를 생성
- 한 클래스로 여러 오브젝트를 만들 수 있다.
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의 것으로 덮어쓰기 된다.
하지만 Tom의 멤버 변수 name의 값은 변경되지 않고 "톰"으로 남아 있다. jerry만 수정하고 Tom을 수정하지 않았기 때문이다.
그러면 이제 코드를 실행해보자.
두 번째 로그에서 수정된 제리의 이름 jerry.name이 "미키"로 출력되는 것은 당연하다. 하지만, 첫 번째 로그에서 톰의 이름 Tom.name도 "미키"로 출력된다.
사실 지금까지 작성한 내용과 설명은 잘못되었다. jerry = Tom;은 직관과 다르게 동작한다.
참조값은 메모리 주소값 그 자체는 아니다. 참조값은 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)가 틈틈이 자동으로 파괴하여 정리한다.
변수에 실체가 아니라 실체로 향하는 참조가 할당되고, 변수에 접근하면 참조를 통해 실체에 접근하는 변수를 참조 타입이라고 한다. C#에서 클래스 타입의 변수는 참조 타입으로 동작한다.
참조 타입은 '한 사람을 여러 개의 별명으로 부르는' 상황을 만들 수 있다. 즉, 오브젝트는 하나지만 그것을 여러 개의 참조 변수가 동시에 가리킬 수 있다.
참조 타입이 중요한 이유는 '컴포넌트의 참조를 변수로 가져와서' 사용하는 것을 가능하게 만들기 때문이다.
모든 변수가 참조로 동작하는 것은 아니다. 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에서 값 타입과 참조 타입의 동작 차이다.
앞으로 사용할 변수 타입을 구분하면 다음과 같다.
string은 클래스로 선언되어 있지만 값 타입으로 동작한다. immutable(생성 후 변경 불가)로 선언되어 있기 때문이다.
다음 강의에서 계속~