이번 편에는 Golang의 Essencial이 될만한 내용만 추려서 예제를 만들어 보았습니다.
먼저 struct 입니다.
C#이나 Java, C/C++ 등에서 자료구조로 많이 쓰이는 자료구조이죠?
이걸 설명하려면, 왜? Struct구조가 필요한지 알고 넘어가야 합니다.
일단 변하지 않는 "상수" const 부터 살펴보죠.
이넘은 연산하거나 변수로 사용할 수 없습니다.
예전에 계산기 두들길때 1+2 = 3 이 나온다 치면 1과 2가 상수입니다. 항상 정해진 값을 가지고 있지요.
당연히 위의 소스를 실행하면 다음과 같이 출력됩니다.
위의 코드를 실행하면 다음과 같이 변수가 변경된 채로 출력됩니다.
하지만, 이것도 한계가 있었습니다. 변수 하나 하나는 재활용 할 수 있지만, 여러개의 숫자를 동시에 넣고 연산하는걸 한방에 하고 싶었습니다.
그래서 만든 개념이 배열입니다.
예를 들어, 한 팀에 5명이 있다고 치고, 5명의 점수를 숫자로 입력하면 한번에 처리하고 싶다면 변수 5개를 쓰는게 아니라, 배열 1번 선언으로 모든걸 해치울 수가 있죠.
위의 코드를 실행하면 첫번째 1이 2로 바뀌어 출력됩니다.
문제는 여기서 부터입니다.
점수와 이름을 같이 쓰고 싶습니다. 그럼, struct 개념이 없다면 이렇게 사용했겠죠?
위의 코드를 실행하면 각 배열의 첫번째 값만 출력이 됩니다.
그래서, 만든 개념이 struct 입니다.
다양한 변수 Type를 한번에 저장할 수 있지요.
위코드를 실행하면 다음과 같이 출력됩니다.
여기에, 자주쓰는건 아에 struct method로 넣어버릴 수 있습니다.
* 다음 예제는 난이도가 위의 예제에 비해 많이 상승해서 Add method 하나만 잘 익혀두면 좋겠네요.
위의 코드를 실행하면, 먼저 입력된 점수의 평균인 3을 출력하고 첫번째 학생에게 100점을 주고, 제대로 들어갔는지 확인합니다.
왜냐하면,
Scoreboard{
[]string{"a", "b", "c"},
[]int{2, 3, 4},
}
이렇게 선언해놓고는 데이터가 추가되면 어떻게 될까요? 혹은 이름은 10자리인데, 점수는 11자리..
일렇게 되면 망했죠... 망했어요...
그래서 C#이나 Java 같은 현대적인 언어들은 List로 이를 해결합니다.
Golang도 List Package가 있지만, Go 언어의 기본 패키지로 이를 해결하려면 결국 포인트를 쓰던가... 아니면 처음부터 Slice로 만듭니다.
위 코드를 실행하면
이제 마지막으로 한 맺힌 Tree 만들어 봅시다.
A는 B, C를 가집니다. B는 D, E를 가지고 C는 F를 가지고 있습니다. B를 삭제하면 이하에 있는 값이 삭제되도록 하는 겁니다.
* 단 빈공간으로 입력했던 흔적이 남아야 합니다.
위의 코드를 실행하면...
깔끔하게 실행되는 군요!
한이 서린 재귀함수 호출은 이제 마지막 단계로 빈 값이 있는 Item들을 날려버리는 겁니다.
실행하면... 깔끔하게 새로 작성해 줍니다. (포인터 드뎌 한번 썼네요...)
이번편 마지막으로 드디어 Interface입니다.
OOP에서는 대표적으로 abstract Class와 interface Class가 있습니다.
하지만, 추상클래스의 경우는 처음 설계에서 대충 껍데기만 만들고 들어가서는 상속받아 사용하고 이걸 또 상속받아 사용하고 또 상속받아 사용하고... 이러다 망하죠.
그래서 최근 추세는 interface class를 만들어 놓고 1단계만 확장하는 개념으로 사용합니다.
여기에 C#은 Vrtual method라는 개념까지 겹쳐있어서 머리아프게 하지만, 별거 없음.
이하 추상화 클래스 C#
이하는 interface입니다.
뭐.. 둘이 사실상 비슷합니다만, 메소드를 오버라이딩 하지 않아도 되기 때문에 인터페이스가 약간 유리? 그리고, 무한 상속 루프에 안걸리죠.
그래서인지 Golang은 인터페이스만 지원합니다.
뭐 암튼... 인터페이스 예제는 다음과 같습니다.
위코드는
- Dogs : Animal struct method중 Hi()를 그냥사용하고 Stay() 추가했습니다.
- Cats : Animal struct method중 Hi()를 교체했습니다.
실행결과는
아마도, Go Language의 수 많은 부분중에 기본 사용법 80% 이상은 예제로 남긴것 같네요.
그럼 다음 시간에는 웹서버 그냥 한번 띄워보겠습니다.
* 유니티 플러그인을 이걸로 대체할 수 있으면 좋겠는데 말입니다. LOL
(찾아본 결과 Go로 생성되는 .lib는 C로 Wrapper만들어서 사용할 수 있다는 이야기가 있네요...)
* 위에서 포인터에 대한 설명없이 포인터를 사용했는데, 다음의 예제로 감잡아서 그냥 사용했어요.
결과는
포인터는 그냥 주소만 가지고 있고 실재 값은 다른곳에 저장되는 이른바 "바로 가기" 폴더입니다.
해당 값을 꺼내올때는 & (레퍼런스)로 꺼내 올 수 있구요.
먼저 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
포인터는 그냥 주소만 가지고 있고 실재 값은 다른곳에 저장되는 이른바 "바로 가기" 폴더입니다.
해당 값을 꺼내올때는 & (레퍼런스)로 꺼내 올 수 있구요.