'고랭'에 해당되는 글 1건

  1. 2016/02/02 글뻥 Go 언어 강좌 2

Go 언어 강좌 2

Developer/GO 2016/02/02 17:49
이번 편에는 Golang의 Essencial이 될만한 내용만 추려서 예제를 만들어 보았습니다.
먼저 struct 입니다.
C#이나 Java, C/C++ 등에서 자료구조로 많이 쓰이는 자료구조이죠?

이걸 설명하려면, 왜? Struct구조가 필요한지 알고 넘어가야 합니다.

일단 변하지 않는 "상수" const 부터 살펴보죠.
이넘은 연산하거나 변수로 사용할 수 없습니다.
예전에 계산기 두들길때 1+2 = 3 이 나온다 치면 1과 2가 상수입니다. 항상 정해진 값을 가지고 있지요.

package main

import (
    "fmt"
)

func main() {
    const x int = 1
    x += 1
    fmt.Println(x)
}


당연히 위의 소스를 실행하면 다음과 같이 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
# command-line-arguments
.\testVal.go:10: cannot assign to x
처음 한번 할당하면 바꿀수 없습니다. 그러다보니 개발자 입장에서 한번 선언해두고 다시 불러와서 재활용할 수 있는 개념이 필요했는데, 이것이 바로 변수입니다.

package main

import (
    "fmt"
)

func main() {
    var x int = 1
    x += 1
    fmt.Println(x)
}


위의 코드를 실행하면 다음과 같이 변수가 변경된 채로 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
2

하지만, 이것도 한계가 있었습니다. 변수 하나 하나는 재활용 할 수 있지만, 여러개의 숫자를 동시에 넣고 연산하는걸 한방에 하고 싶었습니다.
그래서 만든 개념이 배열입니다.

예를 들어, 한 팀에 5명이 있다고 치고, 5명의 점수를 숫자로 입력하면 한번에 처리하고 싶다면 변수 5개를 쓰는게 아니라, 배열 1번 선언으로 모든걸 해치울 수가 있죠.

package main

import (
    "fmt"
)

func main() {
    var x []int = []int{1, 2, 3, 4, 5}
    x[0] += 1
    fmt.Println(x[0])
}

위의 코드를 실행하면 첫번째 1이 2로 바뀌어 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
2

문제는 여기서 부터입니다.
점수와 이름을 같이 쓰고 싶습니다. 그럼, struct 개념이 없다면 이렇게 사용했겠죠?

package main

import (
    "fmt"
)

func main() {
    var x []string = []string{"a", "b", "c", "e", "f"}
    var y []int = []int{1, 2, 3, 4, 5}
    y[0] += 1
    fmt.Println(x[0], y[0])
}

위의 코드를 실행하면 각 배열의 첫번째 값만 출력이 됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
a 2
하지만, 역시 개발자는 줄이고 싶어합니다.
그래서, 만든 개념이 struct 입니다.
다양한 변수 Type를 한번에 저장할 수 있지요.
package main

import (
    "fmt"
)

func main() {
    s := Scoreboard{"a", 1}
    s.y += 1
    fmt.Println(s.x, s.y)
}

type Scoreboard struct { //대문자로 쓰면 public입니다. 소문자면 private
    x string
    y int
}


위코드를 실행하면 다음과 같이 출력됩니다.
D:\DevelopSource\Go Lang>go run testVal.go
a 2
짜잔..

여기에, 자주쓰는건 아에 struct method로 넣어버릴 수 있습니다. 
* 다음 예제는 난이도가 위의 예제에 비해 많이 상승해서 Add method 하나만 잘 익혀두면 좋겠네요.

package main

import (
    "fmt"
)

func main() {

    s := Scoreboard{
        []string{"a", "b", "c"},
        []int{2, 3, 4},
    }
    fmt.Println(s.Avg())
    fmt.Println(s.Add(0, 100))
    fmt.Println(s.y[0])
}

type Scoreboard struct {
    x []string
    y []int
}

func (s Scoreboard) Add(num int, sco int) int {
    s.y[num] += sco
    return s.y[num]
}

func (s Scoreboard) Avg() float32 {
    var _sum int = 0
    for i := 0; i < len(s.y); i++ {
        _sum += s.y[i]
    }
    var _av float32 = float32(_sum / len(s.y))
    return _av
}


위의 코드를 실행하면, 먼저 입력된 점수의 평균인 3을 출력하고 첫번째 학생에게 100점을 주고, 제대로 들어갔는지 확인합니다.
D:\DevelopSource\Go Lang>go run testVal.go
3
102
102
하지만, 이역시 문제가 많습니다.
왜냐하면, 

Scoreboard{
        []string{"a", "b", "c"},
        []int{2, 3, 4},
    }

