Golang에서의 제어구문(Control Structures)은 C와 굉장히 밀접하면서도, 다르다. do, while 반복문은 사용하지 않고, for 만을 사용한다. switch는 좀 더 유연히 사용된다. switch를 포함해 select와 같은 multiway communications mulitplexer를 제공하기도 한다.
If
Golang에서 if 는 아래와 같이 사용된다.
if x > 0 {
return y
}
if 와 switch 에서 지역 변수에 대한 초기화 구문을 허용한다.
if err := file.Chmod(0644); err != nil {
log.Print(err)
return err
}
Go Libraries에서, if 구문이 다음의 명령문(코드)로 수행되지 않게 사용하는 스타일을 많이 보게 될 것이다. (개인적으로도 이런 스타일의 코드를 좋아한다.) if 문의 body는 보통 break, continue, goto 또는 return으로 끝나는 경우가 많다. 불필요한 else는 사용하지 않도록...
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
Redelcaration and Reassignment
직전의 예시 구문에서 := 를 사용해 변수를 선언했다.
f, err := os.Open(name)
이 구문은 두개의 변수(f, err)를 선언했다. 그리고 몇줄 뒤에 아래와 같이 d와 err를 선언한다.
d, err := f.Stat()
:=(Short Declaration) 선언에서 변수는 이미 선언된 경우에도 나타날 수 있다. 이 때, err은 두개의 구문에서 같이 선언된 것을 알 수 있다. 이러한 중복은 Golang에서 허용되는 형식이다. err은 첫번째 구문에서 선언되었으나, 두번째 구문에서 re-assigned되었다. 조금 더 상세하게 말하면, err 변수가 f.Stat() 에서 먼저 선언되었고, 두번째 구문에서는 단순히 새로운 값을 할당받은 것이다.
- 이 선언은 기존에 선언한 f와 동일한 scope에 포함되어 있다. (만약 f가 이미 외부 번위에서 선언된 경우라면 새 변수를 생성한다.)
- 초기화에 따른 값은 f에 할당할 수 있다.
- 선언에 의해 생성된 다른 변수가 하나 이상 존재할 수 있다.
이러한 Golang의 속성은 순수한 실용주의에서 비롯되었다고 한다. Golang에서 함수의 매개변수 및 반환 값의 scope는 function body로 제한된다. (함수 외부에서 동일한 명칭으로 나타나더라도)
For
Golang에서의 For 반복문은 C와 비슷하지만 다르다. C에서의 for, while 을 통합한 형태이고, do-while 은 더이상 사용하지 않는다. 아래 3가지 형식이 있다.
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
Short Declaration을 이용해 반복문의 index 변수를 간단히 선언해 아래처럼 사용할 수도 있다.
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
array, slice, string, map 또는 channel로부터의 reading에서 range 구문을 이용할 수도 있다.
for key, value := range oldMap {
newMap[key] = value
}
key(첫번째 변수)만 필요하다면, value(두번째 변수)는 생략해도 된다.
for key := range m {
if key.expired() {
delete(m, key)
}
}
반대로 value(두번째 변수)만 필요하다면 blank identifier(_)을 이용해 사용하지 않도록 선언할 수 있다.
sum := 0
for _, value := range array {
sum += value
}
문자열의 경우 range 구문은 굉장히 유용하다. Golang에서는 String은 (기본 charset으로) UTF-8을 사용해 파싱하고, range를 사용하면 개별 Unicode code size 단위로 분리하게 된다. 인코딩에 문제가 있다면 한 바이트씩 떼네어 rune 형식으로 전환한다.(rune은 U+FFFD Golang에서 사용하는 기본 제공 type이다.)
for pos, char := range "日本\\x80語" { // \\x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\\n", char, pos)
}
---
character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7
Golang에서는 다른 언어에서 흔히 사용하는 ++ 와 -- 는 연산이 아닌 구문의 형식으로 제공한다. 만약 여러 변수를 for에서 사용하고 싶다면 아래처럼 병렬로 할당하면 된다.
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
Switch
Golang의 switch 구문에서는 조건의 표현식이 상수나 정수일 필요가 없고, 구문의 top-down으로 매칭하게 된다. 만약 switch 에 값이 없다면 true로 판단하게 된다. 이를 이용해 if-else-if-else 체인을 switch로 작성하는 것이 가능하다.
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
또한 조건을 하나씩 나열할 필요 없이 comma-separated list형식으로 표현 가능하다.
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
C와 다르게 switch 내부에서 break구문은 사용하지 않아도 된다. 간혹 반복문 내부에서 반복문 자체를 break하기 위해서라면 아래와 같이 사용 가능하다. 물론 loop 내부에서 continue도 사용 가능하다.
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
Type Switch
switch 는 interface 변수의 동적 타입을 판단하기 위해 사용되기도 한다.
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\\n", t) // t has type bool
case int:
fmt.Printf("integer %d\\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\\n", *t) // t has type *int
}
'Development > Golang' 카테고리의 다른 글
[Effective Go] functions (0) | 2021.11.30 |
---|---|
[Effective Go] Names (0) | 2021.11.20 |