개발일기

Go - 구조체 선언 및 사용법 본문

프로그래밍 언어/Go

Go - 구조체 선언 및 사용법

Flashback 2022. 8. 27. 22:37
728x90
반응형

구조체서로 다른 타입의 필드 또는 동일한 타입을 가진 필드들을 하나로 묶은 것을 뜻한다.

 

1. 구조체 정의

type fruitStruct struct {
	name string
	price int
}

/*
type 구조체명 struct {
	[구조체 변수명1] [구조체 변수 타입1]
	[구조체 변수명2] [구조체 변수 타입2]
}
*/

위와 같은 fruitStruct라는 구조체 변수를 정의하고 해당 구조체 안에 name(이름)과 price(가격)의 각각 다른 자료형을 가진 필드들을 포함하고 있다.

 

2. 구조체 선언

pacakge main

import "fmt"

type fruitStruct struct {
    name string
    price int
}

func main() {
    var fruit fruitStruct
    fmt.Println(fruit)
}

/*
    실행 결과 : 
    { 0}
*/

일반 변수를 선언할 때와 동일하게 함수 안에 위와 같은 형식으로 구조체를 선언하면 된다.

구조체 필드에 값들을 입력하지 않은 상태로 선언할 경우, 구조체 필드들의 초기값은 해당 자료형의 기본값으로 초기화된다. 예를 들어 위와 같이 선언한 경우, {0 }으로 fruit 구조체가 초기화된다. name은 문자형, price는 정수형이므로 각 자료형의 초기값인 공백과 0으로 설정된다.

 

2-1. 구조체 선언 및 필드값 추가

pacakge main

import "fmt"

type fruitStruct struct {
    name string
    price int
}

func main() {
    var fruit fruitStruct = fruitStruct{"mango", 1000}
    // fruit := fruitStruct{"mango", 1000} var 키워드 생략
    fmt.Println(fruit)
    
    fruit2 := fruitStruct{price: 5000, name: "melon"}
    fmt.Println(fruit2)
}

/*
    실행 결과 : 
    {mango 1000}
*/

fruit이라는 구조체 변수를 선언함과 동시에 초기값을 mango / 1000으로 할당하여 선언하였다. 구조체 함수를 출력하면 정의된 구조체에 포함된 필드의 순서에 따라 값들이 나열되어 출력된다.

fruit2변수는 필드값을 추가할 때, 필드명도 입력하여 이름과 값을 매칭시켜주었다. 필드명을 지정하여 값을 대입하는 경우는 해당 필드명에 값이 대입된다.

2-2. 구조체 포인터 선언

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
}

func main() {
    var fruit *fruitStruct
    fmt.Println(&fruit) // 주소값 출력
    
    fruit1 := new(fruitStruct)
    fmt.Println(&fruit1) // 주소값 출력
}

/*
    실행 결과 : 
    0xc0000ba018
    0xc0000ba028
*/

일반 변수를 선언할 때와 동일하게 타입 선언 부분에 *를 추가하면 구조체 포인터로 선언할 수 있다. 또한 &를 구조체 변수 앞에 추가하면 출력하면 해당 구조체의 주소값을 확인할 수 있다.

 

2-3. 구조체 필드에 값 추가

구조체를 선언함과 동시에 필드값을 추가하는 방법도 있지만, 구조체를 먼저 선언 한 후, 추후에 필드값을 추가하는 방법도 존재한다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
}

func main() {
    var fruit fruitStruct
    fruit.name = "blueberry"
    fruit.price = 3000
    
    fmt.Println(fruit) // {blueberry 3000}
    fmt.Println("과일명 : ", fruit.name) // 과일명 : blueberry
    fmt.Println("과일 가격 : ", fruit.price) // 과일 가격 : 3000
}

/*
    실행 결과 : 
    {blueberry 3000}
    과일명 : blueberry
    과일 가격 : 3000
*/

구조체의 필드에 접근할 때는, .(점)으로 연결하여 접근할 수 있다.

 

2-4. 구조체 new 함수

