7 minute read

개요

Go에서 변수는 데이터를 저장하는 메모리 공간입니다. Go는 정적 타입 언어이므로 모든 변수는 선언 시 또는 컴파일 시점에 타입이 결정됩니다.


변수 선언 방법

1. var 키워드 사용

기본 형식

var 변수명 타입
package main

import "fmt"

func main() {
	var age int
	var name string
	var isActive bool
	
	fmt.Printf("age: %d, name: '%s', isActive: %t\n", age, name, isActive)
	// age: 0, name: '', isActive: false
}

초기값과 함께 선언

var 변수명 타입 = 
package main

import "fmt"

func main() {
	var age int = 25
	var name string = "홍길동"
	var price float64 = 19.99
	
	fmt.Printf("나이: %d, 이름: %s, 가격: %.2f\n", age, name, price)
}

타입 추론

타입을 생략하면 Go가 초기값으로부터 타입을 추론합니다.

var 변수명 = 
package main

import "fmt"

func main() {
	var age = 25        // int로 추론
	var name = "홍길동"  // string으로 추론
	var price = 19.99   // float64로 추론
	
	fmt.Printf("age: %T, name: %T, price: %T\n", age, name, price)
	// age: int, name: string, price: float64
}

여러 변수 동시 선언

package main

import "fmt"

func main() {
	// 같은 타입
	var x, y, z int
	fmt.Println(x, y, z)  // 0 0 0
	
	// 같은 타입, 초기값 포함
	var a, b, c int = 1, 2, 3
	fmt.Println(a, b, c)  // 1 2 3
	
	// 다른 타입, 타입 추론
	var name, age, price = "상품", 10, 9.99
	fmt.Printf("%s: %d개, $%.2f\n", name, age, price)
	// 상품: 10개, $9.99
}

블록 형식 선언

여러 변수를 그룹화하여 선언할 수 있습니다.

package main

import "fmt"

func main() {
	var (
		name    string = "Go"
		version float64 = 1.23
		stable  bool   = true
		count   int    // 제로값: 0
	)
	
	fmt.Printf("%s %.2f (안정: %t, 개수: %d)\n", name, version, stable, count)
}

2. 짧은 선언 (:=)

함수 내에서만 사용 가능한 간결한 선언 방법입니다.

변수명 := 
package main

import "fmt"

func main() {
	// 타입 추론과 함께 선언 및 초기화
	age := 25
	name := "홍길동"
	price := 19.99
	
	fmt.Printf("age: %T, name: %T, price: %T\n", age, name, price)
	// age: int, name: string, price: float64
}

다중 변수 짧은 선언

package main

import "fmt"

func main() {
	x, y, z := 1, 2, 3
	name, age := "Alice", 30
	
	fmt.Println(x, y, z)      // 1 2 3
	fmt.Println(name, age)    // Alice 30
}

재선언 규칙

:=는 새로운 변수를 선언하지만, 다중 할당에서 하나 이상의 새 변수가 있으면 기존 변수도 재할당됩니다.

package main

import "fmt"

func main() {
	x := 10
	fmt.Println("x:", x)  // 10
	
	// x는 이미 선언됨, y는 새 변수 → OK
	x, y := 20, 30
	fmt.Println("x:", x, "y:", y)  // 20 30
	
	// ❌ 에러: 모든 변수가 이미 선언됨
	// x, y := 40, 50  // no new variables on left side of :=
	
	// ✅ 재할당은 = 사용
	x, y = 40, 50
	fmt.Println("x:", x, "y:", y)  // 40 50
}

3. var vs := 비교

특징 var :=
사용 위치 패키지/함수 레벨 함수 내부만
타입 명시 가능 불가 (추론만)
제로값 초기화 가능 불가 (값 필수)
재선언 불가 조건부 가능
가독성 명시적 간결함
package main

import "fmt"

// 패키지 레벨 변수 - var만 가능
var globalVar = "전역"

func main() {
	// 함수 내부 - 둘 다 가능
	var explicitType int = 10
	inferredType := 20
	
	fmt.Println(globalVar, explicitType, inferredType)
}

