[14/20] 구조체와 객체
글을 시작하기에 앞서 해당 시리즈는 Allen Downey, Ben Lauwens의 저서인 Think Julia: How to Think Like a Computer Scientist 를 바탕으로 작성된 글임을 알려드립니다.
이 포스트는 Structs and Objects를 한글로 요약 정리한 글입니다.
구조체와 객체 (Structs and Objects)
지금까지 우리는 내장된 데이터 타입들과 함수를 어떻게 사용하는지 배웠다. 다음 단계는 고유한 데이터 타입을 생성하는 방법을 배우는 것이다. 이 주제는 양이 방대하기 때문에 여러 장에 걸쳐 공부할 것이다.
복합 데이터 타입 (Composite Types)
우리는 그동안 줄리아에 내장된 데이터 타입을 많이 사용했다. 이제부터는 2차원 공간에서 점을 나타내는 새로운 데이터 타입인 Point
를 정의할 것이다.
수학에서 점은 보통 좌표를 구분하는 쉼표와 함께 괄호 안에 표시된다. 예를 들어, (0,0)은 원점을 나타낸 것이고, (x,y)는 원점으로부터 x는 가로축, y는 세로축의 거리를 표시한 것이다.
- 좌표를 x와 y라는 두 변수에 별도로 저장할 수 있다.
- 좌표를 배열이나 튜플의 요소로 저장할 수 있다.
- 점을 객체로 나타내는 새로운 데이터 타입을 만들 수 있다.
새로운 타입을 만드는 것은 다른 방법에 비해서 더욱 복잡하지만, 확실한 장점이 있다. (장점은 나중에 볼 것이다)
복합 데이터 타입을 정의하는 것을 구조체(struct)라고 한다. 점의 struct
정의는 다음과 같다.
1 |
|
헤더는 새로운 구조체를 부를 이름으로 Point
라고 정의하며, 본문은 구조체의 속성(attributes) 또는 필드(fields)를 정의한다. Point
구조체는 x
와 y
라는 두 개의 필드를 가진다.
구조체는 객체를 만드는 공장과 같다. 점을 만들기 위해서는 필드의 값을 인수로 갖는 Point()
를 호출한다. Point()
를 함수로 쓸 때, 이를 생성자(constructor)라고 한다.
1 |
|
반환 값은 p
에 할당한 Point
객체로 표시된다.
새 객체를 만드는 것을 인스턴스화(instantiation)라고 하며, 객체는 데이터 타입의 인스턴스이다.
만약 인스턴스를 출력한다면, 줄리아는 해당 인스턴스가 어떤 데이터 타입 및 속성을 가지고 있는지 일려준다.
모든 객체는 어떤 데이터 타입의 인스턴스이다. 그렇기 때문에 객체(object)와 인스턴스(instance)라는 단어는 바꿔서 사용할 수 있다. 하지만 이 장에서는 개발자가 정의한 고유 데이터 타입을 설명할 때만 인스턴스를 사용할 것이다.
객체와 그 필드를 보여주는 상태 다이아그램을 '객체 다이어그램(object diagram)'이라고 한다.

구조체는 불변이다 (Structs are Immutable)
.
표기법을 사용하면 필드의 값을 가져올 수 있다.
1 |
|
표현식 p.x
는 '객체 p
를 참고(reference)하여 x
의 값을 가져와라'라는 의미이다. 예를 들어, x
라는 이름으로 변수에 값을 할당해보자. 그래도 변수x
와 필드x
는 충돌하지 않는다.
또한 어떤 표현식에서는 점 표기법을 사용할 수 있다. 예시는 아래와 같다.
1 |
|
그러나 구조체는 기본적으로 변경할 수 없으며, 생성(construction) 후에는 필드는 값이 변경될 수 없다.
1 |
|
이런 구조체의 불변성은 몇 가지의 장점을 가지고 있다.
- 더 효율적일 수 있다.
- 데이터 타입 생성자가 제공하는 불변값을 위반하는 것은 불가능하다.
- 불변 객체를 사용하는 코드는 추론하기가 더 쉽다.
변경가능한 구조체 (Mutable Structs)
필요한 경우, '변경가능한 복합 데이터 타입(mutable composite types)'은 키워드 mutable struct
로 정의할 수 있다. 변경가능한 점(point) 정의는 아래와 같다.
1 |
|
또한 점 표기법을 사용하여 변경가능한 구조체의 인스턴스에 값을 할당할 수 있다.
1 |
|
직사각형 (Rectangles)
대부분은 객체의 필드를 어떻게 설정해야 할지 명확하지만, 어떤 경우에는 개발자가 결정을 내려야 한다. 예를 들어 직사각형을 나타내는 데이터 타입을 디자인한다고 가정해보자. 직사각형의 사이즈와 위치를 구체화하기 위해서 어떤 필드를 사용해야 하는가? 간단하게 생각하기 위해서 각도는 무시하고 수직이나 수평 단위로 생각하자.
그러면 적어도 두 가지의 가능성이 있다.
- 직사각형의 한 모서리 또는 가운데 좌표, 너비와 높이를 지정할 수 있다.
- 두 개의 반대쪽 모서리 좌표를 지정할 수 있다.
위 두 개의 방안 중에 무엇이 더 나은지 판단하기는 어렵기 때문에 첫 번째 방법을 예로 하여 설명할 것이다.
1 |
|
독스트링(docstring)은 필드 목록을 알려준다. 높이와 넓이는 숫자로 작성하고, 모서리의 경우는 왼쪽 아래 모서리를 지정하는 Point
객체이다.
직사각형을 나타내기 위해서는 Rectangle
객체를 인스턴스화해야 한다.
1 |
|
객체 다이어그램은 이 객체의 상태를 그림으로 보여준다. 그림을 보면 다른 객체의 필드인 객체가 포함되었다. 그 이유는 corner
의 속성이 변경가능한 객체라서 Rectangle
외부에서 작성할 수 있기 때문이다.

