프로그래밍 언어/Go

Go: 변수와 상수 (var, const)

Go 는 정적 타입 언어다

이번 포스트에서는 Go 에서 변수와 상수에 대해 알아보자. 변수를 코드에서 사용하고 활용하는 법에 대해 알아보기전에 먼저 알아야할 사항이 있다면, Go 는 정적 타입 언어라는 점이다. 정적 타입은 컴파일 타임에 타입이 결정되며 런타임 중에 동적으로 타입이 변경될 수 없음을 말한다. 인터프리터를 사용하는 동적 언어인 자바스크립트, 파이썬과는 전혀 다른 성격을 띄고 있다.

 

정적 타입을 가진 언어는 대체로 빠른 속도를 가진다. 런타임 중에 타입을 추론하거나 그로 인해 다시 한 번 최적화를 진행해야 하는 상황이 발생하지 않기 때문이다. 이러한 정적 타입 언어에는 대표적으로 C 가 있다. Go 는 구글이 개발했지만, 마이크로소프트 또한 타입을 무척이나 좋아하기 때문에 C/C++/C#/Typescript 모두 타입을 가지고 있다.

 

타입이 있으면 런타임 중에 발생할 수 있는 임의의 타입이 삽입되어 발생할 수 있는 위험에 대해 방지할 수 있으며 개발을 진행할 때도, 유지보수를 진행 할 때도 수월하게 진행할 수 있다. 타입이 함께 있기 때문에 이 변수는 어떤 값을 담고, 어떠한 형태를 담을 것인지 미리 개발자 입장에서도 추측이 가능하기 때문이다.

변수

변수는 값을 담는 공간이다. 모든 프로그래밍의 기본적인 사항이다. 변수의 이름은 숫자가 아닌 문자로 시작해야 한다. 물론 중간에는 숫자가 포함될 수 있다. Go 에서 변수를 선언하는 방법은 생각보다 다양한데, 글로 설명하기 보다는 코드를 보는 것이 빠르다. 변수를 사용할 때 또 하나의 주의점은, Go 는 사용하지 않는 변수가 있는 경우 에러를 뱉는다.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var message string
	message = "Hello, Go!"
	// -> string
	fmt.Println(reflect.TypeOf(message))

	var hi string = "Hi"
	// -> string
	fmt.Println(reflect.TypeOf(hi))

	var whoAreYou = "Who are you?"
	// -> string
	fmt.Println(reflect.TypeOf(whoAreYou))

	goodBye := "Goodbye"
	// -> string
	fmt.Println(reflect.TypeOf(goodBye))
}

var 키워드를 사용하여 변수를 먼저 선언하고 할당하기, 선언과 초기화를 함께 하기, 자료형을 생략하고 선언과 할당하기, 그리고 마지막으로 가장 많이 쓰이는 단축 변수 선언까지 있다. 타입이 명시되지 않은 경우에는 컴파일시 자동으로 타입을 추론한다. var 를 사용하여 변수를 할당없이 선언만 할 때는 자료형을 생략할 수 없다. 단축 변수 선언 구문을 쓸 때는 := 를 사용함을 눈여겨 보자.

전역에서 변수 선언하기

전역 스코프에서 변수를 선언할 때 주의해야 할 점이 있다면 전역에서는 단축 구문을 사용하면 문법 에러가 발생하며 변수를 선언하고 따로 할당하는 것도 전역에서 불가능하다.

package main

import (
	"fmt"
	"reflect"
)

var message string

var hi string = "Hi"
var whoAreYou = "Who are you?"

// -> Error
// goodBye := "Goodbye"

func main() {
	message = "Hello, Go!"
	// ...
}

이렇게 구성한 의도에 대해서 생각해보자. var whoAreYou = "Who are you?" 는 가능하면서 goodBye := "Goodbye" 는 안 되는 이유는? 내부의 동작 때문인가? A Tour of Go 에는 다음과 같이 언급되어 있다.

Outside a function, every statement begins with a keyword (var, func, and so on) and so the := construct is not available.

함수의 외부에서는 모든 문장이 var, func 와 같은 키워드로 시작하므로 키워드로 시작하지 않는 문장인 단축 선언은 함수 외부에서 사용할 수 없다는 이야기다. 의외로 간단하지 않은가.

여러 개의 변수 선언하기

변수를 다룰 때 보통 여러 개의 변수를 같이 만들어야 하는 경우를 이야기 하는데, Go 에서는 아래와 같이 할 수 있다.

package main

func main() {
	message, hi, whoAreYou, goodBye := "Hello Go!", "Hi", "Who are you?", "Goodbye"
}

변수를 여러 개를 선언하고 초기화할 때는 반드시 선언하는 변수와 초기화하는 값의 개수가 같아야 하며 타입은 굳이 똑같을 필요 없이 달라도 상관없다.

package main

import (
	"fmt"
	"reflect"
)

func main() {
	golang, java := "Fantastic", false
	// -> string, bool
	fmt.Println(reflect.TypeOf(golang), reflect.TypeOf(java))
}

var

아래의 상수에서도 똑같이 언급하겠지만, var 를 사용할 때 여러 개의 변수를 선언할 때 다른 방법으로 할 수도 있는데 아래의 코드를 딱 보면 알 수 있을 것이다.

package main

var (
	message   string
	hi        string
	whoAreYou string
	goodBye   string
)

func main() { 
	//...
}

변수 재할당과 선언

Go 에서 변수를 재할당과 선언을 하는 행위는 다소 특이하게 동작한다. 다음과 같이 말이다.

package main

import "fmt"

func main() {
	message := "Hello, Go!"
	message, java := "Fantastic", false

	// -> Fantastic, false
	fmt.Println(message, java)
}

