The Ultimate Go Study Guide의 내용을 참고하여 작성했습니다.
상수는 한 번 할당된 값을 변경할 수 없는 변수입니다.
상수는 const
키워드로 선언합니다. 고언어에서 상수는 반드시 컴파일 타임에 실행 가능한 표현식이어야 하며, 런타임 계산 결과는 상수로 할당할 수 없습니다.
// 상수는 컴파일 타임에 계산 가능한 표현만 가능 const seconds = 120 const minutes = seconds / 60 func addMinutes(minutes int) { // minutes는 런타임에 값이 변경되므로 minutes를 이용한 more 선언은 컴파일 에러가 발생 // Const initializer 'minutes' is not a constant const more = minutes + 60 return more }
컴파일 시 상수 폴딩이 진행되고 상수 표현식은 계산된 값으로 저장됩니다. 이 때문에 런타임 시 연산이 줄어들고 메모리 공간도 더 적게 사용하게되어 변수에 비해 더 빠르게 동작합니다.
// 작성한 코드 const seconds = 120 const minutes = seconds / 60 // 컴파일 후 계산된 값으로 저장됨 const seconds = 120 const minutes = 2
상수는 문자열, 숫자형, 불 타입만 사용 가능합니다. 배열, 슬라이스, 맵, 구조체 등을 상수로 할당할 수 없는 이유가 궁금했는데요, 이들 타입은 본질적으로 포인터 이므로 주소가 가르키는 데이터가 변경될 수 있어서 상수의 불변성을 만족하지 않기 때문에 상수로 사용할 수 없습니다. 앞에 예제에서 documentDatabase
을 const
로 선언하면 컴파일 에러가 발생합니다.
Const initializer '[]string{...}' is not a constant
상수는 상수만을 위한 타입 시스템이 정의되어 있으며, 최소 정밀도는 256 bit입니다.
상수는 선언 시 타입 선언 여부에 따라서 Typed, Untyped 상수로 구분합니다. 타입을 생략하면 컴파일러가 암묵적으로 특정 타입으로 변환합니다. Untyped 상수는 아래와 같이 선언합니다.
const ui = 12345 // 정수형 타입 처럼 동작 const uf = 3.141592 // 부동 소수점 타입 처럼 동작 const str = "Gump" // 문자열 타입 처럼 동작
Typed 상수는 상수 타입 시스템을 사용하지만 정밀도는 타입이 없는 상수에 비해서 떨어집니다.
const ti int = 12345 // int 타입 const tf float64 = 3.141592 // float64 타입 const str string = "Gump" // string 타입
Untyped 상수는 산술 연산에서 암묵적 형변환을 지원합니다. 고언어는 강타입 언어이기 때문에 산술 연산에서 타입이 서로 다르면 반드시 타입 케스팅을 통해서 타입을 동일하게 맞춰주어야 합니다. 하지만 Untyped 상수는 암묵적 형변환을 지우너하여 Typed 상수에 비해서 더 유연하게 사용이 가능합니다.
var answer = 3 * 0.333 // 가능 float64(3) * float64(0.333) fmt.Printf("%g, %T\n", answer, answer) <출력> 0.999, float64 --- const answer = 1 / 3.0 // float64(1) / float64(3.0) fmt.Printf("%g, %T\n", answer, answer) <출력> 0.3333333333333333, float64 --- const answer = 1 / 3 // int(1) / int(3) fmt.Printf("%d, %T\n", answer, answer) <출력> 0, int const num1 int64 = 5 const num2 int32 = 10 const sum = num1 + num2 // 컴파일 에러: Typed 상수간 산술 연산은 변수와 동일하게 동작 fmt.Printf("%d, %T\n", sum, sum) const num1 = 5 const num2 int32 = 10 const sum = num1 + num2 fmt.Printf("%d, %T\n", sum, sum) // 정상 동작: Untyped 상수가 암묵적으로 형변환되어 연산 가능, int32(5) + int32(10) <출력> 15, int32
타입을 정의한 상수와 정의하지 않은 상수를 계산하려면 자동 형변환이 가능한 유사한 타입이어야 합니다.
const one int8 = 1 const two = 2 * one // int8(2) * int8(1) fmt.Println(one) fmt.Println(two) <출력> 1 2
상수는 변수에 비해서 더 큰 값의 범위와 높은 정밀도를 지원합니다. 이 경우 출력을 할 수 없으나 선언과 연산에 사용 하는 것은 가능합니다.
const maxInt64 = math.MaxInt64 const greaterThanInt64 = 999999999999999999999999999999999999999999999 fmt.Println(maxInt64) fmt.Println(greaterThanInt64) // 출력 불가 <출력> constant 999999999999999999999999999999999999999999999 overflows int --- const maxInt64 = math.MaxInt64 const greaterThanInt64 = 999999999999999999999999999999999999999999999 const base = 333333333333333333333333333333333333333333333 fmt.Println(maxInt64) fmt.Println(greaterThanInt64 / base) // 연산 가능 <출력> 9223372036854775807 3
Go 언어에서는 열거형 타입을 지원하지 않습니다. 대신에 상수를 이용하여 열거형의 목적을 달성합니다. 이때 iota
라는 상수 생성자를 이용하면 순차적으로 증가하는 상수값을 쉽게 선언할 수 있습니다. iota
의 초기값은 0
이고 정수값입니다.
const ( A1 = iota // 0 : 0에서 시작한다 B1 = iota // 1 : 1 증가한다 C1 = iota // 2 : 1 증가한다 ) fmt.Println("1:", A1, B1, C1) <출력> 1: 0 1 2 --- const ( A2 = iota // 0 : 0에서 시작한다 B2 // 1 : 1 증가한다 C2 // 2 : 1 증가한다 ) fmt.Println("2:", A2, B2, C2) <출력> 2: 0 1 2 --- const ( A3 = iota + 1 // 1 : 1에서 시작한다 B3 // 2 : 1 증가한다 C3 // 3 : 1 증가한다 ) fmt.Println("3:", A3, B3, C3) <출력> 3: 1 2 3 --- const ( A3 = iota * 2 // 0 : 1에서 시작한다 B3 // 1 : 1 증가한다 C3 // 2 : 1 증가한다 ) fmt.Println("3:", A3, B3, C3) <출력> 3: 0 2 4 --- const ( A3 = iota * 2 + 1 // 0 : 1에서 시작한다 B3 // 1 : 1 증가한다 C3 // 2 : 1 증가한다 ) fmt.Println("3:", A3, B3, C3) <출력> 3: 1 3 5 --- const ( Ldate= 1 << iota // 1 : 오른쪽으로 0번 시프트 된다. 0000 0001 Ltime // 2 : 오른쪽으로 1번 시프트 된다. 0000 0010 Lmicroseconds // 4 : 오른쪽으로 2번 시프트 된다. 0000 0100 Llongfile // 8 : 오른쪽으로 3번 시프트 된다. 0000 1000 Lshortfile // 16 : 오른쪽으로 4번 시프트 된다. 0001 0000 LUTC // 32 : 오른쪽으로 5번 시프트 된다. 0010 0000 ) fmt.Println("Log:", Ldate, Ltime, Lmicroseconds, Llongfile, Lshortfile, LUTC) <출력> Log: 1 2 4 8 16 32
다음은 12개월을 상수로 표현하는 예시입니다.
type Month int const ( January Month = iota + 1 February March April May June July August September October November December ) var months = [...]string{ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", } // Stringer 인터페이스 구현 func (m Month) String() string { return months[(m-1)%12] } func main() { fmt.Println(March) } <출력> March