[Go] 변수
개요
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]
}
베스트 프랙티스
- 의미 있는 이름:
x,temp대신userCount,tempValue사용 - 짧은 범위에서 짧은 이름: 반복문에서
i,j사용은 OK - 함수 내부에서는 :=: 간결하고 명확
- 패키지 레벨에서는 var: 명시적이고 초기화 선택 가능
- 제로값 활용: 기본값이 0이나 false가 적절한 경우 생략
- 타입 명시가 필요한 경우 var: float32, int8 등 구체적 타입
- 그룹화: 관련된 변수는 var ( ) 블록으로 묶기