상수

상수는 Immutable(불변)한 특징을 갖으며 한 번 할당된 값을 변경할 수 없는 변수이다.



const a int = 1
const b string = "hi"
const d = 10  //자료형을 생략가능하다.
f := 10 //이는 자동으로 var 형태의 변수로 선언이 되기 때문에 상수가 될 수 없다.

var runTimeVar int = 1
const e = runTimeVar * 10  //runTimeVar는 런타임에 값이 할당되기 때문에 컴파일타임에 d의 값을 할당할 수 없어 error가 발생한다.

상수 키워드는 const로 선언방식은 변수 var와 같고 short assginment statement는 var키워드 전용이기 때문에 상수는 짧은 대입문 사용이 불가능 하다. 또한, 반드시 컴파일 타임에 실행이 가능한 표현식이어야만 한다.


const a,b,c  = 1, true, "hi"
const d = [5]int{1,2,3,4,5}  //error

상수에는 문자열, 숫자, bool타입만 가능하고, js같이 자료구조(배열,map 등)와 같이 내부적으로 포인터를 이용하는 타입들은 상수로 표현이 불가능하다. go에서 상수는 컴파일타임에 실행이 가능해야하기 때문에 내부의 값이 변할 수 있는 타입들은 상수로 표현이 불가능하다.


const a1 int = 1
const b1 float64 = 3

const sum1 = a1 + b1
fmt.Printf("%d, %T\n", sum, sum)  //error

const a2 = 1
const b2 = 3

const sum = a2 + b2
fmt.Printf("%d, %T\n", sum, sum)  //4, int

const casting = 1 / 3.0 //암묵적으로 형변환이 일어난다.
fmt.Printf("%g, %T\n", sum, sum)  // 0.333333.., float64

타입을 생략해서 표현할 수 도 있는데 이렇게 되면 실제로 사용되는 시점(명시적 형변환)전까지는 type이 존재하지 않는다.

그리고, 상수의 산술연산에서 암묵적 형변환을 지원해서 조금 더 유연하게 표현할 수 있다.



상수 사용시기

상수는 변수에 비해 매우 큰 표현 범위를 갖을 수 있어 높은 정밀도를 제공하기 때문에 보다 정확한 계산을 위해 사용되거나 열거형(Enum)을 만드는데 사용할 수 있다.

fmt (
  "fmt"
  "math"
)

func main(){
  const maxInt = math.MaxInt64  //int64의 최대값
  const maxConst = 1 << 100   //int 표현의 범위를 벗어났지만 실제 int로 명시하기 전에는 int타입이 아니기 때문에 표현이 가능하다.

  fmt.Println(maxInt)   //9223372036854775807
  fmt.Println(maxConst)  //함수로 표현하려고 하면 타입의 명시가 일어나 int범위를 벗어나 overflow가 발생한다.
  ftm.Println(maxConst * 0.1) //1.2676506002282295e+29  <-- 산술연산 중에 암묵적으로 float로 형변환이 일어나 overflow가 발생하지 않는다.
}


const i1 = 14345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345   //error

정수형 상수도 저장할 수 있는 한계가 존재하기는 한다.



Enum

Go는 Java나 C++ 처럼 enum을 제공하지 않기 때문에 C에서 enum을 비슷하게 만들어 사용하는 것처럼 go도 const를 이용해서 enum을 만들어 사용할 수 있다. 또한, iota라는 상수 생성자를 제공해서 순차적으로 증가하는 상수값을 쉽게 선언 할 수 있다.

const (
  a = 1   //1으로 시작
  b       //1
  c       //1
)
const (
  a = iota  //0으로 시작
  b         //1
  c         //2
)
const (
  a = iota + 1   //1으로 시작
  b              //2
  c              //3
)
const (
  a = 1 << iota   //1
  b               //2
  c               //4
  d               //8
)



상수와 리터럴

리터럴은 변수의 값이 변하지 않는 데이터로 메모리 안의 존재하는 값 그자체를 의미한다.

var a int = 1

변수를 할당하는 데 있어 주로 우측에 오는 값으로 1이라는 값 그 자체는 변경할 수 없는데 이를 리터럴이라고 한다. 1이라는 값이 2가 될 수 없는 것이다.


var s string = "ex"

string의 "ex"도 리터럴인데 ex라는 값이 heap에 한번 할당되고 값이 변경되지 않기 때문이다. 우리가 string변수를 연산자를 이용해서 문자열을 붙이는 것은 기존의 ex라는 값에 붙이는 것이 아니라 heap공간에 ex+새로운 문자열을 새로 할당하는 방식으로 동작하기 때문에 string의 우측에 오는 값도 리터럴이다.


리터럴도 변하지 않는 값이기 때문에 상수와 햇갈릴 수 있지만, 상수는 값을 한번만 저장하고 변경이 불가능한 메모리 공간이고, 리터럴은 값 그자체로 변경이 불가능한 이다.




Reference

https://tramamte.github.io/2018/07/03/deep-inside-constants/

『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트