반응형

언어를 배울 때 가장 기본적인 것 중에 하나가 비교문(if), 반복문(for), 함수(function) 이다.

 

 

If 문

if 는 비교 구문에서 사용되는 키워드 이다. 변수 선언과 동시에 비교를 할 수 있는데 이 때 선언된 변수는 해당 if 문 block 안에서만 유효하다. 그렇기 때문에 마지막 라인은 num 변수는 인식하지 못해 에러가 난다.

func main() {
	if num := 5; num == 0 {
		fmt.Println("False")
	} else if num < 10 {
		fmt.Println("True")
	} else {
		fmt.Println("Big number")
	}
	fmt.Println(num)
}

--- output ---
./main_05_for_function.go:17:14: undefined: num

 

 

 

if 문 밖에서 선언한 변수는 if 문 안과 밖 모두에서 사용할 수 있다. 하지만 if 문 안에서 같은 변수명을 재정의 하면 해당 변수 값은 오버라이드 된다.

func main() {
	num := 10
	if num > 0 {
		fmt.Println("outer num =", num)
		num := 0
		fmt.Println("inner num =", num)
	}
	fmt.Println("outer num =", num)
}

--- output ---
outer num = 10
inner num = 0
outer num = 10

 

 

For 문

for lloop 는 반복문이며 slice 나 map 타입에 대해서 range 를 사용하여 item 을 하나씩 가져온다. slice 를 range 로 가져오면 index, value 2개의 값으로 넘어온다. value 만을 사용하고 싶으면 index 부분은 _ 로 처리하여 버릴 수 있다. 또한 index 1개의 값만 가져올 수 있다.

func main() {
	nums := []int{10, 20, 30, 40, 50}
	for i, v := range nums {
		fmt.Println("index =", i, ",", "value =", v)
	}
	for _, v := range nums {
		fmt.Println("value =", v)
	}
	for i := range nums {
		fmt.Println("index =", i)
	}
}

--- output ---
index = 0 , value = 10
index = 1 , value = 20
index = 2 , value = 30
index = 3 , value = 40
index = 4 , value = 50
value = 10
value = 20
value = 30
value = 40
value = 50
index = 0
index = 1
index = 2
index = 3
index = 4

 

 

 

map 도 slice 와 마찬가지로 range 로 가져올 수 있다. 이 때 return 되는 값은 key, value 이다.

func main() {
  m := map[string]int{
		"a": 1,
		"b": 2,
		"c": 3,
	}
	for k, v := range m {
		fmt.Println("key =", k, ",", "value =", v)
	}
	for _, v := range m {
		fmt.Println("value =", v)
	}
	for k := range m {
		fmt.Println("key =", k)
	}
}

--- output ---
key = c , value = 3
key = a , value = 1
key = b , value = 2
value = 2
value = 3
value = 1
key = a
key = b
key = c

 

 

Switch

선택문은 switch 로 가능하다. 각 case 마다 break 문이 없어도 되며, case 에는 , 로 여러 개의 값을 지정할 수 있다.

func main() {
  alpha := []string{"a", "ab", "abc", "abcd", "abcde", "abcdef", "abedefg"}
	for _, a := range alpha {
		switch l := len(a); l {
		case 1, 2:
			fmt.Println(a, "length = 2 or 3")
		case 3:
			fmt.Println(a, "length = 3")
		case 4, 5, 6:
			fmt.Println(a, "length = 4, 5 or 6")
		default:
			fmt.Println(a, "length > 6")
		}
	}
}

--- output ---
a length = 2 or 3
ab length = 2 or 3
abc length = 3
abcd length = 4, 5 or 6
abcde length = 4, 5 or 6
abcdef length = 4, 5 or 6
abedefg length > 6

 

 

Function

함수는 func, function name, parameter, return value 로 만들 수 있다. 아래 2개의 숫자를 받아서 나누는 div 함수는 아래와 같이 정의 할 수 있다. parameter 가 같은 타입이면 앞의 타입은 생략할 수 있다.

func main() {
  result := div(4, 2)
	fmt.Println(result)
}

// func div(n int, d int) int { 는 n, d 파라미터의 타입이 같아 아래와 같이 바꿀 수 있음
func div(n, d int) int {
	if d == 0 {
		return 0
	}
	return n / d
}

--- output ---
2

 

 

 

, 로 구분하여 여러 개의 아규먼트를 넘길 수 있으며 이 때 받는 함수에서는 ... 를 활용하여 가변 파라미터로 받을 수 있다. 또한 slice... 와 같이 slice 를 가변인자로 보낼 수 있다.

func main() {
  fmt.Println(addTo(1))
	fmt.Println(addTo(1, 2))
	fmt.Println(addTo(1, 2, 3))
	i := []int{4, 5}
	fmt.Println(addTo(3, i...))   // slice를 가변인자로 보내기
	fmt.Println(addTo(6, []int{7, 8, 9}...))
}

// 가변인자 (variadic parameter)
func addTo(base int, vals ...int) []int {
	result := make([]int, 0, len(vals))
	for _, v := range vals {
		result = append(result, base+v)
	}
	return result
}

--- output ---
[]
[3]
[3 4]
[7 8]
[13 14 15]

 

 

 

Function 의 리턴 값을 여러 개로 할 수 있다. (multi return values)

