언어를 배울 때 가장 기본적인 것 중에 하나가 비교문(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]