new함수를 사용하여 구조체를 생성할 수 있다. new 함수를 사용할 경우에는 선언과 동시에 필드값은 할당할 수 없다. new함수로 생성된 구조체는 주소값만 할당받게 된다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
}

func main() {
    fruit := new(fruitStruct)
    fmt.Println(fruit)
    fmt.Prinltn(&fruit)
}

/*
    실행 결과 : 
    &{ 0}
    0xc00000e028
*/

구조체 포인터일 경우, 값을 출력하면 앞에 &가 같이 출력된다. &의 의미는 해당 변수가 구조체 포인터라는 것을 나타내는 의미이다.

 

3. 함수에서의 구조체 사용

main에서 구조체를 선언 한 후, 다른 함수에서 해당 구조체를 사용하고자 할 경우, 구조체 포인터로 매개변수를 지정하여 넘겨주면 해당 구조체를 사용할 수 있다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
    count int
}

func addCount(fruit *fruitStruct) {
    fruit.count = 5
}

func resetCount(fruit fruitStruct) {
    fruit.count = 0
}

func main() {
    fruit := fruitStruct{name: "mango", price: 1000} 
    addCount(&fruit) // 주소값
    fmt.Println("after addCount : ", fruit)

    resetCount(fruit)
    fmt.Println("after resetCount : ", fruit)
}

/*
    실행 결과 : 
    after addCount :  {mango 1000 5}
    after resetCount :  {mango 1000 5}
*/

구조체를 매개변수로 하여 해당 구조체의 값을 변경하고자 한다면 주소값을 넘겨주어야 한다.

addCount는 과일 수량을 추가하는 함수로 fruit 구조체 변수의 주소값을 넘겨주었다. 이로인해 main함수에서 해당 구조체를 출력했을 때, 수량이 5개로 나오는 것을 확인할 수 있다.

resetCount는 과일 수량을 초기화하는 함수다. 과일 수량을 0으로 변경하기 위해 해당 함수를 호출하였지만 과일 수량은 변경되지 않았다. 주소값을 넘겨주지 않았기 때문에 과일의 수량은 그대로 5이다.

 

4. 구조체의 리시버 함수

선언된 구조체에 리시버 함수를 연결하여 해당 메서드를 호출할 수 있다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
    count int
}

// addFruitInfo라는 함수명 앞에 리시버 변수를 괄호안에 추가한다.
func (fs *fruitStruct) addFruitInfo() {
    fs.name = "cherry"
    fs.price = 1000
    fs.count = 30
    // 리서버 변수를 통해 구조체의 필드에 접근할 수 있다.
}

func main() {
    var fruit fruitStruct
    fruit.addFruitInfo()
    fmt.Println(fruit)
}

/*
    실행 결과 : 
    {cherry 1000 30}
*/

리시버 함수를 생성할 때, 해당 함수에 전달할 구조체를 리시버 변수라 한다. 리시버 변수는 함수명 앞 괄호 안에 넣어주면 된다. 만약 포인터로 넘겨줄 경우, addFruitInfo 메서드에서 변경된 구조체값을 main함수에서도 확인할 수 있다. 포인터로 넘기지 않은 경우, 구조체의 변경된 값이 main함수에서는 적용되지 않은채로 출력된다.

 

4-1. 구조체 리시버 함수의 반환값

일반 함수의 반환값을 정의할 때와 마찬가지로 리시버 함수에서 반환값을 지정할 때는 함수명 뒤에 반환타입을 입력하면 된다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
    count int
}

// addFruitInfo라는 함수명 앞에 리시버 변수를 괄호안에 추가한다.
func (fs *fruitStruct) addFruitInfo() int {
    fs.name = "cherry"
    fs.price = 1000
    fs.count = 30
    // 리서버 변수를 통해 구조체의 필드에 접근할 수 있다.
    
    return fs.count
}

func main() {
    var fruit fruitStruct
    var fruitCount int = fruit.addFruitInfo()
    fmt.Println(fruitCount)
}

/*
    실행 결과 : 
    30
*/

 

 

4-2. 리시버 변수의 생략

리시버 함수에 전달된 리시버 변수를 사용하지 않을 경우 리시버 변수 선언 부에 변수명 대신 _을 입력하면 된다.

