반응형

구조체 

  • 하나 이상의 변수를 묶어서 새로운 자료형을 정의하는 커스텀 데이터 타입
  • 필드들의 집합체이자, 컨테이너 = 정보의 집합
  • 다른 언어의 구조체와 같이, 내부에 변수를 가지고 있으며, 접근제한 가능, method를 가짐 
  • Go언어에는 class 키워드가 없어 구조체를 사용하여 클래스를 정의한다. 
    • Go 언어는 객체 지향을 따르기 때문에 java의 객체지향은 class, 상속 등의 개념이 존재하지 않으나 Go언어에서는 존재 
    • 객체지향의 class가 field 와 method를 함께 갖는 것과 다르게, Go언어의 구조체는 fIled만 가지고 method는 별도로 분리
  • Go언어의 구조체 vs 클래스의 차이는 인스턴스 만들어 넘길 때 
    • 구조체는 값을 복사해서 넘김
    • 클래스는 그 값이 담긴 인스턴스의 주소를 넘김 

 

** 인스턴스란?

설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체 

>> 객체를 소프트웨어에 실체화 하면 그것을 인스턴스라 부르고, 실체화된 인스턴스는 메모리에 할당

Oop의 관점에서 객체가 메모리에 할당되어 실제 사용될 때 인스턴스라 부른다. 

  • 객체는 클래스의 인스턴스 
  • 객체 간의 링크는 클래스 간의 연관 관계의 인스턴스 

 

구조체 문법

구조체 선언 → string 변수 name을 가지는 구조체를 Dog이라 한다. 

type Dog struct {
  name string
}

구조체 메소드 선언 → Go언어에서의 접근 제한은 패지키 밖에서 해당 패키지를 사용할 때 접근 제한 / 변수 및 메소드는 소문자로 시작하면 패키지 내에서만 사용하며, 대문자면 패키지 외부에서도 사용할 수 있음 

func (d Dog) Sounds(){
  Fmt.Printf("%s : wal\n", d.name)
}

구조체와 Class

인스턴스를 만들 때 구조체는 struct 그대로, 클래스는 struct 를 가리키는 포인터 생성 

package main

import "fmt"

type Dog struct{
   name string
}

type Cat struct{
  name string
}

func NewDog(name string){
  var d Dog 
  d.name = name
  return d
} // 구조체 

func (d Dog) Sounds(){
   Fmt.Printf("%s : wal\n", d.name)
}

func NewCat(name string) *Cat{
  var c Cat
  c.name = name
  return &c
} // 클래스 -> struct를 가리키는 포인터를 생성하고 주소값을 불러오면 된다


func (c *Cat) Sounds(){
  fmt.Printf("%s : yaong\n", c.name)
}

Func main(){
  d1 := NewDog("Baduk")
  d2 := d1
  d2.name = "Nureong"
  d1.Sounds()
  d2.Sounds() c1 := NewCat("Happy")
  c2 := c1
  c2.name = "Merry"
  c1.Sounds()
  c2.Sounds()

}


>> 출력문
Baduk: wal
Nureong: wal
Merry: yaong.
Merry: yaong.

** 서로 연관성이 적은 문법 요소(구조체, 포인터)를 합성하여 새로운 문법을 만들어내는 성질을 직교성이라하는데

언어의 직교성이 높을 수록 문법이 단순해지며 유연해진다. 직교성을 높여 문법을 유연해지게 하는 것이 Go언어의 설계 목표와 부합

 

 

구조체 종류 

중첩 구조체

  • 내장 타입 방식 → 일반 구조체 선언 방식과 같음 / 구조체 필드에 다른 구조체 타입의 필드 선언 / 중첩 구조체의 필드에 접근하기 위해 (구조체).(중첩구조체).(중첩구조체필드명)으로 접근함
package main

import "fmt"

tyue User struct{
  name string
  id string
  age int
}