func main() {
  result, remainder, err := multiReturnValues(10, 5)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(result, remainder)
}

// multiple retrun values
func multiReturnValues(n, d int) (int, int, error) {
	if d == 0 {
		return 0, 0, errors.New("cannot divid by zero")
	}
	return (n / d), (n % d), nil
}

--- output ---
2 0

 

 

 

Function 은 변수에 담을 수 있다. cal 함수 내부에 보면 map 의 value 로 Function 타입을 선언하여 할당한 것을 볼 수 있다. Function 을 변수로 선언할 수 있고, 파라미터로 받을 수 있고 return value 로도 사용할 수 있다.

func main() {
  exp := []string{"3", "+", "4"}
	result, err := cal(exp)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(result)
}

func cal(exp []string) (int, error) {
	// Function Type
	type opFuncType func(int, int) int
	var opMap = map[string]opFuncType{
		"+": add,
		"-": sub,
	}

	a, err := strconv.Atoi(exp[0])
	if err != nil {
		fmt.Println(err)
		return 0, err
	}

	b, err := strconv.Atoi(exp[2])
	if err != nil {
		fmt.Println(err)
		return 0, err
	}

	op := exp[1]
	opFunc, ok := opMap[op]
	if !ok {
		fmt.Println("unsupported operator: ", op)
		return 0, err
	}
	return opFunc(a, b), nil
}

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

func sub(a, b int) int {
	return a - b
}

--- output ---
7

 

 

 

struct 와 마찬가지로 Function 도 anonymous 로 만들 수 있다.

func main() {
  for i := 0; i < 5; i++ {
		func(j int) {
			fmt.Println(j)
		}(i)
	}
}

--- output ---
0
1
2
3
4

 

 

 

Function 안에는 Function 을 중첩해서 선언할 수 있는데 이 것을 clousure (클로저)라 한다. 클로저는 앞에서 설명한 Function 을 파라미터와 리턴 값으로 사용할 수 있는 성질을 많이 이용한다.

 

클로저는 변수 값이 사라지지 않고 계속 유지되는 특성을 갖는다. 아래 multiple(2) 을 호출 할 때 생성된 파라미터 base 변수 에는 2가 할당 되고 funcA(3) 을 호출 할 때도 계속 값이 남아 있어 base * factor 의 값은 2 * 3 이 되어 6 이 출려된다.

func main() {
  funcA := multiple(2)
	fmt.Println(funcA(3))

	funcA = multiple(4)
	fmt.Println(funcA(5))
}

// high-ooder fuctnion
func multiple(base int) func(int) int {
	return func(factor int) int {
		return base * factor
	}
}

--- output ---
6
20

 

 

 

함수를 호출할 때 모든 것은 Call by Value 이다. 아래과 같이 기본 타입이나 Struct 타입은 함수를 통해 넘겨받은 변수의 값을 변경하더라도 원래의 값은 변경되지 않는다. 즉, 아래와 같이 person 을 modify 함수를 통해 넘긴 다음, modify 함수에서 값을 수정해도 원래의 person 의 값은 수정되지 않는다.

func main() {
  i := 1
	s := "Hello"
	p := person{}
	modify(i, s, p)
	fmt.Println(i, s, p)
}

type person struct {
	name string
	age  int
}

// Call by Value (여기서 수정해도 원래 변수 값은 수정되지 않는다)
func modify(i int, s string, p person) {
	i = i + 1
	s = "modified"
	p.name = "ask"
	p.age = 100
}

--- output ---
1 Hello { 0}

 

 

 

그러나 포인터로 넘기면 원래의 값도 수정할 수 있다. 포인터는 다음에 설명하기로 하고 여기서는 slice 와 map 이 포인터와 같이 작동한다고 이해하면 된다.

 

한가지 중요한 것이 있다. modSlice(s) 에서의 s 변수 가 가리키는 주소와 modSlice(s []int) 함수에서 s 변수 가 가리키는 주소는 동일하다. (마치 포인터 처럼 동작한다) 하지만 s 변수 의 경우 modSlice 함수 통해 넘기는데 처음 생성할 때 길이:2, 크기:2 로 생성했다. 즉, 길이와 크기가 같아 modSlice 함수에서 append 로 item 을 추가할 경우 크기를 늘린 새로운 slice 를 만들어서 리턴 된다.

 

s = append(s, 3) 에서 s 가 가리키는 주소는 처음 modSlice(s []int) 일 때 가리키는 주소가 아니라 크기가 늘어나 새롭게 생성된 slice 를 가리키게 된다. 그래서 그 이전에 수정된 s 의 값은 변화가 있고, 그 이후에 수정된 s 의 값은 변화가 없다.

func main() {
  m := map[string]int{
		"one": 1,
		"two": 2,
	}
	modMap(m)
	fmt.Println(m)

	s := []int{1, 2}
	modSlice(s)
	fmt.Println(s)
}

// Call by Value but slice and map like pointer
func modMap(m map[string]int) {
	m["two"] = 20
	m["three"] = 3
	delete(m, "one")
}

func modSlice(s []int) {
	s[0] = s[0] * 10
	s = append(s, 3)  // 길이 == 크기 이므로 새로운 크기의 slice 가 만들어져서 리턴된다
	s[1] = s[1] * 10
}

--- output ---
map[three:3 two:20]
[10 2]

 

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

