GO

[Go] A Tour of Go - Basics (Packages, variables, and functions)

미소서식지 2023. 6. 24. 17:05

A Tour of Go

https://go.dev/tour/welcome/1

 

A Tour of Go

 

go.dev

다음주 수요일까지 이 튜토리얼을 쭉 따라가면서 코드를 따라 쳐보면서 개념을 익혀보기로 했다.

강의를 듣는 것도 좋지만, 강의는 이 튜토리얼을 한 번 쭉 해보고 듣는 것도 나쁘지 않을 것 같다.

일단.. 좀 많이 코드를 쳐보면서 쭉쭉 진도를 빼야 할 것 같다.

 

목차

오늘 정리하는 부분은 Basics의 Packages, variables, and functions 부분이다.

헷갈리거나 잘 몰랐던 부분만 정리해보도록 하겠다.

 

Package

Golang에서 패키지는 코드를 묶는 기본 단위이다. 따라서 모든 코드는 반드시 패키지로 묶어야 한다.

또한 Golang으로 프로그램을 작성한다면, 반드시 main 패키지가 존재해야 하며, main 패키지안에 main 함수가 정의되어야 한다. Golang에서는 이 main 패키지의 main 함수가 프로그램의 시작 위치가 된다.

 

Module

모듈은 패키지(Package)의 모음으로써, 한 개의 모듈은 다수의 패키지를 포함할 수 있다.

이런 모듈을 통해 Golang은 패키지들의 종속성을 관리할 수 있으며, 모듈은 패키지 관리 시스템으로써 활용이 된다.

모듈은 패키지를 트리 형식으로 관리하며, 루트(root) 폴더에 go.mod 파일을 생성하여 모듈을 정의하고, 종속성 정보를 관리하게 된다.

 

Exported Names

import 한 패키지 안의 메소드나 상수 등을 사용하고 싶으면 항상 대문자로 가져와야 한다.

 

package main

import (
	"fmt"
	"math"
)

func main() {
	// fmt.Println(math.pi) 
	// when you export from the package, you should start with capital letter
	// error undefined: math.pi (unexported)
	
	fmt.Println(math.Pi)
	
}

 

Multiple Results

한 함수는 몇 개의 결과든 반환 가능 (하나 또는 아무 것도 반환 안하는 C 언어와 다른 점)
return문을 사용하려면 반드시 Return type을 함수 선언부에 명시해야 함 (생략 불가)

 

package main

import "fmt"