type VipUser struct{
  UserInfo User // 내장 타입
  VipLevel int
  Price int
}

func main(){
    user1 := User("js", "oh_exp", 29)
    user1_vip := VipUser(user1, 1, 1000)
     
    user2_vip := VipUser{
            User("test", "abc",24),
            1,
            10000}

    fmt.Println(user1_vip.Userinfo.ID)
    fmt.Println(user2_vip.Userinfo.Age)
}
 

포함된 필드 방식

  • 구조체에서 다른 구조체를 포함할때 필드 명을 생략하면 .을 이용하여 한번에 접근 가능하다 / (구조체).(중첩구조체 필드명)으로 바로 접근
package main

import "fmt"

tyue User struct{
  name string
  id string
  age int
}

type VipUser struct{
  User // 포함된 필드 방식
  VipLevel int
  Price int
}

func main(){
    user1 := User("js", "oh_exp", 29)
    user1_vip := VipUser(user1, 1, 1000)
     
    user2_vip := VipUser{
            User("test", "abc",24),
            1,
            10000}

    fmt.Println(user1_vip.ID)
    fmt.Println(user2_vip.)
}
  • 그러나 중복되는 필드가 있을 시는 겹치는 필드가 어느 타입에 해당하는 구조체 필드인지 명시해줘야함
package main

import "fmt"

type User struct {
    Name  string
    ID    string
    Age   int
    Level int // 중첩
}

type VIPUser struct {
    User
    Level int /// 중첩
    Price int
}

func main() {
    user1 := User{"Hoplin", "hoplin1234", 24, 30}
    user1_vip := VIPUser{user1, 1, 10000}
    fmt.Println(user1_vip.Level) // VIPUser구조체의 Level필드
    fmt.Println(user1_vip.User.Level) // VIPUser구조체의 중첩된 User타입의 Level필드
}

메소드

구조체를 클래스처럼 사용한다면, 구조체 밖에서 기능을 표현한 것이 메소드 

구조체 밖에 메소드를 정의할때는 리시버(메소드가 속하는 타입 알려주는 기법)라는 기능을 사용하여 정의

**통상적인 OOP 에서 클래스는 메소드를 멤버로 속성을 표현하는 필드(data)와 기능을 표현하는 메소드(function)을 가진다. / /이중 메소드는 특정 작업을 수행하기 위한 명령문 집합

 

별칭 리시버 타입

타입에 대한 별칭을 지정 /  기존 타입에 대해서도 별칭을 지정할 수 있고, 사용자가 정의한 구조체 타입으로 별칭 지을 수 있음 

 

package main

import "fmt"

type MyInt int // int 타입 별칭

type Info struct {
Name string
age int
} // 구조체

type MyStruct Info //사용자 정의 구조체 타입 별칭

func main() {
var a MyInt = 20

var b int = 10

//MyInt 가 int의 별칭이지만, 별칭 타입이기 때문에 다른 타입으로 인식
fmt.Println(int(a) + b)

var e MyStruct = MyStruct{
"jisu",
29,
}
fmt.Print(e.Name)
} }

 

메소드 선언하기

메소드 선언하기 위해서 리시버 사용 / 리시버는 func 키워드 , 함수 이름 사이 명시

type testStruct struct {
    width int
    height int
}

func (r testStruct) info() int{
    return r.width * r.height
}// 리시버
// 해당 리시버를 통해 testStruct 타입에 속함

 

위를 python으로 바꾸면?

패키지 내에서 선언된 구조체, 별칭 타입들이 리시버가 될 수 있음.

class testStruct(object):
    # 초기화 메서드  (클래스의 객체가 만들어질때 자동으로 호출되어 그 객체가 갖게 될 여러 가지 성질을 정해줌)
    def __init__(self, width : int = 10, height : int = 20) -> None:
        self.width = width
        self.height = height

    def info(self) -> int:
            return self.width * self.height

