정상우
hELLO.
정상우
전체 방문자
384,472
오늘
91
어제
1,174
  • hELLO. (121)
    • 컴퓨터과학 (4)
      • 알고리즘 & 자료구조 (4)
    • 언어 & 프레임워크 (63)
      • Go (23)
      • PHP & Laravel (40)
    • 웹 (7)
    • 블록체인 (12)
      • 메인넷 (9)
      • 암호화폐 플랫폼 (3)
    • 포트폴리오 (10)
    • 칼럼 (20)
      • 에세이 (4)
      • 개발자스럽게 살기 (14)
      • 회고 (2)
    • 티스토리 (5)

블로그 메뉴

  • ⚡ 개발자 이력서
  • 🌟 깃허브
  • 💻 강의
  • ✨ 예제코드
  • ⭐ 브런치
  • 태그 클라우드
  • 방명록

공지사항

  • 2차 도메인을 설정했습니다 ✨

인기 글

  • JWT(JSON Web Token)의 개념부⋯
    2021.07.29
    JWT(JSON Web Token)의 개념부⋯
  • 'REST' 를 보다 'RESTful' 하게⋯
    2021.08.14
    'REST' 를 보다 'RESTful' 하게⋯
  • [Laravel] 라라벨 프레임워크⋯
    2021.06.10
    [Laravel] 라라벨 프레임워크⋯
  • 깃허브를 포트폴리오로 쓰려면⋯
    2021.12.25
    깃허브를 포트폴리오로 쓰려면⋯
  • 암호화폐 트레이딩 봇을 만들었⋯
    2021.05.12
    암호화폐 트레이딩 봇을 만들었⋯

태그

  • 라라벨
  • go
  • 포트폴리오
  • 개발 리뷰
  • 개발
  • php
  • 블록체인
  • 프로그래머스
  • Algorithm
  • 코딩테스트

최근 댓글

  • 고맙습니다 ~^^
    정상우
  • 오늘 블로그 만들었는데 검색하⋯
    엥뿌삐
  • 좋은 스킨 정말 감사드립니다.⋯
    이태홍
  • 고맙습니다 ㅎㅎ
    정상우
  • 제가 원하던 최고의 스킨입니다⋯
    _HEON

최근 글

  • 빠르게 성장하는 개발자의 세⋯
    2022.06.08
    빠르게 성장하는 개발자의 세⋯
  • 개발자와 엔지니어, 그 사이에서
    2022.05.10
    개발자와 엔지니어, 그 사이에서
  • 아임포트(Iamport)로 결제기능⋯
    2022.04.03
    아임포트(Iamport)로 결제기능⋯
  • 아임포트(Iamport)로 결제기능⋯
    2022.04.01
    아임포트(Iamport)로 결제기능⋯
  • [Laravel] 카페24 호스팅에 라⋯
    2022.03.29
    [Laravel] 카페24 호스팅에 라⋯

티스토리

hELLO · Designed By 정상우.
정상우

hELLO.

언어 & 프레임워크/Go

Go: 변수 스코프와 블록

2021. 1. 6. 16:56

렉시컬 블록

렉시컬 블록은 명시적으로 선언되는 블록 뿐만 아니라, if, for, switch, select, case 에서 사용하는 블록과 중괄호로 묶이지 않는 선언의 그룹 및 광역, 패키지, 파일 블록을 모두 포함하는 개념이다.

 

렉시컬 블록은 블록의 범위를 결정하는데, 이는 스코프라고 부를 수 있다. Go 의 변수는 기본적으로 블록 스코프를 가지기 때문에 블록 외부에서 내부에 선언된 변수에 대해서는 접근하는 것이 불가능하다. 물론 변수의 스코프는 어디에 선언이 되어있는가에 따라 다르다. 함수나 패키지에 선언되어 있을 수도 있어서 함수 스코프가 되기도 하고 패키지 스코프가 되기도 한다. Go 에서의 가장 작은 스코프의 범위가 블록이라는 점은 중요 포인트다.

func main() {
	x := "Hello, Go!"
	for _, x := range x {
		fmt.Printf("%c", x)
	}
}

위의 코드에서 range x 의 x 와 블록 내부에서 접근하는 x 는 서로 다른 것을 의미한다. 이는 Go 의 변수가 블록 스코프를 가지고 있기 때문이다. range x 의 x 는 Hello, Go! 문자열을 의미하고, for 구문에 있는 x 변수는 for 블록 내부에서만 유효하며 Hello, Go! 의 각 문자 하나를 의미한다. 이는 명시적으로 중괄호로 묶이지는 않았지만, 하나의 렉시컬 블록으로 취급한다.

 

