Go Lang 기초 11 - 구조체(Struct)
Go Lang의 구조체(Struct)는 필드를 하나의 개념으로 묶는 것을 의미합니다.
Go Lang에는 타 언어에서 사용하는 Class와 상속을 지원하지 않습니다. 대신 메소드를 구조체에 연결하는 방식을 통해 Class의 형태를 구현합니다. 이 경우 메소드는 구조체안에서 정의되는 것이 아니라 구조체 밖에서 정의되어 사용합니다.
우선 메소드에 대해 알아보고 구조체를 공부해봅시다.
Go Lang은 객체지향 언어가 아니기 때문에 메소드가 존재합니다.
Go Lang의 메소드란 특정한 구조체에 연결되어 호출되는 함수를 의미합니다. 데이터와 코드를 묶어서 응집도를 높이기 위해 사용하는데, 응집도를 높여서 쓰는 것이 유리한 곳에 활용하기에 좋습니다. (통장 입출금 예제 등)
메소드의 형태
func (구조체변수 구조체이름) 메소드명() 리턴값 {
구조체 멤버를 접근하여 연산수행 가능
}
통장 입출금 예제를 사용해서 메소드에 대해 더 자세히 알아보도록 하겠습니다.
예제. 통장 인출 메소드 만들기
package main
import "fmt"
type Account struct {
balance int
}
// 인출 메소드
func (a *Account) withdrawMethod(amount int) {
a.balance -= amount
}
func main() {
a := Account{100}
b := &Account{200}
// 인출 메소드 사용
a.withdrawMethod(40)
b.withdrawMethod(50)
fmt.Printf("a계좌 : %d, b계좌 : %d", a.balance, b.balance)
}
출력결과물
a계좌 : 60, b계좌 : 150
출금메소드를 생성해서 a와 b 계좌에서 차감하는 예제를 간단히 만들 수 있습니다.
구조체의 형태
struct {
field1 string
field2 int
}
구조체 내부는 각각의 필드명과 필드 타입으로 구성됩니다.
예제. 구조체 만들기
package main
import "fmt"
type hero struct {
name string
age int
weapon string
}
var h hero
func main() {
h := hero{}
h.name = "유비"
h.age = 30
h.weapon = "자웅일대검"
fmt.Println(h)
}
출력결과물
{유비 30 자웅일대검}
위의 예제는 person 구조체를 선언하고 그 구조체에 name과 age를 각각 타입 선언하였습니다. person 구조체 하에 변수 p 선언하고 이를 통해 name과 age의 필드값을 설정한 결과값을 출력하였습니다.
구조체 선언 후에 별도 함수를 생성해서 연결하는 것이 가능하고 매우 쉽고 간편합니다.
예제. 함수 연결하기
package main
import "fmt"
type hero struct {
name string
age int
weapon string
}
var h hero
func main() {
h := hero{}
h.name = "유비"
h.age = 30
h.weapon = "자웅일대검"
fmt.Println(h)
hero01()
hero02()
}
func hero01() {
h.name = "관우"
h.age = 31
h.weapon = "청룡언월도"
fmt.Println(h)
}
func hero02() {
h.name = "장비"
h.age = 28
h.weapon = "장팔사모"
fmt.Println(h)
}
출력결과물
{유비 30 자웅일대검}
{관우 31 청룡언월도}
{장비 28 장팔사모}
위에서 보듯 구조체는 크기가 큰 경우도 많습니다. 이런 경우에도 함수는 전달된 인자의 복사본을 사용합니다. 필드가 많은 구조체의 경우 포인터를 통해 인자를 전달하는 것이 메모리 관리에 도움이 됩니다.
포인터 변수를 직접 출력하면 변수가 가리키는 메모리의 주소 값이 나옵니다. 포인터가 나타내는 주소값이 아닌 포인터의 값을 가져오려면 *를 사용합니다.
var i int = 12
var p *int = &i
var q *int = p
fmt.Printf("i의 주소 : %p\n", &i)
fmt.Printf("p의 주소 : %p, p의 값 : %p\n", &p, p)
fmt.Printf("q의 주소 : %p, q의 값 : %p\n", &q, q)
출력결과물
i의 주소 : 0xc0000140b0
p의 주소 : 0xc00000e028, p의 값 : 0xc0000140b0
q의 주소 : 0xc00000e030, q의 값 : 0xc0000140b0
i의 주소값이 p의 값이 되었음을 확인 가능합니다. p와 q 모두 값 자체는 동일하지만 각 포인터가 가리키는 주소값은 다름을 확인할 수 있습니다.
**이중 포인터를 활용해서 주소를 따라가라는 명령을 줄 수 있습니다.
var i int = 12
var p *int = &i
var q **int = &p
fmt.Printf("i의 주소 : %p\n", &i)
fmt.Printf("p의 주소 : %p, p의 값 : %p\n", &p, p)
fmt.Printf("q의 주소 : %p, q의 값 : %p\n", &q, q)
출력결과물
i의 주소 : 0xc0000140b0
p의 주소 : 0xc00000e028, p의 값 : 0xc0000140b0
q의 주소 : 0xc00000e030, q의 값 : 0xc00000e028
포인터를 너무 자주 사용하기보다는 규모가 큰 구조체나 변경이 필요한 구조체의 경우에 용이하게 사용하고, 슬라이스 맵 함수 등에는 내부적으로 포인터가 구현이 되어있으므로 굳이 쓰지 않아도 무방할 것입니다.
'IT > Develop' 카테고리의 다른 글
Go Lang 기초 13 - 인터페이스(interface) (0) | 2021.07.30 |
---|---|
Go Lang 기초 12 - 메소드(Method) (0) | 2021.07.28 |
Go Lang 기초 10 - 맵(Map) (0) | 2021.07.28 |
Go Lang 기초 9 - 슬라이스(Slice) (0) | 2021.07.28 |
Go Lang 기초 8 - 배열(Array) (0) | 2021.07.28 |