구조체를 통해 해당 리시버 함수에 접근은 할 수 있지만, 해당 구조체의 값에는 접근할 수 없다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
    count int
}

func (_ fruitStruct) addFruitInfo() {
    fmt.Println("skip the receiver variable")
}

func main() {
    var fruit fruitStruct
    fruit.addFruitInfo()
}

/*
    실행 결과 : 
    skip the receiver variable
*/

 

5. 구조체 임베딩

구조체 안에서 다른 구조체를 정의하는 것을 구조체 임베딩이라고 한다. 구조체의 관계는 (Has-A) 즉, 포함관계로 이루어지게 된다. 과일에는 과일명과 과일 가격이라는 필드가 존재한다. 또한 과일에는 풍부한 영양분을 포함하고 있다. 즉, 과일과 영양분의 관계를 Has-A 관계로 구조체 임베딩을 하기에 올바른 예제이다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
    n nutrientStruct
    // 과일안에 영양분이!
}

// 100g 기준
type nutrientStruct struct {
    calcium string
    vitaminA string
    vitaminC string
}

func main() {
    // 임베딩된 구조체 안에 값을 추가한다.
    fruit := fruitStruct{"mango", 1000, nutrientStruct{"11mg", "54ug", "36.4mg"}}
    /* 
        임베딩 타입 선언 후(nutrientStruct) 대괄호를 추가하여
        필드값을 입력한다.
    */
    
    fmt.Println(fruit)
}

/*
    실행 결과 : 
    {mango 1000 {11mg 54ug 36.4mg}}
*/

임베딩 된 구조체에 값을 추가하고자 할 경우, 구조체 값 초기화 부분에 해당 임베딩 타입을 선언 후, 필드값을 추가하면 된다.

 

5-1. 구조체 선언 후, 임베딩 값 추가

이미 선언된 구조체에 임베딩 필드값을 추가하려면 기존 구조체에 값을 추가할 때 사용했던 .(점)으로 연결하여 해당 필드에 접근 후, 값을 추가할 수 있다.

package main

import "fmt"

type fruitStruct struct {
    name string
    price int
    n nutrientStruct
}

// 100g 기준
type nutrientStruct struct {
    calcium string
    vitaminA string
    vitaminC string
}

func main() {
    fruit := fruitStruct{name: "mango", price: 1000}
    fruit.n.calcium = "11mg"
    fruit.n.vitaminA = "54ug"
    fruit.n.vitaminC = "36.4mg"
  
    fmt.Println(fruit)
}

/*
    실행 결과 : 
    {mango 1000 {11mg 54ug 36.4mg}}
*/

 


참고 사이트 : 

https://medium.com/@adityaa803/wth-is-a-go-receiver-function-84da20653ca2

 

What the heck are Receiver functions in Golang?

Go is very similar to C due to presence of pointers, static typing and many other things. But Go is a modern language and so it has many…

medium.com

 

https://go101.org/article/type-embedding.html

 

Type Embedding -Go 101

From the article structs in Go, we know that a struct type can have many fields. Each field is composed of one field name and one field type. In fact, sometimes, a struct field can be composed of a field type only. The way to declare struct fields is calle

go101.org

 

https://dev-yakuza.posstree.com/en/golang/struct/

 

[Golang] Struct

Let’s how to define Struct and how to use Struct in Golang.

dev-yakuza.posstree.com

 

https://gobyexample.com/structs

 

Go by Example: Structs

Go’s structs are typed collections of fields. They’re useful for grouping data together to form records. package main import "fmt" This person struct type has name and age fields. type person struct { name string age int } newPerson constructs a new pe

gobyexample.com

 

728x90
반응형

'프로그래밍 언어 > Go' 카테고리의 다른 글

Go - goroutine 사용법  (0) 2022.09.03
Go - 인터페이스 선언 및 사용법  (0) 2022.08.28
Go - 포인터란 무엇인가  (0) 2022.08.14
Go - panic() / recover() 예외처리  (0) 2022.08.09
Go - defer 호출이란?  (0) 2022.08.07
Comments