사용 권장:

  • var 사용: 제로값으로 초기화, 타입을 명시하고 싶을 때, 패키지 레벨
  • := 사용: 함수 내부에서 초기값이 있고 타입이 명확할 때


제로값 (Zero Value)

Go는 선언만 하고 초기화하지 않은 변수에 제로값을 자동으로 할당합니다.

타입 제로값 설명
정수형 (int, int8, …, uint, …) 0  
부동소수점 (float32, float64) 0.0  
복소수 (complex64, complex128) 0+0i  
bool false  
string "" 빈 문자열
rune 0 널 문자
byte 0  
포인터 nil  
슬라이스 nil 빈 슬라이스로 동작
nil  
채널 nil  
함수 nil  
인터페이스 nil  
package main

import "fmt"

func main() {
	var i int
	var f float64
	var b bool
	var s string
	var r rune
	var slice []int
	var m map[string]int
	var ptr *int
	
	fmt.Printf("int: %d\n", i)              // 0
	fmt.Printf("float64: %f\n", f)          // 0.000000
	fmt.Printf("bool: %t\n", b)             // false
	fmt.Printf("string: '%s'\n", s)         // ''
	fmt.Printf("rune: %d\n", r)             // 0
	fmt.Printf("slice: %v (nil: %t)\n", slice, slice == nil)   // [] (nil: true)
	fmt.Printf("map: %v (nil: %t)\n", m, m == nil)             // map[] (nil: true)
	fmt.Printf("pointer: %v (nil: %t)\n", ptr, ptr == nil)     // <nil> (nil: true)
}

제로값의 실용성

제로값은 추가 초기화 없이도 안전하게 사용할 수 있도록 설계되었습니다.

package main

import "fmt"

func main() {
	var counter int  // 제로값: 0
	counter++
	counter++
	fmt.Println(counter)  // 2
	
	var builder string  // 제로값: ""
	builder += "Hello"
	builder += " "
	builder += "World"
	fmt.Println(builder)  // Hello World
	
	var numbers []int  // 제로값: nil, 하지만 append 가능
	numbers = append(numbers, 1, 2, 3)
	fmt.Println(numbers)  // [1 2 3]
}


변수 재할당

변수는 같은 타입의 값으로만 재할당 가능합니다.

package main

import "fmt"

func main() {
	var x int = 10
	fmt.Println("x:", x)  // 10
	
	x = 20  // 재할당
	fmt.Println("x:", x)  // 20
	
	// ❌ 타입 에러
	// x = "hello"  // cannot use "hello" (type string) as type int
	
	// ✅ 타입 변환 필요
	var y float64 = 3.14
	x = int(y)
	fmt.Println("x:", x)  // 3
}


변수 스코프

1. 패키지 레벨 변수

패키지 전체에서 접근 가능합니다.

package main

import "fmt"

// 패키지 레벨 변수
var globalCounter int = 0

func increment() {
	globalCounter++
}

func main() {
	fmt.Println(globalCounter)  // 0
	increment()
	fmt.Println(globalCounter)  // 1
}

공개 vs 비공개:

  • 대문자 시작: 다른 패키지에서 접근 가능 (공개)
  • 소문자 시작: 같은 패키지 내에서만 접근 (비공개)

2. 함수 레벨 변수

함수 내부에서만 접근 가능합니다.

package main

import "fmt"

func main() {
	x := 10  // 함수 레벨
	
	if true {
		y := 20  // 블록 레벨
		fmt.Println(x, y)  // 10 20
	}
	
	fmt.Println(x)  // 10
	// fmt.Println(y)  // 에러: undefined: y
}

3. 블록 레벨 변수 (Shadowing)

내부 블록에서 같은 이름의 변수를 선언하면 외부 변수를 가립니다.

package main

import "fmt"

func main() {
	x := "outer"
	fmt.Println("1:", x)  // outer
	
	{
		x := "inner"  // 새로운 변수 (shadowing)
		fmt.Println("2:", x)  // inner
	}
	
	fmt.Println("3:", x)  // outer (외부 변수)
}