func swap(x, y string) (string, string) { 
	return y, x // 두 개의 string 반환
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

 

Named Return values

Go 프로그래밍 언어에서 함수는 리턴값이 없을 수도, 리턴값이 하나 일 수도, 또는 리턴값이 복수 개일 수도 있다. 

C 언어에서 void 혹은 하나의 값만을 리턴하는 것과 대조적으로 Go 언어는 복수개의 값을 리턴할 수 있다.

Go에서 Named Return Parameter도 있다.
리턴되는 값들을 (함수에 정의된) 리턴 파라미터들에 할당할 수 있는 기능이다.
이는 리턴되는 값들이 여러 개일 때, 코드 가독성을 높이는 장점이 있다. 

 

가독성을 왜 높인다는 것이냐?
단지 함수의 signature만을 읽고도 return parameter들을 알 수 있기 때문이다.

 

쓰는 방식은 함수 정의에서 return 변수의 명시적 이름과 타입을 지정하고 이를 리턴 가능한데,
반드시 return을 써줘야 하고, return 만 써주면 반환이 된다.


그러나, := 이 사용 불가하다.
Go 컴파일러에 의해 선언부에 있는 return parameter들이 이미 선언되고 초기화되었기 때문에 오류가 발생하는 것이다.

따라서 (=)을 사용하여 명명된 반환 매개변수에 값을 할당 가능하다.

 

package main

import "fmt"

func split(sum int) (x, y int) { // 리턴해주는 것에 변수명과 타입을 반드시 지정해야 함
	x = sum * 4 / 9
	y = sum - x
	return // named return values를 통해 return만 쓰면 선언부의 return 값들이 반환됨
}

func main() {
	fmt.Println(split(17))
}

 

Type Safety

타입 안정성

프로그래밍 언어에서 변수나 표현식의 타입이 명확하게 지정되고, 타입 규칙을 준수해야 하는 특성을 말한다. 
타입 안정성은 잠재적인 타입 오류를 방지하고 프로그램의 안정성과 신뢰성을 높이는데 도움을 준다.
(타입 안정성 있는 언어에서는 변수 타입을 미리 지정함!)

타입 안정성이 있는 언어에서는 변수의 타입을 선언하거나 할당 시에 명시적으로 지정하며, 해당 타입과 일치하지 않는 타입의 값을 할당하거나 사용하려고 하면 "컴파일 오류"가 발생한다. 이는 변수와 값을 사용하는 시점에서 타입 호환성을 검사하여 타입 오류로 인한 문제를 "사전에 방지"한다.

 

Go 언어는 타입 안정성을 가진 언어로서,
변수의 타입은 명시적으로 선언되거나 컴파일러가 타입을 추론하여 결정한다.

 

Variables

Go에서 변수와 상수는 함수 밖에서도 사용할 수 있다.
또한 함수 밖에서의 패키지 레벨에서는 := (Short Assignment Statement)는 사용 못하고 var을 사용해야 한다.

함수 밖에서는 모든 선언이 키워드(var, func, 기타 등등)로 시작하기 때문이다.

만약 선언된 변수가 Go 프로그램 내에서 사용되지 않는다면, 에러를 발생시킨다. 따라서 사용되지 않는 변수는 프로그램에서 삭제한다.

동일한 타입의 변수가 복수 개 있을 경우, 변수들을 나열하고 마지막에 타입을 한번만 지정할 수 있다.

할당되는 값을 보고 그 타입을 추론하는 기능이 자주 사용된다. (타입 명시 없이 알아서 초기화 값을 보고 데이터 타입 지정해줌)

package main

import "fmt"

var c, python, java bool

func main() {
	var i int
	fmt.Println(i, c, python, java) // 선언하고 초기화하지 않으면 default 값 : 0, false, ""
	
	var j, k int = 2, 3
	fmt.Println(j, k)
	
	var s = "Hi" // 타입 선언 안해도 할당되는 값 보고 타입 추론해줌
	var z = 1
	fmt.Println(s, z)
	
	var p = 1
	// p := 1 // 변수 p가 이미 선언되었으므로 여기서 또 선언해줄 수 없음
	fmt.Println(p)
	
	q := 2
	fmt.Println(q)
}

 

Data Types

Integers

Signed Integers

  • int
  • 양수 값과 음수 값을 모두 저장할 수 있습니다.

 

package main
import ("fmt")

func main() {
  var x int = 500
  var y int = -4500
  fmt.Printf("Type: %T, value: %v", x, x)
  fmt.Printf("Type: %T, value: %v", y, y)

 

int Depends on platform:
32 bits in 32 bit systems and
64 bit in 64 bit systems
-2147483648 to 2147483647 in 32 bit systems and
-9223372036854775808 to 9223372036854775807 in 64 bit systems
int8 8 bits/1 byte -128 to 127
int16 16 bits/2 byte -32768 to 32767
int32 32 bits/4 byte -2147483648 to 2147483647
int64 64 bits/8 byte -9223372036854775808 to 9223372036854775807

 

 

Unsigned Integers

  • uint
  • 음수가 아닌 값만 저장할 수 있습니다.

 

package main
import ("fmt")

func main() {
  var x uint = 500
  var y uint = 4500
  fmt.Printf("Type: %T, value: %v", x, x)
  fmt.Printf("Type: %T, value: %v", y, y)
}

 

uint Depends on platform:
32 bits in 32 bit systems and
64 bit in 64 bit systems
0 to 4294967295 in 32 bit systems and
0 to 18446744073709551615 in 64 bit systems
uint8 8 bits/1 byte 0 to 255
uint16 16 bits/2 byte 0 to 65535
uint32 32 bits/4 byte 0 to 4294967295
uint64 64 bits/8 byte 0 to 18446744073709551615

 

Format Specifier

형식 지시자.

 

package main

import "fmt"

func main() {
	var i int
	var f float64
	var b bool
	var s string
	fmt.Printf("%v %v %v %q\n", i, f, b, s) // 0 0 false ""
}

 

  • %v
    • %v는 정수, 부동 소수점, 문자열, 배열, 맵 등의 값이 주어지면 해당 값을 기본 형식으로 출력한다.
    • 이는 Go의 기본 형식 출력 방식이다.
  • %q
    • 이 형식 지시자는 문자열 값을 출력할 때 사용된다.

 

Type conversions

명시적 타입 간 변환만 가능

C와 달리 Go는 다른 type의 요소들 간의 할당에는 명시적인 변환을 필요로 함!
즉, 작은 데이터 타입에서 범위가 큰 데이터 타입으로 데이터 타입 변환 시 자동 현변환이 안되는 것이다.
예를 들어 정수형 int에서 uint로 변환할 때, 암묵적(implicit) 변환이 일어나지 않으므로 uint(i) 처럼 반드시 변환을 지정해 주어야 한다.

만약 명시적 지정이 없이 변환이 일어나면 런타임 에러가 발생한다.

 

C 언어의 경우 암시적인 데이터 타입 변환이 가능한데, 아래 사례를 보면 알 수 있다.

 

int a = 5;
double b = a;  // 암시적인 타입 변환: int를 double로 변환하여 대입


int a = 5;
float b = 2.5;
float result = a + b;  // 암시적인 타입 변환: int를 float로 변환하여 연산 수행

 

그러나 GO 언어의 경우 모든 type conversion에 대해 명시적으로 표기를 반드시 해줘야 한다.

예시 코드는 아래와 같다.

 

package main

import (
	"fmt"
	"math"
)

func main() {
	var x, y int = 3, 4
	var f float64 = math.Sqrt(float64(x*x + y*y)) // int -> float64
	var z uint = uint(f) // float64 -> unit
	fmt.Println(x, y, z)
}

 

Type Inference

타입 유추

:= 혹은 var = 표현을 이용해 명시적인 type을 정의하지 않고 변수를 선언할 때, 그 변수 type은 오른편에 있는 값으로부터 유추된다.

 

하지만, 오른 편에 type이 정해지지 않은 숫자 상수가 올 때, 새 변수는 그 상수의 정확도에 따라 int 혹은 float64, complex128 이 된다.

이게 무슨 뜻이냐면, Go 언어에서 숫자 상수는 타입이 없는 상태로 존재한다. 근데 값에 따라 자동으로 Go 컴파일러는 변수의 타입을 결정한다는 것이다.

 

예를 들어,

42 는 int 타입으로 인지하고

3.14 는 float64 타입으로 인지하고

2 + 3i 는 complex128 타입으로 인지한다.

 

Constants

상수

상수는 := 를 사용하지 못한다.
그 이유는 상수의 경우 선언과 초기화가 동시에 한 줄에서 이루어져야 하기 때문인데, 이는 값의 불변성을 유지하기 위해서이다.

따라서 상수는 = 만 사용하여 선언과 동시에 초기화가 일어나야 한다.
이렇게 선언된 상수는 값을 변경할 수 없고, 컴파일 타임에 초기화되어 고정된 값을 가지게 된다.

상수는 변수처럼 선언되지만 const 키워드와 함께 선언됩니다.
상수는 character 혹은 string, boolean, 숫자 값이 될 수 있다.

package main

import "fmt"

const Pi = 3.14

func main() {
	const World = "世界"
	fmt.Println("Hello", World)
	fmt.Println("Happy", Pi, "Day")

	const Truth = true
	fmt.Println("Go rules?", Truth)
	
	
	const ( // var() 처럼 한 번에 여러 개의 변수 선언 가능!
		visa = "visa"
		master = "masterCard"
		amx = "american express"
	)
	
	const (
		Apple = iota
		Grape
		Orage
	)
	
	// visa = "hey"
	// 상수이므로 다시 초기화 불가능
	// cannot assign to visa (untyped string constant "visa")
	
	fmt.Println(visa)
}