인수로서 인스턴스 (Instances as Arguments)
일반적인 방법으로 인스턴스를 인수로 전달할 수 있다. 예를 들면 아래의 코드와 같다.
1 |
|
printpoint()
는 Point
를 인수로 사용하여 수학 표기법으로 반환한다. 이를 호출하기 위해 p
를 인수로 전달하였다.
1 |
|
만약 변경가능한 구조체 객체가 함수에 인수로 전달되면, 함수는 객체의 필드를 수정할 수 있다. 예를 들어 movepoint!()
는 변경가능한 Point
와 두 개의 숫자인 dx
, dy
을 인수로 가져가서 Point
의 x
와 y
의 속성에 각각 숫자를 추가한다.
1 |
|
아래의 결과는 필드가 수정되는 것을 보여준다.
1 |
|
함수 내부에서는 p
가 origin
의 에일리언스이므로, 함수가 p
를 수정하면 origin
이 변경된다.
만약 변경불가능한 Point
객체를 movepoint!()
에 입력하면 오류가 발생한다.
1 |
|
그러나 변경불가능한 객체의 변경가능한 속성 값은 수정할 수 있다. 예를 들어 moverectangle!()
은 인수로 Rectangle
객체와 두 개의 숫자인 dx
와 dy
를 가져간 후 movepoint!()
를 사용하여 직사각형의 모서리를 움직인다.
1 |
|
이제는 movepoint!
안의 p
가 rect.corner
의 에일리언스이므로, p
를 수정하면 rect.corner
도 변경된다.
1 |
|
WARNING 변경불가능한 객체의 변경가능한 속성도 재할당할 수는 없다.
1
2julia> box.corner = MPoint(1.0, 2.0)
ERROR: setfield! immutable struct of type Rectangle cannot be changed
반환 값으로서 인스턴스 (Instances as Return Values)
함수는 인스턴스를 반환할 수 있다. 예를 들어, findcenter()
은 Rectangle
을 인수로 받아서 직사각형 중심의 좌표를 포함하는 Point
를 반환한다.
1 |
|
표현식 rect.corner.x
은 "객체 rect
을 참고하여 필드 corner
를 선택한 후, 해당 객체의 x
필드를 가져오라"는 의미이다.
다음은 box
을 인수로 전달하고 Point
를 center
로 할당하는 예이다.
1 |
|
복사 (Copying)
에일리어싱은 한 곳에서 변경하면 다른 곳에 예기치 못한 영향을 주기 때문에 프로그램을 읽기 어렵게 만든다. 주어진 객체를 참조하는 모든 변수를 추적하기는 어렵다.
객체를 복사하는 것은 에일리어싱을 대신한다. 줄리아는 객체를 복제할 수 있는 deepcopy()
를 제공한다.
1 |
|
≡
연산자는 우리가 예상했던 대로 p1
과 p2
가 같은 객체가 아니라는 것을 보여준다. 하지만 왜 ==
연산자는 두 객체가 같은 값임에도 불구하고 false
를 반환하는가? 그 이유는 변경가능한 객체에서 ==
연산자는 ===
연산자와 동일하게 작동하기 때문이다. 즉, 객체의 동등성(equivalence)이 아닌 동일성(identity)을 비교한다. 이것은 줄리아가 변경가능한 복합 데이터 타입에서 무엇을 동등성으로 고려해야 하는지 모른다는 것을 의미한다.
디버깅
객체 작업을 시작하면 몇 가지 새로운 예외가 발생할 수 있다. 존재하지 않는 필드에 접근하려고 하면 다음과 같은 결과를 얻는다.
1 |
|
만약 객체의 데이터 타입이 무엇인지 확실하지 않다면, 물어볼 수 있다.
1 |
|
또한 isa
를 사용하여 객체가 데이터 타입의 인스턴스인지 확인할 수 있다.
1 |
|
만약 객체가 특정 속성을 가지고 있는지 확신할 수 없다면, 내장 함수인 fieldnames()
를 사용할 수 있다.
1 |
|
또는 isdefined()
도 사용가능하다.
1 |
|
첫 번째 인수는 어떤 객체든 들어갈 수 있다. 두 번째 인수에는 :
기호를 넣고 뒤에 필드의 이름을 작성하면 된다.