if 와 같이 명시적으로 중괄호로 묶이지 않은 것들에 대해서도 다음과 같이 접근할 수 있다. 그렇지만, 밖에서 접근하는 것은 허용하지 않는다.

if x := "Hello, Go"; x != "" {
	fmt.Println(x)
} else if y := x; x == y {
	fmt.Println(y)
} else {
	fmt.Println(x, y)
}
fmt.Println(x, y) // ERROR

함수, 패키지 스코프

변수가 작은 블록을 넘어 함수나 패키지 단위에 선언되어 있다면 내부 블록에서 외부 블록에 있는 변수에 접근할 수 있다. 다른 스코프에 같은 이름의 변수가 선언되어 있다면 가까운 범위에서 선언된 변수를 우선적으로 찾으며 그렇지 않다면 상위 스코프로 검색을 이어나가게 된다. 이러한 절차를 스코프 체이닝(Scope Chaining)이라고 한다.

func main() {
	x := "Hello, Go!"
	for _, x := range x {
		fmt.Printf("%c", x)
	}
}

위의 코드에서 main() 함수 아래에 선언된 x 는 함수 스코프이고, for 구문의 일부로 선언된 x 는 블록 스코프이다. 따라서 for 블록에서 x 변수에 접근할 때 가까운 스코프를 먼저 탐색하게 되므로 블록 스코프에 선언된 x 를 찾는 것이다. 

 

여기서 살펴볼만한 것은 짧은 변수선언 표현을 사용할 때 눈속임이 발생할 수 있다는 것이다. 아래의 코드를 보자.

var cwd string

func main() {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(cwd)
}

이 코드의 cwd 변수는 전역 변수의 값을 바꾸는 것이 아니라 지역 변수로써 main() 함수 스코프에 선언하게 된다. 만약에 이것을 전역변수에 할당되는 것을 의도로한 것이라면 이 코드는 잘못된 것이므로 아래와 같이 바꿔줄 필요가 있다.

var cwd string

func main() {
	var err error
	cwd, err = os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(cwd)
}

아래의 코드와 같이 Go 에서는 두 개 이상의 값을 반환하는 함수를 호출하고 값을 할당할 때 이미 선언한 변수를 짧은 선언문에서 함께 사용하더라도 할당으로 취급하는 특징이 있는데, 스코프가 다른 경우 이는 해당되지 않는다는 점을 확인할 수 있다. 

func main() {
	f, err := os.Open("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	var buf bytes.Buffer
    
	n, err := buf.ReadFrom(f)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%#v, Read %d bytes", buf.String(), n)
}

err 변수가 두 번 선언되어 있는 모습을 볼 수 있는데, Go 에서는 둘 이상의 선언에서 하나 이상의 새로운 변수가 포함되어 있다면 같은 이름에 대해 선언이 아닌 할당으로 취급되어 n, err := buf.ReadFrom(f) 의 err 변수는 할당으로 취급된다. 그러나 스코프가 다르다면 이는 선언이 된다.

렉시컬 스코프

Go 는 다른 언어와 마찬가지로 렉시컬 스코프를 가진다. 즉, 함수의 호출이 아닌 선언을 할 때 스코프가 결정되어 정적 스코프라고도 한다. 이것은 런타임 중에 동적으로 스코프가 일어나는 동적 스코프와는 반대되는 개념이다.

var x string = "Hello, Go!"

func foo() {
	fmt.Println(x)
}

func bar() string {
	x := "Goodbye"
	foo()

	return x
}

func main() {
	x := bar() // -> Hello, Go
	fmt.Println(x) // -> Goodbye
}

bar() 함수를 호출했을 때 출력되는 결과는 Hello, Go! 다. foo() 함수를 선언한 시점에서 스코프가 결정되어 전역변수 x 값을 기준으로 하기 때문이다. 그러나 bar() 함수가 반환하는 값은 지역변수 x 이므로 Goodbye 가 된다.

    '언어 & 프레임워크/Go' 카테고리의 다른 글
    • Go 언어를 공부하기 위한 자료 및 문서 정리
    • Go: recover() 를 사용하여 복구하기 [Effective Go]
    • Go: 웹 서버 구축하기 (net/http, html/template)
    • Go: Switch 를 사용하여 타입 체크하기 (Reflection) [Effective Go]
    정상우
    정상우
    과거의 배움으로 현재를 바꾸고 미래를 만듭니다. #25+2살 #INFJ #개발자 #브런치작가
    댓글쓰기
    다음 글
    Go 언어를 공부하기 위한 자료 및 문서 정리
    이전 글
    Go: recover() 를 사용하여 복구하기 [Effective Go]
    • 이전
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • ···
    • 23
    • 다음

    티스토리툴바