언더스코어 변수 (_)

사용하지 않는 값을 무시할 때 사용합니다 (blank identifier).

package main

import "fmt"

func getValues() (int, string, bool) {
	return 42, "hello", true
}

func main() {
	// 첫 번째와 세 번째 값만 사용
	a, _, c := getValues()
	fmt.Println(a, c)  // 42 true
	
	// 에러 무시 (권장하지 않음)
	value, _ := getValue()  // 에러 체크 생략
	fmt.Println(value)
}

func getValue() (int, error) {
	return 10, nil
}


포인터 변수

변수의 메모리 주소를 저장하는 변수입니다.

package main

import "fmt"

func main() {
	var x int = 10
	var p *int = &x  // x의 주소를 저장
	
	fmt.Printf("x 값: %d\n", x)        // 10
	fmt.Printf("x 주소: %p\n", &x)     // 0x...
	fmt.Printf("p 값(주소): %p\n", p)  // 0x... (x의 주소)
	fmt.Printf("p가 가리키는 값: %d\n", *p)  // 10
	
	*p = 20  // 포인터를 통한 값 변경
	fmt.Printf("x 값: %d\n", x)  // 20
}


실전 예제

package main

import "fmt"

func main() {
	// 1. 사용자 정보 관리
	var (
		username string = "user123"
		email    string = "user@example.com"
		age      int    = 25
		verified bool   = true
	)
	
	fmt.Printf("사용자: %s (%s), 나이: %d, 인증: %t\n", 
		username, email, age, verified)
	
	// 2. 계산 결과 저장
	price := 100.0
	quantity := 3
	discount := 0.1
	
	total := float64(quantity) * price * (1 - discount)
	fmt.Printf("총액: $%.2f\n", total)
	
	// 3. 여러 반환값 처리
	name, score := getStudentInfo()
	fmt.Printf("학생: %s, 점수: %d\n", name, score)
	
	// 4. 제로값 활용
	var counter int  // 0으로 초기화
	for i := 0; i < 5; i++ {
		counter++
	}
	fmt.Printf("카운터: %d\n", counter)
}

func getStudentInfo() (string, int) {
	return "김철수", 95
}


자주 하는 실수

1. 선언했지만 사용하지 않음

// ❌ 컴파일 에러
func main() {
	x := 10  // declared and not used
}

// ✅ 해결: 변수 사용
func main() {
	x := 10
	fmt.Println(x)
}

2. := 패키지 레벨에서 사용

// ❌ 컴파일 에러
package main

x := 10  // syntax error: non-declaration statement

// ✅ 해결: var 사용
package main

var x = 10

3. 타입 불일치

// ❌ 컴파일 에러
func main() {
	var x int = "hello"  // cannot use "hello" as type int
}

// ✅ 해결: 올바른 타입 사용
func main() {
	var x string = "hello"
}

4. nil 슬라이스 vs 빈 슬라이스

package main

import "fmt"

func main() {
	var s1 []int         // nil 슬라이스
	s2 := []int{}        // 빈 슬라이스 (nil 아님)
	
	fmt.Println(s1 == nil)  // true
	fmt.Println(s2 == nil)  // false
	fmt.Println(len(s1), len(s2))  // 0 0
	
	// 둘 다 append는 가능
	s1 = append(s1, 1)
	s2 = append(s2, 1)
	fmt.Println(s1, s2)  // [1] [1]
}


베스트 프랙티스

  1. 의미 있는 이름: x, temp 대신 userCount, tempValue 사용
  2. 짧은 범위에서 짧은 이름: 반복문에서 i, j 사용은 OK
  3. 함수 내부에서는 :=: 간결하고 명확
  4. 패키지 레벨에서는 var: 명시적이고 초기화 선택 가능
  5. 제로값 활용: 기본값이 0이나 false가 적절한 경우 생략
  6. 타입 명시가 필요한 경우 var: float32, int8 등 구체적 타입
  7. 그룹화: 관련된 변수는 var ( ) 블록으로 묶기


참고 자료