Contents
for문 동작 원리
- Go언어는 반복문으로 for문 하나만 지원한다.
- 하지만 여러가지 형태가 있기 때문에 적재적소에 잘 사용해야 한다.
형식
for 초기문; 조건문; 후처리 {
코드 블록 // 조건문이 true인 경우 수행됨.
}
동작 순서
- 초기문이 먼저 실행됨.
- 조건문을 검사함.
- if 조건문이 true?
- for문 { } 안쪽 코드 블록 수행
- 후처리 구문 실행
- if 조건문이 false?
- for문 종료
- if 조건문이 true?
다양한 형태
1️⃣ 초기문 생략
for ; 조건문; 후처리 {
코드 블록
}
초기문을 생략해도 ;를 붙여서 조건문 자리를 표시해줘야 한다.
2️⃣ 후처리 생략
for 초기문; 조건문; {
코드 블록
}
후처리를 생략해도 조건문 뒤에 ;를 붙여줘야 한다.
3️⃣ 조건문만 있는 경우
형식 1
for ; 조건문; {
코드 블록
}
형식 2
for 조건문 {
코드 블록
}
4️⃣ 무한 루프
형식 1
for true {
코드 블록
}
조건문이 true이면 무한 루프가 된다.
형식 2
for {
코드 블록
}
switch문과 마찬가지로 true를 생략할 수 있다.
continue와 break
- continue : 이후 코드 블록을 수행하지 않고 곧바로 후처리를 하고 조건문 검사부터 다시한다.
- break : for문에서 곧바로 빠져 나온다.
예제
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
stdin := bufio.NewReader(os.Stdin)
for { // ❶ 무한 루프
fmt.Println("입력하세요")
var number int
_, err := fmt.Scanln(&number) // ❷ 한줄 입력을 받습니다.
if err != nil { // ❸ 숫자가 아닌 경우
fmt.Println("숫자를 입력하세요")
// 키보드 버퍼를 비웁니다.
stdin.ReadString('\\n') // ❹ 키보드 버퍼를 지워줍니다.
continue // ➎ ❶ 로 돌아갑니다
}
fmt.Printf("입력하신 숫자는 %d입니다\\n", number)
if number%2 == 0 { // ➏ 짝수 검사를 합니다.
break // ➐ for문을 종료합니다.
}
}
fmt.Println("for문이 종료되었습니다.")
}
중첩 for문
for문을 중첩해서 사용할 수 있다.
예제 1
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ { // ❶ 3번 반복합니다.
for j := 0; j < 5; j++ { // ❷ 5번 반복합니다.
fmt.Print("*") // ❸ * 를 출력합니다.
}
fmt.Println() // ❹ 줄바꿈합니다.
}
}
중첩 반복문을 사용하면 연산량이 크게 증가되므로 사용에 주의해야 한다.
예제 2
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ { // ❶ 5번 반복합니다.
for j := 0; j < i+1; j++ { // ❷ 현재 i값+1만큼 반복합니다.
fmt.Print("*") // ❸ *를 출력합니다.
}
fmt.Println() // ❹ 줄바꿈합니다.
}
}
예제 3 continue와 break를 사용
package main
import "fmt"
func main() {
dan := 2
b := 1
for {
for {
fmt.Printf("%d * %d = %d\\n", dan, b, dan*b)
b++
if b == 10 { // ❶
break // 안쪽 for문을 종료합니다.
}
}
b = 1
dan++
if dan == 10 { // ❷
break // 바깥쪽 for문을 종료합니다.
}
}
fmt.Println("for문이 종료되었습니다.")
}
- for문을 중첩하여 구구단을 출력한다.
중첩 for문과 break, 레이블
앞의 예제들은 break가 속한 for문에서만 빠져나온다.
모든 for문을 빠져나가고 싶을 때는 어떻게 해야 할까?
예제 1 플래그 (Flag) 변수 사용
package main
import "fmt"
func main() {
a := 1
b := 1
found := false
for ; a <= 9; a++ {
for b = 1; b <= 9; b++ {
if a*b == 45 {
found = true // ❶ 찾았음을 표시하고 break
break
}
}
if found { // ❷ 바깥쪽 for문에서 찾았는지 검사해서 break
break
}
}
fmt.Printf("%d * %d = %d\\n", a, b, a*b)
}
- 1~9 사이의 두 수를 곱했을 때 45가 되는 수를 찾는다.
- 안쪽 for문에서 두 수의 곱이 45가 되는 경우를 찾았으면 안쪽 for문을 종료한다.
- 이 때 바깥쪽의 for문은 종료되지 않았으므로 found가 ture인지 검사하고 for문을 종료해야 한다.
이런 형태로 boolean 변수를 사용하는 것을 플래그(Flag) 변수라고한다.
그러나 이런 플래그 변수 사용하는 것은 번거로울 수 있음. (삼중, 사중 for문 이면 복잡..)
예제 2 레이블
package main
import "fmt"
func main() {
a := 1
b := 1
OuterFor:
for ; a <= 9; a++ {
for b = 1; b <= 9; b++ {
if a*b == 45 {
break OuterFor
}
}
}
fmt.Printf("%d * %d = %d\\n", a, b, a*b)
}
for문을 시작할 때 레이블을 정의하고 break할 때 정의한 레이블을 적어준다면 그 레이블에서 가장 먼저 속한 for문까지 모두 종료하게 된다.
- 레이블은 레이블 이름을 적고 콜론 : 을 적어서 정의한다.
그러나 레이블 방법은 편리할 수 있으나 다음의 단점이 존재한다.
- 혼동을 불러일으킬 수 있음.
- 예기치 못한 버그가 발생할 수 있음.
따라서 되도록 플래그를 사용하고 레이블은 꼭 필요한 경우에만 사용하기를 권장한다.
클린코드
- 중첩된 내부 로직을 함수로 묶어서 중첩을 줄이고
- 플래그 변수나 레이블 사용을 최소화 해야한다.
예제
package main
import "fmt"
func find45(a int) (int, bool) { // ❶ 곱해서 45가 되는 값을 찾는다
for b := 1; b <= 9; b++ {
if a*b == 45 {
return b, true
}
}
return 0, false
}
func main() {
a := 1
b := 0
for ; a <= 9; a++ {
var found bool
if b, found = find45(a); found { // ❷ 함수 호출
break
}
}
fmt.Printf("%d * %d = %d\\n", a, b, a*b)
}