[Go] 기본 문법 2

윤동환·2023년 7월 20일
0

Go

목록 보기
16/18
post-thumbnail

Flowcontrol

for

기본

Go는 for 반복문이라는 단 하나의 반복 구조를 가집니다.

func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}

for 문 구성 요소는 3개로 나뉘며 c언어에서 사용하는 방법과 동일합니다.

초기화 구문: 첫 번째 iteration 전에 수행됩니다.
조건 표현: 매 모든 iteration 이전에 판별됩니다.
사후 구문: 매 iteration 마지막에 수행됩니다.

초기화 구문과 사후 구문은 필수가 아닙니다.

func main() {
	sum := 1
	for ; sum < 1000; {
		sum += sum
	}
	fmt.Println(sum)
}

> **주의**
C나 Java, JavaScript와 같은 다른 언어들과 달리, Go는 for문의 세 가지 구성 요소를 감싸는 괄호가 없고, { } 괄호가 항상 필수입니다.



### while
; 을 생략할 수 있다는 점에서 C의 while 은 Go에서 for 로 쓰입니다.
```go
func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

if

Go의 if 문은 ( ) 괄호로 둘러쌓일 필요는 없지만, { } 괄호는 필수입니다.

짦은 구문 if

if 문의 조건문 전에 수행될 짧은 구문으로 시작될 수 있습니다.

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

그 짧은 구문에서 선언된 변수들은 오직 if 문의 끝까지만 스코프가 존재합니다.
만약 스코프 밖에서 v변수를 사용한다면
에러를 유발합니다.

if, else

짧은 if문 안에서 선언된 변수들은 모든 else 블럭에서 사용할 수 있습니다.

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else {
		fmt.Printf("%g >= %g\n", v, lim)
	}
    // can't use v here, though
    return lim
}

하지만 if, else 스코프 밖에선 사용할 수 없습니다.

Switch

기본

switch 구문은 연속적인 if - else 구문을 짧게 사용하는 방안입니다.

Go의 switch는 뒤이어 오는 모든 case를 실행하는 것이 아니라 오직 첫 번째로 선택된 케이스 만을 실행한다는 점을 제외하곤 C, C++, Java, Javascript, PHP와 유사합니다.
즉, Go에서는 자동으로 break를 제공합니다.

만약 Go에서 자동으로 제공하는 break를 사용하지 않고, 다른 케이스로 넘어가고 싶다면 (다른 언어에서 break를 사용하지 않은 것 처럼) fallthrough 키워드를 case 맨 마지막에 넣어주면 됩니다. 단, 마지막 case에는 fallthrough를 사용할 수 없습니다.

조건 없는 switch

조건이 없는 Switch는 switch true 와 동일합니다.

이 구조는 긴 if-else 체인을 작성하는 데에 아주 깔끔한 방식일 수 있습니다.

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

t 시간을 찍어보니
playground 기본 시간대로 잡혀서 "Good evening"이 출력되네요

Defer

defer 문은 자신을 둘러싼 함수가 종료할 때 까지 어떠한 "함수"의 실행을 연기합니다.
연기된 호출의 인자는 즉시 평가되지만 해당 함수 호출은 자신이 속한 함수가 종료할 때 까지 수행되지 않습니다.

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}

defer 스택

연기된 함수들의 호출은 스택에 쌓이며, 한 함수가 종료될 때 해당 함수 내부에서 연기된 함수의 호출들은 후입선출 순서로 수행됩니다.

defer문 동작 규칙 3가지

  1. 지연된 함수의 인수는 defer 문이 평가될 때 평가됩니다
func a() {
    i := 0
    defer fmt.Println(i) // 0
    i++
    return
}

return 될 때 i의 값은 1이지만 0으로 평가되어 0이 출력됩니다.

  1. 지연된 함수 호출은 주변 함수가 반환된 후 후입선출 순서로 실행됩니다.
func b() {
    for i := 0; i < 4; i++ {
        defer fmt.Print(i) // 3210
    }
}

0, 1, 2, 3순서로 호출이 연기되며 i가 평가 받았지만, 순서는 후입선출로써 뒤바뀝니다.

  1. 지연된 함수는 반환 함수의 명명된 반환 값을 읽고 할당할 수 있습니다.
func c() (i int) {
    defer func() { i++ }()
    return 1
}

위 출력이 2가 나온다
return시 1이 return 변수 i를 1로 바꾸고, defer가 실행되며 2로 증가시키기 때문인 것으로 보인다.
chat gpt 무료버전에 이유를 물어보니 계속 이상한 소리를 하여 내가 확인한 방법이 이유라고 생각된다..

package main

import "fmt"

func c() (i int) {
    fmt.Println("0", i)
    defer func() {
        fmt.Println("1", i)
        i++
        fmt.Println("2", i)
    }()
    fmt.Println("3", i)
    return 1
}

func main() {
    fmt.Println("4", c())
}

이렇게 순서와 i의 값을 찍어 보았다.

i를 func()에서 i++하기전 return 에 의해 1로 바뀐 것을 확인 할 수 있다.

Defer, Panic, and Recover

defer 문은 호출을 목록에 push합니다.

panic은 일반적인 제어 흐름을 중지하는 내장함수입니다.
만약 함수 F에서 panic을 만나게되면 defer 등의 이유로 지연된 함수가 실행된 다음 F는 호출자에게 반환됩니다.

recover는 패닉에 빠진 고루틴의 통제권을 되찾는 내장 함수입니다. 복구는 지연된 함수 내에서만 유용합니다. 정상적인 실행 중에 복구 호출은 nil을 반환하고 다른 효과는 없습니다. 현재 고루틴이 패닉 상태이면 복구 호출이 패닉에 지정된 값을 캡처하고 정상적인 실행을 재개합니다.

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

동작 순서

  1. f() 호출합니다.
  2. f() 내 defer가 스택에 담깁니다.
  3. g()를 호출합니다.
  4. 인자값이 4가 될 때 까지 defer print를 스택에 넣으며 "print in g"를 출력합니다.
  5. 인자값 i가 3을 초과하며 Panicking을 출력후, panic에 빠집니다. 이 때 panic에 빠진 i의 값을 저장합니다.
  6. g()함수 내부에 쌓였던 연기된 defer함수의 스택을 후입선출 구조로 비워줍니다.
  7. g()가 끝나고 f()에 돌아와 "Returned normally from g."를 출력하기 전에 defer로 연기해둔 함수 내에서 recover()를 실행시키며 panic에 빠질 때 저장한 i의 값을 recover()의 리턴값으로 받습니다.
  8. 남은 출력문을 출력하며 끝납니다.

recover()는 지연된 함수 내에서만 유용하기때문에, 7번처럼 동작합니다.

Reference

https://go-tour-ko.appspot.com/flowcontrol/1

profile
모르면 공부하고 알게되면 공유하는 개발자

0개의 댓글