이렇게 선언해놓고는 데이터가 추가되면 어떻게 될까요? 혹은 이름은 10자리인데, 점수는 11자리.. 
일렇게 되면 망했죠... 망했어요...

그래서 C#이나 Java 같은 현대적인 언어들은 List로 이를 해결합니다.
Golang도 List Package가 있지만, Go 언어의 기본 패키지로 이를 해결하려면 결국 포인트를 쓰던가... 아니면 처음부터 Slice로 만듭니다.

package main

import (
    "fmt"
)

func main() {

    //아래는 좀 원시적인 방법으로 포인터 선언하고 새로 다 때려넣는 방법
    //s := []*Score{}
    //row := new(Score)
    //row.x = "a"
    //row.y = 1
    //s = append(s, row)

    //조금 아는척하면서 Slice로 init하고 Slice append로 데이터 넣는 방법
    Scoreboard := Scoreboard{
        {x: "a", y: 1},
        {x: "b", y: 2},
        {x: "c", y: 3},
    }
    Scoreboard = append(Scoreboard, Score{x: "d", y: 4})
    fmt.Println(Scoreboard[3].x, Scoreboard[3].y)
}

type Score struct {
    x string
    y int
}

type Scoreboard []Score


위 코드를 실행하면 
D:\DevelopSource\Go Lang>go run testVal.go
d 4
가 출력됩니다. =)

이제 마지막으로 한 맺힌 Tree 만들어 봅시다.

A는 B, C를 가집니다. B는 D, E를 가지고 C는 F를 가지고 있습니다. B를 삭제하면 이하에 있는 값이 삭제되도록 하는 겁니다.
* 단 빈공간으로 입력했던 흔적이 남아야 합니다.
package main

import (
    "fmt"
)

func main() {
    ScoreTree := ScoreTree{
         {Parent: "", Value: "A"},
          {Parent: "A", Value: "B"},
          {Parent: "A", Value: "C"},
          {Parent: "B", Value: "D"},
          {Parent: "B", Value: "E"},
          {Parent: "C", Value: "F"},
    }

     Remove(ScoreTree, "B")

     for key, val := range ScoreTree {
         fmt.Println("K:", key, "P:", val.Parent, "V:", val.Value)
     }
}

func Remove(obj ScoreTree, _x string) {
     fmt.Println("Remove Letter:", _x)

     for key, val := range obj {
         if val.Value == _x {
             obj[key].Value = ""
         }
         if val.Parent == _x {
             obj[key].Parent = ""
             Remove(obj, obj[key].Value)
         }
     }
}

type Score struct {
     Parent string
     Value string
}

type ScoreTree []Score


위의 코드를 실행하면...
D:\DevelopSource\Go Lang>go run testVal.go
Remove Letter: B
Remove Letter: D
Remove Letter: E
K: 0 P: V: A
K: 1 P: A V:
K: 2 P: A V: C
K: 3 P: V:
K: 4 P: V:
K: 5 P: C V: F

깔끔하게 실행되는 군요! 

한이 서린 재귀함수 호출은 이제 마지막 단계로 빈 값이 있는 Item들을 날려버리는 겁니다.

package main

import (
    "fmt"
)

func main() {
     ScoreTree := ScoreTree{
         {Parent: "", Value: "A"},
         {Parent: "A", Value: "B"},
         {Parent: "A", Value: "C"},
         {Parent: "B", Value: "D"},
         {Parent: "B", Value: "E"},
         {Parent: "C", Value: "F"},
     }

     ScoreTree.Remove(ScoreTree, "B")
     ScoreTree.Trunc()
     for key, val := range ScoreTree {
         fmt.Println("K:", key, "P:", val.Parent, "V:", val.Value)
     }
}

func (s ScoreTree) Remove(obj ScoreTree, _x string) {
     for key, val := range obj {
         if val.Value == _x {
             obj[key].Value = ""
         }
         if val.Parent == _x {
             obj[key].Parent = ""
             s.Remove(obj, obj[key].Value)
         }
     }
}

func (s *ScoreTree) Trunc() {
     var r ScoreTree
     for _, val := range *s {
         if val.Value != "" {
             r = append(r, val)
         }
    }
     *s = r
}

type Score struct {
     Parent string
     Value string
}

type ScoreTree []Score

실행하면... 깔끔하게 새로 작성해 줍니다. (포인터 드뎌 한번 썼네요...)
D:\DevelopSource\Go Lang>go run testVal.go
K: 0 P: V: A
K: 1 P: A V: C
K: 2 P: C V: F

