Go 언어 - 상수

검프·2021년 3월 21일
1

Go 언어 학습

목록 보기
3/9

The Ultimate Go Study Guide의 내용을 참고하여 작성했습니다.

상수

상수Constatns^{Constatns}는 한 번 할당된 값을 변경할 수 없는Immutable^{Immutable} 변수입니다.

상수const 키워드로 선언합니다. 고언어에서 상수는 반드시 컴파일 타임Compile time^{Compile\ time}에 실행 가능한 표현식이어야 하며, 런타임Run time^{Run\ time} 계산 결과는 상수로 할당할 수 없습니다.

// 상수는 컴파일 타임에 계산 가능한 표현만 가능
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
}

컴파일 시 상수 폴딩Constant folding^{Constant\ folding}이 진행되고 상수 표현식은 계산된 값으로 저장됩니다. 이 때문에 런타임 시 연산이 줄어들고 메모리 공간도 더 적게 사용하게되어 변수에 비해 더 빠르게 동작합니다.

// 작성한 코드
const seconds = 120
const minutes = seconds / 60

// 컴파일 후 계산된 값으로 저장됨
const seconds = 120
const minutes = 2

상수는 문자열, 숫자형, 불 타입만 사용 가능합니다. 배열, 슬라이스, 맵, 구조체 등을 상수로 할당할 수 없는 이유가 궁금했는데요, 이들 타입은 본질적으로 포인터Pointer^{Pointer} 이므로 주소가 가르키는 데이터가 변경될 수 있어서 상수의 불변성을 만족하지 않기 때문에 상수로 사용할 수 없습니다. 앞에 예제에서 documentDatabaseconst로 선언하면 컴파일 에러가 발생합니다.

Const initializer '[]string{...}' is not a constant

상수는 상수만을 위한 타입 시스템이 정의되어 있으며, 최소 정밀도Minimum precision^{Minimum\ precision}는 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

iota

Go 언어에서는 열거형Enum^{Enum} 타입을 지원하지 않습니다. 대신에 상수를 이용하여 열거형의 목적을 달성합니다. 이때 iota라는 상수 생성자Constant generator^{Constant\ generator}를 이용하면 순차적으로 증가하는 상수값을 쉽게 선언할 수 있습니다. 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
profile
권구혁

0개의 댓글