Slice 와 Map 은 값을 여러개 자질 수 있는 타입이다. 그러나 선언한 타입과 같은 타입만 저장해야 하는 문제가 있다. 또한 class 와 같이 다양한 멤버 변수를 가질 수 없다.

 

그래서 Go 에서는 다양한 값을 가질 수 있도록 Struct 타입을 지원한다.

 

Struct

struct 의 를 만들려면 type 키워드, struct명, struct 키워드로 만든다. 그리고 그 안에 다양한 변수를 포함할 수 있으며 {} 로 묶어주면 된다.

 

type 으로 struct 를 선언했으면 그 자체가 하나의 type 이 된다. 그럼 var 로 새로운 만든 type을 활용하여 변수 선언을 할 수 있다. 아래와 같이 name 과 age 를 필드로 갖는 person 이라는 새로운 타입을 선언하고 jina 와 jamie 라는 변수를 만들 수 있다.

 

변수 선언만 한 struct 내의 필드들은 zero 값을 갖는다.

 

person 타입의 변수를 초기화 할 때는 {} 로 초기화 할 수 도 있다.

package main

import "fmt"

func main() {
	type person struct {
		name string
		age  int
	}

	var jina person
	fmt.Println(jina)

	jamie := person{}
	fmt.Println(jamie)
}

--- output ---
{ 0}
{ 0}

 

혹은 값을 지정하여 초기화 할 수 있다.

 

값을 지정하여 초기화할 때는 json 타입으로 변수:  으로 지정하거나  으로 지정할 수 있다. 변수 사이는 , 로 구분하며, 마지막 변수 끝에도 , 를 넣어야 한다.

 

