[The Go Programming Language] 2장 프로그램 구조 - 2.3 변수

in kr-dev •  6 years ago  (edited)

modolee_logo
안녕하세요. 개발자 모도리입니다.
The Go Programming Language 라는 책으로 Go를 공부하고 있으며, 해당 책의 내용을 요약 정리해서 올리려고 합니다. 저는 번역본을 구매해서 공부하고 있습니다.
예제코드 라고 나오는 것들은 https://github.com/modolee/tgpl.git 에서 다운 받으실 수 있습니다.

지난 게시물


2장 프로그램 구조

2.3 변수

초기화

  • var 선언은 특정 타입의 변수를 만들고 이름을 붙인 뒤 초기 값을 설정합니다.
    var [이름] [타입] = [표현식]
  • [타입] 이나 = [표현식] 부분 중 하나는 생략 가능, 둘 다 생략은 불가능
    • [타입] 생략 : 초기화 표현식에 의해 타입이 결정
    • [표현식] 생략 : 타입 별 제로 값 (숫자 : 0/ 불리언 : false / 문자열 : "" / 인터페이스, 참조 타입(슬라이스, 포인터, 맵, 채널, 함수) : nil)으로 초기화
    • 배열, 구조체의 제로 값 : 타입 내의 모든 원소나 필드가 제로 값
  • 값 방식은 변수가 항상 타입에 맞는 값을 갖게 보장합니다.
    • Go에는 초기화되지 않은 변수가 없습니다. 그래서 코드가 단순해지고, 별도의 추가 작업 없이 경계 조건(Boundary Condition)에 맞게 동작합니다.
    • 아래 코드는 오류나 예기치 않은 동작을 일으키지 않고 빈 문자열을 출력합니다.
    var s string
    fmt.Println(s) // ""
    
  • 한 선언문으로 여러 변수를 선언하고, 선택적으로 그에 대응하는 표현식 목록으로 초기화 할 수 있습니다. 타입을 생략하면 서로 다른 타입의 여러 변수를 선언할 수 있습니다.
    var i, j, k int // int, int, int
    var b, f, s = true, 2.3, "four" // bool, float64, string
    
  • 초기 값은 상수나 임의의 표현식입니다.
  • 초기화 시점
    • 패키지 수준 변수 : main이 시작하기 전
    • 지역 변수 : 함수 실행 중 해당 변수의 선언이 나올 때
  • 여러 값을 반환하는 함수를 호출해 여러 변수를 초기화 할 수 있습니다.
    var f, err = os.Open(name) // os.Open은 파일과 오류를 반환한다.

2.3.1 짧은 변수 선언

  • 짧은 변수 선언의 형태 : 이름 := 표현식

    • 이름의 타입은 표현식의 타입에 의해 결정
    • 짧은 변수 선언의 예시
    anim := gif.GIF{LoopCount: nframes}
    freq := rand.Float64() * 3.0
    t := 0.0
    
  • var 선언을 주로 사용하는 경우

    • 초기화 표현식과 다른 명시적인 타입이 필요한 경우
    • 값이 나중에 할당돼 초기 값이 중요하지 않은 경우
    i := 100 // an int
    var boiling float64 = 100 // a float64
    var names []string
    var err error
    var p Point
    
  • 하나의 짧은 변수 선언으로 여러 변수를 선언하거나 초기화 할 수 있습니다.

    i, j := 0, 1
    
  • =가 할당인 것에 반해 :=는 선언이라는 점을 염두에 둬야 합니다.

    in, err := os.Open(infile)
    // ...
    out, err := os.Create(outfile)
    
    • 첫 번째 구문은 in과 err을 모두 선언합니다.
    • 두 번째 구문에서는 out은 선언하지만, 기존 err 변수에는 값을 할당합니다.
    f, err := os.Open(infile)
    // ...
    f, err := os.Create(outfile) // 컴파일 오류 : 새 변수 없음
    
    • 짧은 변수 선언은 적어도 하나의 새로운 변수를 선언해야 하므로, 위의 코드는 컴파일 되지 않습니다.

2.3.2 포인터

  • 포인터 값은 변수의 주소로, 값이 저장되어 있는 위치입니다.

    • 모든 값이 주소를 갖지는 않지만, 모든 변수에는 주소가 있습니다.
    • 변수명을 사용하지 않거나 심지어 변수명을 모르더라도 포인터를 통해 변수의 값을 간접적으로 읽거나 수정할 수 있습니다.
  • 포인터 표현식

    • var x int 를 선언
    • &x - 타입 : *int | 명칭 : '정수 포인터' | 역할 : 정수 변수에 대한 포인터
    • &x == p 라면 'px를 가리킨다' 또는 'p에는 x의 주소가 있다'라고 말합니다.
    • p가 가리키는 변수는 *p로 나타냅니다.
    • *p 표현식은 변수의 값인 int를 생성하지만, *p가 변수 자체를 나타내므로 할당문의 왼쪽에 써서 변수를 갱신할 수도 있습니다.
    x := 1
    p := &x // *int 타입 p는 x를 가리킴
    fmt.Println(*p) // "1"
    *p = 2 // x = 2와 같음
    fmt.Println(x) // "2"
    
  • 집합형 변수의 각 구성 요소인 구조체의 필드나 배열의 원소도 변수 이기 때문에 주소를 갖습니다.

  • 변수를 나타내는 표현식에만 address-of 연산자(&)를 쓸 수 있습니다.

  • 포인터 초기화, 비교

    • 포인터의 제로 값은 타입에 무관하게 nil입니다.
    • p가 변수를 가리키고 있다면 p != nil은 참입니다.
    • 포인터는 비교할 수 있으며, 두 개의 포인터가 둘 다 동일한 변수를 가리키거나 nil일 경우에만 참입니다.
    var x, y int
    fmt.Println(&x == &x, &x == &y, &x == nil) // "true false false"
    
  • 함수는 지역 변수의 주소를 반환할 수 있습니다.

    var p = f()
    func f() *int {
      v := 1
      return &v
    }
    
    • 함수 f를 호출해 생성 된 지역 변수 v는 호출이 반환된 후에도 존재하며, 포인터 p는 해당 값을 계속 참조할 수 있습니다.
    • f를 호출 할 때마다 다른 값을 반환합니다.
      fmt.Println(f() == f()) // "false"
  • 포인터를 함수의 인자로 전달하면 함수에서 간접적으로 전달된 변수를 갱신할 수 있습니다.

    func incr(p *int) int {
      *p++ // p가 가라키는 값을 증가시키고, p는 변경하지 않습니다.
      return *p
    }
    
    v := 1
    incr(&v) // 부작용 : v는 2가 됩니다.
    fmt.Println(incr(&v)) // "3" (v는 3)
    
  • echo4 - 포인터를 이용하는 flag 패키지 사용