위의 코드를 보면, message 라는 변수를 재선언하고 값이 바뀐 것을 볼 수 있다. 이는 정확히 말하자면 message 변수는 재선언이라고 볼 수 없다. 일반적으로 재선언이라고 하면 타입까지 함께 변경되어야 하지만, 사실은 이미 string 타입으로 고정되어 있기 때문이다. 하지만 문법상으로 보면 단축 변수 선언을 사용하고 있어서 재선언으로 보일 수 있는데, 다음과 같은 경우는 성립하지 않는다.

package main

import "fmt"

func main() {
	message := "Hello, Go!"
	// message := "Fantastic" // -> Error

	// -> "Hello, Go!"
	fmt.Println(message)
}

단축 변수 선언을 통해 여러 개의 변수를 함께 선언할 때, 하나라도 새로운 변수가 있으면 이는 단축 변수 선언 문법을 사용하더라도 기존 변수에 대해서는 재할당으로 처리된다. 처음 코드에서, java 라는 변수가 새롭게 추가면서 단축 변수 선언을 하였으므로 message 변수는 재할당, java 는 새로운 변수 선언 및 할당으로 처리된다. Go 에서는 함수가 여러 개의 값을 반환할 수 있는데, 그것을 위해 이렇게 처리하는 것이다. 함수에서 이에 대해 알게 될 것이다.

이름 쉐도잉

제목만 봐선 뭔지 모를 수 있는데, Go 에서는 패키지, 타입과 똑같은 이름의 변수를 선언해도 에러가 나지 않는다.

package main

import "fmt"

func main() {
	string := 10

	// -> 10
	fmt.Println(string)
}

이 상태로 string 타입의 변수를 선언하려고 들면 다음과 같은 에러를 뱉는다.

string is not a type
package main

func main() {
	string := 10
	var message string

	// ...
}

이것을 이름 쉐도잉이라고 하므로 패키지를 사용하거나 할 때 주의해야 한다.

제로 값

제로 값(Zero Value)변수를 선언만 하고 명시적으로 초기화를 하지 않은 상태에서 디폴트로 할당되는 값을 말하는데, 예를 들어 int 타입의 경우 0 이 제로값이다. 이러한 제로값은 이후에서도 등장할 예정이니 지금은 모든 자료형에 대해 알려고 할 필요는 없고, 개념에 대해서만 알아두면 된다.

package main

import "fmt"

var (
	myInt    int
	myFloat  float64
	myString string
	myBool   bool
)

func main() {
	// -> 0, 0, "", false
	fmt.Printf("%#v %#v %#v %#v", myInt, myFloat, myString, myBool)
}

상수

상수는 변수와 비슷하지만 한 번 선언되고 할당되면 값을 바꿀 수 없다. 선언과 할당을 따로 할 수는 없으며 선언을 할 때 초기화를 같이 해주어야 한다. const 키워드를 사용하면 상수를 선언할 수 있다. 자료형 명시는 선택적으로 해주면 된다. 어차피 컴파일러가 알아서 추론해줄 것이다.

package main

import "fmt"

func main() {
	const MAX_SIZE int = 10
	// MAX_SIZE = 20 // -> Error

	// -> 10
	fmt.Println(MAX_SIZE)
}

여러 개의 상수 선언하기

변수에서도 그렇듯 상수도 여러 개를 함께 선언 할 수 있다.

package main

func main() {
	const MAX_SIZE, LANG = 10, "Golang"
	// ...
}

const

상수도 변수처럼 여러 개를 같이 선언할 때 다른 형태로 선언할 수 있다. 다만, 이렇게 할 경우 할당까지 같이 해주는 것은 일반적으로 상수를 선언할 때와 마찬가지로 필수적으로 해야한다.

package main

import "fmt"

const (
	MAX_SIZE int    = 10
	LANG     string = "Golang"
)

func main() {
	// -> 10, Golang
	fmt.Println(MAX_SIZE, LANG)
}

변수와 상수 스코프

Go 의 변수와 상수 스코프패키지, 함수, 블록 스코프로 나누어 볼 수 있다. 패키지 수준의 스코프는 전역 변수와 비슷하고 함수와 블록 스코프는 지역 변수라고 생각하면 좋다. 상위 스코프에 선언된 변수는 하위 스코프에서 사용할 수 있지만, 반대로 하위 스코프에서 선언한 변수는 상위 스코프에서 사용할 수 없다.

package main

import "fmt"

var packageVar = "package"

func main() {
	var functionVar = "function"
	if true {
		var conditionVar = "conditinal"
		fmt.Println(packageVar)
		fmt.Println(functionVar)
		fmt.Println(conditionVar)
	}
	fmt.Println(packageVar)
	fmt.Println(functionVar)
	// fmt.Println(conditionVar) // -> Error
}

상위 스코프에 선언된 변수를 하위 스코프에서 재선언하면 하위 스코프에서 선언한 변수를 우선으로 취급하되 효력은 해당 스코프에서만 제한된다.

package main

import "fmt"

func main() {
	message := "Hello, Go!"
	if true {
		message := 10
		// -> 10
		fmt.Println(message)
	}
	// -> Hello, Go!
	fmt.Println(message)
}

'프로그래밍 언어 > Go' 카테고리의 다른 글

Go: 반복문 (for, range)  (0) 2020.11.25
Go: 조건문 (if, switch)  (0) 2020.11.25
Go: 패키지(package, import)  (0) 2020.11.23
Go: 함수 (func)  (0) 2020.11.21
Go: 변수와 상수 (var, const)  (0) 2020.11.19
Go: Hello, Go  (0) 2020.11.17