# 직접 실행시켰을 때만 실행되기를 원하는 코드들을 넣어줌
if __name__ == "__main__":
    t = testStruct(20,20)
    print(t.info())

 

구조체 메소드 선언

package main

import "fmt"

type account1 struct {
    balance int
}

func (a *account1) withdrawPointer(amount int) {
    a.balance -= amount
    // *account 타입에 속한 메소드 / *account 타입의 인스턴스(구조체를 생성해 저장한 변수 (=데이터 실체))들은 해당 메소드를 사용할 수 있음
    // a는 account 구조체 포인터 타입의 메소드 / account 구조체의 필드에 대해 "." 연산자 통해 접근 가능
    // 메소드는 (리시버타입).(메소드)() 형태로 접근
    // 각 리시버 타입의 함수라는 응집성이 있음
}

func withdrawFunc(a *account1, amount int) {
    a.balance -= amount
    // void 반환 타입의 함수
    // 구조체 포인터의 매개변수를 받아 해당 구조체의 데이터를 변경하는 함수
    // (함수명)() 형태로 호출
    // 함수는 응집성이 없음
}

func main() {
    a := &account1{100} // account1 타입의 인스턴스 생성
    withdrawFunc(a, 30)

    a.withdrawPointer(10)
    fmt.Println(a.balance)
}

 

 

별칭 리시버 타입

별칭타입도 리시버가 될 수 있음 

package main

import "fmt"

type MyInt int

func (a MyInt) add(b int) int {
    return int(a) + b
}

func main() {
    var a MyInt = 10
    fmt.Println(a.add(20))
}

 

 

포인터 메소드 vs 값 타입 메소드 

  • 포인터 메소드 : 포인터 메소드 호출 시 호출한 구조체의 메모리 주소가 복사 / 복사된 주소에 있는 구조체에 대해 메소드 연산 수행
  • 값 타입 메소드 : 호출한 구조체의 필드가 새로운 구조체 필드로 복사 / 값 타입 메소드를 호출한 구조체 인스턴스와 메소드 내에서 사용하는 구조체 인스턴스는 서로 다른 구조체 
    (그래서 아래 A.ValueMethod(20) fmt.Println(A.balance) // 70 가 위의 포인터 메소드 호출과 값이 같았던 이유)
  • 값 반환 타입 메소드 : 값 타임 메소드와 원리는 동일하고 거기에 구조체 반환하는 점이 추가 / 구조체 반환하면 반환된 구조체 필드 값들이 새로운 구조체 필드에 복사된 후 변수에 저장 
    (결론적으로 값이 변경된 구조체를 복사한 것이기 때문에 값이 변경되긴 함)
package main

import "fmt"

// 구조체 생성
type account struct {
    balance   int
    firstName string
    lastName  string
}

//포인터 메소드
func (a1 *account) PointMethod(amount int) {
    a1.balance -= amount
}

//값 타입 메소드
func (a2 account) ValueMethod(amount int) {
    a2.balance -= amount
}

//수정된 값을 반환하는 값 타입 메소드
func (a3 account) modifiedValueMethod(amount int) account {
    a3.balance -= amount
    return a3
}

func main() {
    var A *account = &account{100, "oh", "jisu"}
    A.PointMethod(30)
    fmt.Println(A.balance) //70

    A.ValueMethod(20)
    fmt.Println(A.balance) // 70

    var B account = A.modifiedValueMethod(20)
    fmt.Println(B.balance) // 50

    B.PointMethod(30)
    fmt.Println(B.balance) //20
    fmt.Println(A.balance) // 70
}
반응형

'Language Study > Go' 카테고리의 다른 글

[Golang]Method  (0) 2022.12.14
[Golang]Quick sort(퀵 정렬)  (0) 2022.12.13
[Golang]bubble sort(버블 정렬)  (0) 2022.12.13
[Golang]array, slice, map  (0) 2022.12.04
[Golang]변수, 상수  (0) 2022.08.21

+ Recent posts