[The Go Programming Language] 2장 프로그램 구조 - 2.7 범위
안녕하세요. 개발자 모도리입니다.
The Go Programming Language 라는 책으로 Go를 공부하고 있으며, 해당 책의 내용을 요약 정리해서 올리려고 합니다. 저는 번역본을 구매해서 공부하고 있습니다.
게시물에 예제코드 라고 나오는 것들은 https://github.com/modolee/tgpl.git 에서 다운 받으실 수 있습니다.
지난 게시물
- [Go] Mac에서 Atom으로 Go 개발 환경 구축하기
- [The Go Programming Language] 1장 튜토리얼 - 1.1 Hello, World
- [The Go Programming Language] 2장 프로그램 구조 - 2.1 이름
- [The Go Programming Language] 2장 프로그램 구조 - 2.2 선언
- [The Go Programming Language] 2장 프로그램 구조 - 2.3 변수
- [The Go Programming Language] 2장 프로그램 구조 - 2.4 할당
- [The Go Programming Language] 2장 프로그램 구조 - 2.5 타입 선언
- [The Go Programming Language] 2장 프로그램 구조 - 2.6 패키지와 파일
2장 프로그램 구조
2.7 범위
선언과 수명
- 선언의 범위는 소스코드 안에서 선언된 이름으로 해당 선언을 참조할 수 있는 구역입니다.
- 선언의 범위는 프로그램 텍스트의 영역이며, 컴파일 시의 속성입니다.
- 수명은 실행 중에 변수가 프로그램의 다른 구역에서 참조할 수 있는 시간의 범위입니다.
- 변수의 수명은 실행 시의 속성입니다.
예제
내부 선언이 외부 선언을 가리는 경우
func f() {}
var g = "g"
func main() {
f := "f"
fmt.Println(f) // "f"; 지역 변수 f가 패키지 수준 함수 f를 가린다.
fmt.Println(g) // "g"; 패키지 수준 변수
fmt.Println(h) // 컴파일 오류; 선언되지 않음: h
}
각기 다른 어휘 블록에 동일한 이름의 변수가 선언되어 있는 경우
func main() {
x := "hello!"
for i := 0; i < len(x); i++ {
x := x[i]
if x != '!' {
x := x + 'A' - 'a'
fmt.Printf("%c", x) // "HELLO" (반복마다 글자 1개)
}
}
}
- 서로 다른
x
가 세 개 사용되고 있습니다. x[i]
,x + 'A' - 'a'
는 각각 다른 외부 x 선언을 참조합니다.
묵시적인 블록 생성
if x := f(); x == 0 {
fmt.Println(x)
} else if y := g(x); x == y {
fmt.Println(x, y)
} else {
fmt.Println(x, y)
}
fmt.Println(x,y) // 컴파일 오류: 여기서는 x, y를 볼 수 없습니다.
- 두번 째 if문은 첫 번째 if문과 중첩돼 있으므로 첫 번째 문장의 초기화 과정에서 선언된 변수는 두 번째 문장에서도 볼 수 있습니다.
if 블록에서 오류 처리 시
if f, err := os.Open(fname); err != nil { // 컴파일 오류: 사용되지 않음: f
return err
}
f.Stat() // 컴파일 오류: 정의되지 않은 f
f.Close() // 컴파일 오류: 정의되지 않은 f
- f의 범위는 if문에 한정되므로 이후 문장에서는 f에 접근할 수 없어서 컴파일 오류가 발생합니다.
- 컴파일러에 따라 지역 변수 f가 사용되지 않았다는 추가 오류 보고를 받을 수도 있습니다.
// f를 조건문 전에 선언해서 이후에 이용할 수 있게 합니다.
f, err := os.Open(fname)
if err != nil {
return err
}
f.Stat()
f.Close()
// Stat와 Close 함수 호출을 else 블록 안으로 옮겨서 f와 err를 외부 블록에 선언하지 않습니다.
if f, err := os.Open(fname); err != nil {
return err
} else {
// f와 err는 여기서도 볼 수 있습니다.
f.Stat()
f.Close()
}
짧은 변수 선언에서의 범위
- init 함수에서 현재 디렉토리를 가져와서 패키지 수준의 변수를 업데이트 하는 프로그램을 작성하려고 합니다.
var cwd string
func init() {
cwd, err := os.Getwd() // 컴파일 오류: 사용되지 않음: cwd
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}
init
함수의 블록에서는 사전에 선언된cwd
와err
가 없으므로:=
구문이 둘 다 지역 변수로 선언합니다.cwd
의 내부 선언이 외부 선언으로의 접근을 막기 때문에 이 문장은 의도대로 패키지 수준의cwd
변수를 갱신하지 않습니다.
var cwd string
func init() {
cwd, err := os.Getwd() // NOTE: 오류!
if err != nil {
log.Fatalf("os.Getwd failed: %v" err)
}
log.Printf("Working directory = %s", cwd)
}
- 첫번째 코드에서
cwd
를 사용하지 않기 때문에 컴파일 오류가 발생했는데, 두번째 코드 처럼cwd
변수를 사용하는 코드가 포함되어 있다면 어디에서 문제가 발생하는지 찾기 어려울 수 있습니다.
var cwd string
func init() {
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}
- 이런 잠재적인 문제를 해결하는 방법은 여러가지가 있는데, 가장 직접적인 방법은 세번째 코드와 같이
err
를 별도의var
선언으로 분리하고:=
를 사용하지 않는 것입니다.
@modolee, I gave you a vote!
If you follow me, I will also follow you in return!