package main

import (
  "flag"
  "fmt"
  "strings"
)

var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")

func main() {
  flag.Parse()
  fmt.Print(strings.Join(flag.Args(), *sep))
  if !*n {
    fmt.Println()
  }
}

예제 코드 [ch2/echo4.go]

실행 결과
$ go build ch/echo4.go
$ ./echo4 a bc def
a bc def
$ ./echo4 -s / a bc def
a bc def $
$ ./echo4 -help
Usage of ./echo4:
-n omit trailing newline
-s string
separator (default " ")

2.3.3 new 함수

  • 변수를 생성하는 또 다른 방법은 내장된 new 함수를 사용하는 것입니다.
p := new(int) // *int 타입 p는 이름 없는 int 변수를 가리킵니다.
fmt.Println(*p) // "0"
*p = 2 // 이름 없는 int를 2로 설정합니다.
fmt.Println(*p) // "2"
  • new로 만든 변수는 임시 이름을 생각해(선언할) 낼 필요가 없고 new(T)를 표현식에 쓸 수 있다는 점 외에는 일반 지역 변수와 동일합니다.
  • 아래 newInt1newInt2는 동일하게 동작합니다.
func newInt1() *int {
  return new(int)
}

func newInt2() *int {
  var dummy int
  return &dummy
}
  • new를 호출 할 때 마다 고유한 주소를 갖는 별개의 변수를 반환합니다.
p := new(int)
q := new(int)
fmt.Println(p == q) // "false"
  • 예외적으로 struct{}나 [0]int 같은 타입에 정보가 없고 크기가 0인 타입은 구현에 따라 같은 주소를 가질 수도 있습니다.
  • new는 키워드가 아니라 사전에 정의 된 함수이므로 함수 안에서 이름을 재정의할 수 있습니다.
    func delta(old, new int) int { return new - old}
    • 물론 delta 안에서는 내장된 new 함수를 사용할 수 없습니다.

2.3.4 변수의 수명

  • 변수의 수명 : 프로그램이 실행 될 때 변수가 존재하는 시간의 길이
  • 패키지 수준 변수의 수명 : 프로그램의 전체 실행 기간과 같음
  • 지역 변수의 수명 : 동적
    • 선언문이 실행될 때 마다 새 인스턴스가 생성되며, 이 변수는 더 이상 접근할 수 없어서 해당 변수의 저장 공간이 재활용될 때 까지 살아 있습니다.
    • 함수 파라미터나 결과 값도 지역 변수이며, 이 들은 포함하는 함수가 호출될 때 마다 생성됩니다.
for t := 0.0; t < cycles*2*math.Pi; t += res {
  x := math.Sin(t)
  y := math.Sin(t*freq + phase)
  img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex)
}
  • t : for 루프 시작 시 생성
  • x, y : 루프의 각 반복 마다 새롭게 생성
  • 변수의 수명이 해당 변수에 접근할 수 있는지 여부로만 결정
    • 지역 변수는 자신이 속한 루프의 반복 이후에도 살아 있을 수 있음
    • 심지어 지역 변수가 속해 있는 함수가 반환된 후에도 계속 남아 있을 수 있음
  • 컴파일러는 지역 변수를 힙이나 스택 중 어디에 할당할지 결정할 수 있지만, 놀랍게도 이 선택은 변수 선언에 var을 사용했는지 new를 사용했는지에 따라 결정되지 않습니다.
var global *int
func f() {
  var x int
  x = 1
  global = &x
}

func g() {
  y := new(int)
  *y = 1
}
  • x는 지역 변수로 선언돼 있지만 f가 반환된 후에도 여전히 global 변수로 접근 가능하므로 힙 영역에 할당되어야 합니다. 이때 x가 f를 탈출했다고 합니다.
  • *y는 g가 반환될 때 더 이상 접근할 수 없으므로 재활용 대상이 되며, *y는 g에서 탈출하지 않았기 때문에 y는 new로 선언 됐더라도 컴파일러는 안전하게 *y를 스택에 할당할 수 있습니다.

C와 같은 언어에서는 보통 정적으로 변수를 선언할 경우(go의 var) 스택에, 동적으로 선언할 경우(go의 new) 힙에 메모리를 할당합니다.

  • 가비지 컬렉션은 정확한 프로그램 작성에 엄청난 도움이 되지만, 그렇다고 메모리 고민에 대한 부담을 덜어주지는 않습니다. 명시적으로 메모리를 할당하고 해제할 필요는 없지만, 효율적인 프로그램을 작성하기 위해서는 여전히 변수의 수명에 대해 인지하고 있어야 합니다.

이어보기

[The Go Programming Language] 2장 프로그램 구조 - 2.4 할당

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!