Go 에서는 타입을 선언할 때는 { 앞에 공백을 넣지만, 값을 초기화할 때는 { 앞에 공백을 넣지 않는다. 또한 타입을 선언할 때는 필드 사이에 , 가 없지만, 값을 초기화 할 때는 필드값 사이에 , 를 넣는다.

jina := person{
	name: "Jina",
	age:  21,
}
fmt.Println(jina)

jamie := person{
	"Jamie",
	18,
}
fmt.Println(jamie)

--- output ---
{Jina 21}
{Jamie 18}

 

 

초기화는 아래와 같이 할 수 도 있다.

 

단, jina[”age”] 와 같이 인덱싱을 통한 접근은 지원하지 않는다.

var jina person
jina.name = "Jina"
jina.age = 21
// jina["age"] = 21  // 인덱싱은 지원하지 않음

fmt.Println(jina.name)

--- output ---
Jina

 

 

Anonymous struct

type 이 없는 struct 도 가능하다.

 

이 때는 type 키워드를 쓰지 않는다.

 

아래와 같이 바로 jina 라는 변수를 struct 형태로 지정하고 . 을 통해 값을 지정할 수 있다. 혹은 {} 를 사용하여 jamie 변수를 선언하면서 초기 값을 지정할 수 있다.

var jina struct {
		name string
		age  int
}

jina.name = "Jina"
jina.age = 21
fmt.Println(jina)

jamie := struct {
	name string
	age  int
}{
	name: "Jamie",
	age:  18,
}
fmt.Println(jamie)

--- output ---
{Jina 21}
{Jamie 18}

 

 

Struct 비교

go 에서는 type 이 같으면 대입(=), 비교(==) 를 할 수 있다. 하지만 type 이 다르면 비교나 대입을 할 수 없다.

 

하지만 type 을 convert 하여 맞춰 준다면 대입, 비교가 가능하다.

 

jina 와 jina2 는 type 이 같고 필드 값도 같다. 그래서 비교를 하면 true 값이 나온다.

 

하지만 jamie 는 jina 와 type 이 같지만 필드 값이 달라서 비교를 하면 false 값이 나온다.

jina := person{
	name: "Jina",
	age:  21,
}

jina2 := person{
	name: "Jina",
	age:  21,
}
fmt.Println(jina == jina2)

jamie := person{
	name: "Jamie",
	age:  18,
}
fmt.Println(jina == jamie)

--- output ---
true
false

 

 

 

새로운 anotherPerson type 을 만들어서 살펴보자.

 

아래와 같이 jina 는 person type 이고 jamie 는 anotherPerson type 이므로 서로 type 이 달라서 비교나 대입을 하면 에러가 난다.

type anotherPerson struct {
	name string
	age  int
}

jina := person{
	name: "Jina",
	age:  21,
}
fmt.Println(jina)

jamie := anotherPerson{
	name: "Jamie",
	age:  18,
}

fmt.Println(jina == jamie)

--- output ---
./main.go:88:19: invalid operation: jina == jamie (mismatched types person and anotherPerson)

 

 

 

이번에는 jamie 를 person type 으로 바꾸는 type converson 을 하여 비교하여 보자.

 

타입 변환도 잘 되고 값도 정상적으로 대입되는 것을 알 수 있다.

type anotherPerson struct {
	name string
	age  int
}

jina := person{
	name: "Jina",
	age:  21,
}
fmt.Println(jina)

jamie := anotherPerson{
	name: "Jamie",
	age:  18,
}

jina = person(jamie)
fmt.Println(jina)

--- output ---
{Jina 21}
{Jamie 18}

 

 

 

한가지 중요한 점은 struct 사이에 타입 변환은 타입, 필드명, 필드 순서, 필드 갯수 가 모두 동일해야 가능하다.

 

그럼 type 으로 선언된 struct 와 anonymous struct 는 타입을 제외한 필드명, 필드 순서, 필드 갯수 가 같다면 대입이나 비교, 타입 변환이 가능할까?

 

정답은 가능하다이다.

jina := person{
	name: "Jina",
	age:  21,
}
fmt.Println(jina)

var jamie struct {
	name string
	age  int
}

jamie = jina
fmt.Println(jina == jamie)

--- output ---
{Jina 21}
true

 

jamie := struct {
	name string
	age  int
}{
	name: "Jamie",
	age:  18,
}

var jina person

jina = person(jamie)
fmt.Println(jina)
fmt.Println(jina == jamie)

--- output --
{Jamie 18}
true

 

 

사실 person(jamie) 로 타입 변환을 하지 않아도 정상적으로 동작한다.

반응형
Posted by seungkyua@gmail.com
TAG go, golang, struct

댓글을 달아 주세요

반응형

 

Map 은 key, value 로 저장할 수 있는 타입이다. HashMap 을 사용하기 때문에 key 는 유일한 값으로 key 를 사용하면 value 를 바로 찾아서 쓸 수 있다.

앞서 slice 와 동일하게 zero 값은 nil 이다. 그러므로 map 변수를 선언만 하면 nil 값으로 비교할 수 있다. slice 는 nil 값이라도 append 를 사용하면 값을 추가하면서 새로운 slice 가 생성되어 return 되었다. 하지만 map 은 nil 값에는 어떠한 key, value 도 추가할 수 있다.

 

Map

map 은 key, value 쌍으로 이루어진 데이터 구조이다. 파이썬에서는 dictionary 와 비슷하다고 생각하면 된다. 단지 다른 것은 하나의 map 에는 사전에 지정된 타입의 key, value 만 저장될 수 있다.

다음은 string 타입을 key 로하고 int 를 value 로 저장하는 map 을 선언한 것이다. map[key타입]value타입 으로 선언할 수 있다.

이렇게 선언만 한 map 변수의 zero 값은 nil 이다.

var m map[string]int
fmt.Println(m == nil)

--- output ---
true

 

이렇게 nil 값이 상태의 map 에는 key, value 를 추가할 수 없다(slice 와는 다르다).

var m map[string]int
m["one"] = 0

--- output ---
panic: assignment to entry in nil map

 

아래와 같이 empty map 을 생성할 수 있으며, 여기에는 key, value 를 추가할 수 있다.

var m1 = map[string]int{}
fmt.Println(m1 == nil)
m1["one"] = 0
fmt.Println(m1)

--- output ---
false
map[one:0]

 

초기 값을 아래와 같이 생성할 수 도 있다. 초기화 할 때 key, 와 value 는 : 로 구분하고 연속적으로 key, value 를 입력하기 위해서는 , 구분자를 사용한다.

m2 := map[string]int{
    "one":  1,
    "two": 2,
}
fmt.Println(m2)

--- output ---
map[one:1 two:2]

 

make 함수

slice 와 동일하게 make 내장 함수를 사용하여 empty map 을 생성할 수 있다. 이렇게 생성된 map 은 길이는 0, 사이즈는 5 가 되고, 길이가 사이즈 보다 커질 때에도 사이즈가 확장되어 key, value 를 추가할 수 있다. (사이즈 확장은 때에 따라 성능 감소의 요인이 될 수 있다)

출력 결과와 같이 map 에 추가된 key, value 는 순서가 없다.

m := make(map[string]int, 5)
fmt.Println(len(m))

m["one"] = 1
m["two"] = 2
m["three"] = 3
m["four"] = 4
m["five"] = 5
fmt.Println(m, len(m))

m["six"] = 6
fmt.Println(m, len(m))

--- output ---
0
map[five:5 four:4 one:1 three:3 two:2] 5
map[five:5 four:4 one:1 six:6 three:3 two:2] 6

 

comma ok idiom

map 에서 없는 key 값으로 value 를 요청하면 value 타입의 zero 값이 리턴된다. 즉 에러가 발생하지 않는다.

m := map[string]int{
    "one": 1,
    "two": 2,
}
fmt.Println(m["three"])

--- output ---
0

 

그렇기 때문에 해당 key 값의 value 가 제대로 넘어왔는지 알 수 있게 , 로 구분되어 value 와 boolen 값이 리턴된다.

m := map[string]int{
    "one": 1,
    "two": 2,
}

v, ok := m["one"]
fmt.Println(v, ok)

v, ok = m["three"]
fmt.Println(v, ok)

--- output ---
1 true
0 false

 

map 의 item 을 삭제하려면 delete 내장 함수 를 사용한다. delete 함수에 첫번째 아규먼트에는 map 을, 두번째 아규먼트에는 key 를 넣으면 된다. 해당 key 가 map 에 없다고 해서 에러를 만들지는 않는다.

m := map[string]int{
    "one": 1,
    "two": 2,
}

delete(m, "one")
fmt.Println(m)
delete(m, "three")

--- output ---
map[two:2]

 

map 의 key 는 중복될 수 없다. key, value 를 추가하는데 기존의 map 에 key 가 존재한다면, key 에 해당 하는 value 값이 업데이트 된다.

Go 에는 Set 타입이 없는데 참고로 key 가 중복될 수 없다는 이 특징을 잘 활용하면 Set 을 구현할 수 있다.

m := map[string]int{
    "one": 1,
    "two": 2,
}
m["two"] = 3
fmt.Println(m)

--- output ---
map[one:1 two:3]

 

 

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

 

 

C 와 Java 를 안다면 Go 를 공부하면 헷갈리는 부분들이 다소 있다. 일반적으로 기본 내장 타입은 비슷해서 이해하기 쉽지만 pointer 와 interface 등을 공부하다 보면 헷갈리기 마련이다.

일단 먼저 기본 타입에 대해서 설명한다.

 

Built-in Type

기본 내장 타입에는 boolean, integer, rune, float, byte, string 이 있다. 물론 complex type 도 있지만 이는 생략하겠다. 기본 내장 타입은 zero 값을 갖고 있다.

이 중에서 숫자 타입은 integer, rune, float 이다.

 

boolean

boolean 의 zero 값은 false 이다.

var isDev bool
var isTrue = true

fmt.Println(isDev)
fmt.Println(isTrue)

--- output ---
false
true

변수 선언은 var 변수명 타입 으로 선언할 수 있다. 이렇게 설명하면 zero 값으로 false 가 자동 대입된다.

다른 방법은 var 변수명 = 값 과 같이 변수를 선언하면서 값을 대입하는 것이다. 그럼 값을 보고 알아서 타입을 결정해 준다.

 

Integer (숫자 타입 중 하나)

Integer type 은 int8, int16, int32, int64, uint8, uint16, uint32, uint64 이다. 그리고 특별한 integer 타입으로 byterune 타입이 있다.

byteuint8 의 별칭으로 unit8 보다는 byte 를 주로 쓰게 된다.

runeint32 의 별칭이다.

보통 Integer Type 은 int 로 많이 사용하는데, 32bit cpu 이면 int32, 64bit cpu 이면 int64 와 동일하다.

int 의 zero 값은 0 이다.

var x int
var y int = 1
var z = 2
fmt.Println(x)
fmt.Println(y)
fmt.Println(z)

--- output ---
0
1
2

 

Float (숫자 타입 중 하나)

Float type 은 float32, float64 이 있다.

float type 의 zero 값은 int 와 동일하게 0 이다.

var p float64
var q = 0.0
fmt.Println(p)
fmt.Println(q)

--- output ---
0
0

숫자 타입 간에는 편하게 해당 타입으로 변환이 가능하다.

var x int = 10
var p float64 = 20.2
fmt.Println(x + int(p))
fmt.Println(float64(x) + p)

--- output ---
30
30.2

 

String

string 의 zero 값은 empty string 이다. Java 에서는 null string 이 있지만 Go 에서는 null 이 아니다.

string 과 자주 사용되는 타입은 runebyte 이다.

앞에서 숫자 타입은 서로 타입 변환이 해당 타입으로 감싸주면 된다고 설명했는데 string, rune, byte 도 서로 타입 변환이 가능하다.

Go 최신 버전에서는 숫자 타입을 단순히 string 으로 감싸는 것은 체크 시에 에러가 발생한다.

var r rune = 'r'
var s string = string(r)
var b byte = 'b'
var s2 string = string(b)
fmt.Println(r)
fmt.Println(b)
fmt.Println(s)
fmt.Println(s2)

--- output ---
114
98
r
b

runebyteint 타입의 별칭이라 숫자 값이 출력된 것을 이해하자.

나중에 설명하겠지만 [] 를 사용하여 slice (배열과 비슷한) 타입을 활용하면 string[]byte 혹은 []rune 으로 변환할 수 있다.

var s string = "Hello, 😀"
var bs []byte = []byte(s)
var rs []rune = []rune(s)
fmt.Println(bs)
fmt.Println(rs)

--- output ---
[72 101 108 108 111 44 32 240 159 152 128]
[72 101 108 108 111 44 32 128512]

😀 이모지 값은 utf-8 으로 표시해야 되기 때문에 byte 는 [240 159 152 128] 와 같이 4 바이트로 표시되고 rune 은 [128512] 로 표시된다. byte 는 int8 이고, rune 은 int32 임을 기억하자.

이렇게 보면 string 은 rune 으로 변환하는 것이 편리하다.

마지막으로 Short Variable Delcaration Operator(:=) 를 사용하여 vartype 키워드 없이 변수를 선언하고 바로 할당할 수 있다.

var a, b int = 1, 2
var c, d = 3, "a"

i := 10
x, s := 20.5, "Hello World"

--- output ---
1 2 3 a
10 20.5 Hello World

 

 

반응형
Posted by seungkyua@gmail.com
TAG go, golang, type

댓글을 달아 주세요

반응형

 

 

Kubernetes source code 를 디버깅하면서 분석하기 위해 golang 환경 설정과 사용을 계속해서 설명하고자 한다.

 

오늘은 첫번째로 무료 IDE 인 (thx MS) VSCode 를 설치하고 세팅하는 법을 알아보자.

golang 설치

mac 에서 golang 은 home brew 로 간단히 설치할 수 있다. 혹시 라도 이전에 낮은 버전이 설치되어 있는지 확인한다.

$ brew list | grep go
$ go@1.12

$ go version
go version go1.12.9 darwin/amd64

새로운 버전을 install 한다.

$ brew install go@1.16

현재 1.12 버전을 unlink 한다.

$ brew unlink go

새로운 버전을 link 한다.

$ brew link --force go@1.16

새로운 버전을 check 하면 업그레이드 되어 있는 것을 알 수 있다.

$ go version
go version go1.16.14 darwin/amd64

Go Workspace

go 는 install 명령어로 다른 go tool 들을 설치할 수 있다. 설치 위치는 workspace 라고 부르는 곳인데 특별히 지정하지 않으면 디폴트로 $HOME/go 디렉토리에 설치 된다.

 

go install 로 설치된 툴들은 소스는 $HOME/go/src 에, 바이너리 실행파일은 $HOME/go/bin 에 설치된다.

 

workspace 는 변경할 수 있는데 아래와 같이 .zshrc (zsh 일 경우) 이나 .bash_profile (bash 일 경우) 파일에 환경 변수를 추가하면 된다. 설정하기 전에 해당 workspace 디렉토리는 미리 만들어 줘야 한다.

## 디렉토리 만들기
$ mkdir -p $HOME/Documents/go_workspace

## .zshrc 파일에 환경변수 세팅
export GOPATH=$HOME/Documents/go_workspace
export PATH=$PATH:$GOPATH/bin

Go Tool 설치

코드를 체크해 주는 golint 툴이 있다. 이를 설치해 보자

$ go install golang.org/x/lint/golint@latest

$ which golint
/Users/ahnsk/Documents/go_workspace/bin/lint

특정 디렉토리에서 lint 체크를 하고 싶으면 아래 명령어를 쓰면 된다. 코드를 만든 프로젝트 디렉토리에서 수행하면 모든 파일을 lint 한다.

$ golint ./...

그리고 go 를 설치하면 일부 tool 들은 자동으로 설치되는데 그 중 vet 이라는 유용한 tool 있다. vet 은 error 를 잡아주는 tool 이다.

$ go vet ./...

VS Code 설치

VS Code 는 아래 url 에 접속해서 OS 별로 설치하면 된다. 설치 화면은 생략한다.

 

https://code.visualstudio.com/Download

 

VS Code 를 띄우고 나서 아무 화면에서나 Cmd+Shift+P 키를 입력하면 Command Palette 가 화면 위에 생성된다. 거기에서 shell command 로 검색하여 install ‘code’ command in PATH 를 선택한다.

 

 

이렇게 설치한 후 터미널을 새롭게 띄워서 특정 디렉토리에서 code 명령어를 이력하면 VS Code 가 실행된다.

 

아래와 같이 go-sample 디렉토리에서 실행하면 VS Code 가 뜨는 것을 볼 수 있다.

$ cd go-sample
$ code

VS Code Extension 설치

VS Code 는 다양한 Extension 을 설치할 수 있다. 그 중에 go 를 써야 하니 Go extension 을 설치해 보자.

 

VS Code 화면에 보면 맨 좌측에 세로로 아이콘들이 여러개 있다. 그 중 위에서 5번째 혹은 6번째에 있는 extension 아이콘을 선택하고 go 를 검색해서 go Team at Google 에서 만든 Go 를 설치해 준다.

 

아래 그림에서 맨 처음 줄의 Go 의 install 파란 버튼을 클릭해서 설치한다.

 

 

이제 기본 설치는 끝났다.

 

hello world 를 출력하는 간단한 소스를 만들어서 실행해 보자.

 

VS Code 에서 새로운 파일을 생성해서 main.go 파일을 만들어서 저장한다.

package main

import "fmt"

func main() {
	fmt.Println("Hello world!!!")
}

VS Code 상단 메뉴에 Terminal >> New Terminal 을 클릭하면 터미널을 VS Code 에서 사용할 수 있다.

$ go run main.go
Hello world!!!

VS Code 화면을 보면 다음과 같다.

 

 

이제 세팅은 모두 끝났다. 디버깅 세팅하는 방법이 있는데 go 를 설명하다가 어느 정도 지나면 디버깅 세팅도 추가로 올릴 예정이다.

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

Golang 으로 개발된 애플리케이션을 Docker image 로 만드는 Dockerfile 이다.

이미지 사이즈를 줄이기 위해서 빌드 이미지와 실행 이미지를 구분하여 작성하였다.

 

실행 이미지를 더 작게 만들고 싶으면 base를 golang 이미지 대신 alphine 이미지로 만들면 된다.

 

한가지 tip 이라 하면 RUN go mod vendor 를 먼저 하고 나중에 COPY . . 를 하여 소스를 복사한 부분이다.

만약 소스 복사를 먼저하면 변경되 소스로 인해서 (하위 layer 의 변경) go mod vendor 로 다운로드 하는 부분을 수행하게 된다. 하지만 go mod vendor 를 먼저한 후 소스를 복사하면 go mod vendor 에서 수정된 부분이 없으면 해당 layer 를 재사용하기 때문에 매번 다운로드를 하지 않는다.  

 

# vi Dockerfile

FROM golang:1.16.3-stretch AS builder
LABEL AUTHOR Seungkyu Ahn (seungkyua@gmail.com)

RUN mkdir -p /build
WORKDIR /build

COPY go.mod .
COPY go.sum .
RUN go mod vendor
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/tksinfo ./cmd/server.go

RUN mkdir -p /dist
WORKDIR /dist
RUN cp /build/bin/tksinfo ./tksinfo




FROM golang:alpine3.13

RUN mkdir -p /app
WORKDIR /app

COPY --chown=0:0 --from=builder /dist /app/
EXPOSE 9111

ENTRYPOINT ["/app/tksinfo"]
CMD ["--port", "9111"]
반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

2021년 4월 Golang 의 최신 버전은 1.16.x 입니다.

 

go 프로그램을 실행파일로 컴파일 하면 default 는 dynamic library 로 만들어 집니다.

 

아무리 OS 와 architecture 에 맞춰서 cross compile 을 했다고 해도 링크된 모듈이 존재하지 않으면 에러가 납니다.

특히 docker 로 실행할 경우 이런 문제를 자주 겪게 됩니다.

 

1.15 버전 이상에서는 CGO_ENABLED=0 환경 변수로 static library 로 만들 수 있습니다. 참고로 1.14 버전 이하에서는 -tags 옵션을 사용할 수 있습니다.

 

아래 Makefile 은 이 부분을 고려해서 만들었습니다.

GOOS는 OS 의 종류를 GOARCH 는 Architecture 에 맞춰 실행파일을 만듭니다.

 

$ vi Makefile

.PHONY: build clean docker

default: build

all: build docker

build: build-darwin build-linux

build-darwin:
	CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/tksinfo-darwin-amd64 ./cmd/server.go
	CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/tksinfo-appclient-darwin-amd64 ./examples/application/client.go

build-linux:
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/tksinfo-linux-amd64 ./cmd/server.go
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/tksinfo-appclient-linux-amd64 ./examples/application/client.go

clean:
	rm -rf ./bin

docker:
	docker build --no-cache -t seungkyua/tksinfo -f Dockerfile .
반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

Kubernetes 는 GoLang 으로 만들어진 대표적인 s/w 입니다. Kubernetes  구조와 비슷하게 GoLang 으로 프로젝트를 만들 때 사용되는 일반적인 디렉토리 구조를 설명하겠습니다.

 

제일 먼저 GOPATH 를 지정하고, bin 디렉토리를 PATH에 추가로 지정합니다.

$ GOPATH=/Users/ahnsk/Documents/go_workspace
$ PATH=/Users/ahnsk/Documents/go_workspace/bin:$PATH

 

다음은 go get 으로 govendor 를 설치합니다. govendor 는 dependency module 을 쉽게 다운받고 관리할 수 있습니다.

$ go get -u github.com/kardianos/govendor
$ govendor -version
v1.0.9

 

이제 본인이 생성할 프로젝트를 만들어 보겠습니다. github.com 에 있는 go 프로젝트를 다운로드 받습니다.

$ mkdir -p /Users/ahnsk/Documents/go_workspace/cookiemonster2
$ GOPATH=/Users/ahnsk/Documents/go_workspace/cookiemonster2
$ go get -u github.com/seungkyua/cookiemonster2

GOPATH 는 각 프로젝트마다 자신만의 용도로 사용하기 위해 각각 지정하는 것이 편리합니다.

 

cookiemonster2 라는 프로젝트는 GOPATH 아래의 src 디렉토리에 패키지 경로(github.com/seungkyua/cookiemonster2)로 다운로드 됩니다.

/Users/ahnsk/Documents/go_workspace/cookiemonster2/src/
github.com/seungkyua/cookiemonster2

 

프로젝트 디렉토리로 이동해서 보면 다음과 같습니다.

$ tree -L 2 .
.
├── Dockerfile.cookiemonster
├── LICENSE
├── Makefile
├── README.md
├── cmd
│   ├── cluster_client.go
│   ├── cluster_client2.go
│   └── server.go
├── config
│   └── config.yaml
├── manifest
│   ├── cookiemonster-cm-config.yaml
│   ├── cookiemonster-deployment.yaml
│   ├── cookiemonster-rbac.yaml
│   └── cookiemonster-service.yaml
├── pkg
│   ├── domain
│   └── handler
└── vendor
    └── ...

 

일반적으로 entrypoint 가 되는 go 파일은 cmd 디렉토리 아래에 위치하고 나머지 go 파일들은 pkg 디렉토리에 패키지 구조로 위치합니다.

vendor 디렉토리는 dependency module 을 다운받은 곳이므로 이 디렉토리는 삭제를 하고, 처음부터 새롭게 구성해 보겠습니다.

$ rm -rf vendor

 

govendor init 으로 vendor 디렉토리를 생성합니다.  새롭게 생성된 vendor 디렉토리에 vendor.json 파일이 보이는데 이것은 향후 dependency module 을 다운 받을 때 활용됩니다.

$ govendor init

$ tree -L 2 .
.
├── Dockerfile.cookiemonster
├── LICENSE
├── Makefile
├── README.md
├── cmd
│   ├── cluster_client.go
│   ├── cluster_client2.go
│   └── server.go
├── config
│   └── config.yaml
├── manifest
│   ├── cookiemonster-cm-config.yaml
│   ├── cookiemonster-deployment.yaml
│   ├── cookiemonster-rbac.yaml
│   └── cookiemonster-service.yaml
├── pkg
│   ├── domain
│   └── handler
└── vendor
    └── vendor.json

 

vendor 디렉토리 밑으로 디펜던시 module 을 다운로드 받습니다.

$ govendor get github.com/seungkyua/cookiemonster2

$ tree -L 2 .
.
├── Dockerfile.cookiemonster
├── LICENSE
├── Makefile
├── README.md
├── cmd
│   ├── cluster_client.go
│   ├── cluster_client2.go
│   └── server.go
├── config
│   └── config.yaml
├── manifest
│   ├── cookiemonster-cm-config.yaml
│   ├── cookiemonster-deployment.yaml
│   ├── cookiemonster-rbac.yaml
│   └── cookiemonster-service.yaml
├── pkg
│   ├── domain
│   └── handler
└── vendor
    ├── appengine
    ├── appengine_internal
    ├── github.com
    ├── golang.org
    ├── google.golang.org
    ├── gopkg.in
    ├── k8s.io
    ├── sigs.k8s.io
    └── vendor.json

 

vendor 디렉토리에 dependency module 의 특정버전까지 다운 받을 수 있으니 관리가 편리해 집니다. 

현재 버전에서 labstack 의 echo v4 모듈에 버그가 있어 v3 로 다시 다운받기 위해 해당 디렉토리를 지우고 다시 다운 받을 수 있습니다. @v3 는 v3  이상의 최신 버전에 해당하는 git 브랜치나 태그를 다운받으라는 의미입니다.

$ rm -rf vendor/github.com/labstack/echo

$ govendor get github.com/labstack/echo@v3

 

여기서 govendor 의 버그로 echo 는 다운받았지만 하위 디렉토리인 echo/middleware 는 다운이 안되었으므로 추가로 vendor.json 을 수정하여 다운로드 받습니다.

$ vi vendor/vendor.json
{
   "checksumSHA1": "ynPXfBgVKquHSKkdFWk3oqSYf+g=",
   "path": "github.com/labstack/echo",
   "revision": "38772c686c76b501f94bd6cd5b77f5842e93b559",
   "revisionTime": "2019-01-28T14:12:53Z",
   "version": "v3.3.10",
   "versionExact": "v3.3.10"
},
{
   "checksumSHA1": "Rp/k+BJKpaeB9vyjEPFBW4LeFP8=",
   "path": "github.com/labstack/echo/middleware",
   "revision": "38772c686c76b501f94bd6cd5b77f5842e93b559",
   "revisionTime": "2019-01-28T14:12:53Z",
   "version": "v3.3.10",
   "versionExact": "v3.3.10"
}

$ govendor fetch github.com/labstack/echo/middleware

 

 

 

 

 

 

 

 

 

 

 

반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요

반응형

전쳬 에제 소스

https://github.com/seungkyua/hello-go


Go 프로그램에서 channel 을 사용할 때 가장 중요한 부분은 channel 을 한 번만 닫아야 한다는 것이다. 그렇지 않고 닫힌 channel 을 중복해서 닫으려고 하면 panic 이 발생한다. 그래서 Discovery Go 책에서는 channel 에 값을 보내는 쪽에서 닫는 패턴을 추천한다.


프로젝트로 완성하는 Go 프로그래밍(Go Programming Blueprints Second Edition) 의 chat 예제를 보면 client 가 종료되어도 client 가 가지고 있는 채널을 닫지 않는 에러가 있어 이를 간단히 수정했다.


client.go 의 client struct 에 필요없는 room 변수는 삭제했다. (나중에 필요하면 추가할지도)

대신 read 메소드에 root 의 forward channel 을 인자로 받는다. 

type client struct {
socket *websocket.Conn
send chan []byte
}


read 메소드에는 socket close 하는 부분이 있는데 socket 에러가 나면 read 가 에러가 날 것이고 이 때 defer 로 channel 닫는 부분을 추가한다. write 메소드에는 중복을 피하기 위해서 channel 닫는 부분을 넣지 않았다.


func (c *client) read(forward chan<- []byte) {
defer func() {
c.socket.Close()
log.Println("Client socker read closed.")
close(c.send)
log.Println("Client send channel closed.")
}()
for {
_, msg, err := c.socket.ReadMessage()
if err != nil {
log.Println("Client Socket ReadMaessage error")
return
}
forward <- msg
}
}

 

 root.go 에서는 쓰이고 있지는 않지만 client 에 leave 할 경우 기존 client channel 닫는 소스 대신 client socket 을 닫는 부분으로 변경했다. range 로 모든 client channel 에 값을 보내고 있기 때문에 여기는 보내는 channel 이지만 channel 을 닫기가 어렵다. 그래서 client 에서 channel 을 닫는 로직을 추가했다.

 

root struct 에서 생성한 forward, join, leave channel 은 처리하지 않았다. 나중에 room 에 client 가 아무도 없을 때의 로직에 추가되면 그 때 닫는 부분을 추가해야 한다.


func (r *room) run() {
go func() {
for {
select {
case client := <-r.join:
r.clients[client] = true
case client := <-r.leave:
delete(r.clients, client)
client.socket.Close()
case msg := <-r.forward:
for client := range r.clients {
client.send <- msg
}
}
}
}()
}








반응형
Posted by seungkyua@gmail.com

댓글을 달아 주세요