이번편 마지막으로 드디어 Interface입니다.
OOP에서는 대표적으로 abstract Class와 interface Class가 있습니다.
하지만, 추상클래스의 경우는 처음 설계에서 대충 껍데기만 만들고 들어가서는 상속받아 사용하고 이걸 또 상속받아 사용하고 또 상속받아 사용하고... 이러다 망하죠.
그래서 최근 추세는 interface class를 만들어 놓고 1단계만 확장하는 개념으로 사용합니다.
여기에 C#은 Vrtual method라는 개념까지 겹쳐있어서 머리아프게 하지만, 별거 없음.

public class 짐승
{
 public virtual void 짖기()
 {
 Console.WriteLine("없어");
 }
}
 
public class 강아지 : 짐승
{
 public override void 짖기()
 {
 Console.WriteLine("으르릉!");
 }
}
 
//이하 사용법
//강아지 강쥐 = new 강아지();
//강쥐.짖기();


이하 추상화 클래스 C#
public abstract class 짐승
{
 public abstract void 짖기();
}
 
public class 강아지 : 짐승
{
 public override void 짖기()
 {
 Console.WriteLine("멍멍멍멍");
 }
}
 
//사용
//강아지 강쥐 = new 강아지();
//강쥐.짖기();


이하는 interface입니다.
 public interface 짐승
 {
 void 짖기();
 }

 class 강아지 : 짐승
 {
 private string _이름;
 public void 짖기()
 {
 Console.WriteLine(이름 + ":멍멍!");
 }
 public string 이름
 {
 get
 {
 return _이름;
 }
 set
 {
_이름 = value;
 }
 }
 }

//사용
//강아지 강쥐 = new 강아지();
//강쥐.이름 = "멍멍이";
//강쥐.짖기(); 


뭐.. 둘이 사실상 비슷합니다만, 메소드를 오버라이딩 하지 않아도 되기 때문에 인터페이스가 약간 유리? 그리고, 무한 상속 루프에 안걸리죠.
그래서인지 Golang은 인터페이스만 지원합니다.

뭐 암튼... 인터페이스 예제는 다음과 같습니다.

package main

import (
    "fmt"
)

func main() {
     fmt.Println("=============")
     maru := Dog{Animal{"Maru", 5, 3, 1}, "LA"}
     var m Dogs = maru
     m.Hi()
     m.Stay()
     fmt.Println("=============")
     ruru := Cat{Animal{"Ruru", 2, 5, 10}, "Seoul"}
     var c Cats = ruru
     c.Hi()
}

type Animal struct {
     name string
     age int
     speed int
     attack int
}

func (a Animal) Hi() {
     fmt.Println("Im Animal as ", a.name)
}

type Dog struct {
     Animal
     home string
}

func (d Dog) Stay() {
     fmt.Println("I stay at ", d.home)
}

type Cat struct {
     Animal
     home string
}

func (c Cat) Hi() {
     fmt.Println("Ningan! Give me food!")
}

type Dogs interface {
     Hi()
     Stay()
}

type Cats interface {
     Hi()
}


위코드는
- Dogs : Animal struct method중 Hi()를 그냥사용하고 Stay() 추가했습니다.
- Cats : Animal struct method중 Hi()를 교체했습니다.

실행결과는 
D:\DevelopSource\Go Lang>go run testVal.go
=============
Im Animal as Maru
I stay at LA
=============
Ningan! Give me food!

아마도, Go Language의 수 많은 부분중에 기본 사용법 80% 이상은 예제로 남긴것 같네요.
그럼 다음 시간에는 웹서버 그냥 한번 띄워보겠습니다.

* 유니티 플러그인을 이걸로 대체할 수 있으면 좋겠는데 말입니다. LOL
(찾아본 결과 Go로 생성되는 .lib는 C로 Wrapper만들어서 사용할 수 있다는 이야기가 있네요...)

* 위에서 포인터에 대한 설명없이 포인터를 사용했는데, 다음의 예제로 감잡아서 그냥 사용했어요.

=================
package main

import (
    "fmt"
)

func main() {
    a := 43 //var a int = 43;
    fmt.Println("Value of a : ", a)
    fmt.Println("Reference of a : ", &a)

    b := &a //var b *int = &a;
    fmt.Println("Address of a : ", b)
    fmt.Println("Value of a : ", *b)
    fmt.Println("Reference of a : ", &b)

    *b = 32
    fmt.Println("Value of a by *b handle : ", a)
}


결과는 
D:\DevelopSource\Go Lang>go run Pointer.go
Value of a : 43
Reference of a : 0xc0820022b0
Address of a : 0xc0820022b0
Value of a : 43
Reference of a : 0xc082024028
Value of a by *b handle : 32

포인터는 그냥 주소만 가지고 있고 실재 값은 다른곳에 저장되는 이른바 "바로 가기" 폴더입니다.
해당 값을 꺼내올때는 & (레퍼런스)로 꺼내 올 수 있구요.

2016/02/02 17:49 2016/02/02 17:49