Yummy Story
Go 알아보기 - 키워드/연산자 본문
안녕하세요. 꿀맛규동 정동규입니다.
지난 Go에 대해 간단하게 개념을 알아본 게시글에 이어 Go를 사용하는 기초적인 방법에 대해 알아보려고 합니다.
부족한 부분이나 잘못된 부분이 있다면 가감없이 피드백 주시면 너무 감사드릴 것 같습니다!
기본적인 변수와 연산자 활용방법은 아래와 같습니다.
변수를 선언할 때, 아래와 같은 구조를 지켜 선언해야합니다.
- "var ${Variable Name} ${Variable Type}" & "${Variable Name} = ${Variable Value}"
- "var ${Variable Name} ${Variable Type} = ${Variable Value}"
- "var ${Variable Name} = ${Variable Value}" (Go 컴파일러가 자동으로 타입 지정)
상수를 선언할 때, 아래와 같은 구조를 지켜 선언해야합니다.
- "const ${Constant Name} ${Constant Type} = ${Constant Value}"
- "const ${Constant Name} = ${Constant Value}" (Go 컴파일러가 자동으로 타입 지정)
💻 예시 코드
package main // 반드시 실행파일의 package는 main이어야 합니다.
import "fmt"
func main() {
var a int // var 변수 선언
var b int
c := 10
const nameA string = "Donggyu" // const 상수 선언
const nameB string = "Seunghyun"
varName := "Example"
a = 3 // 변수 값 정의
b = 4
fmt.Println(a + b) // 7
fmt.Println(a + c) // 13
fmt.Println(b + c) // 14
fmt.Println(nameA) // Donggyu
fmt.Println(nameA + nameB) // DonggyuSeunghyun
fmt.Println(varName) // Example
}
💡 `:=` 연산자란?
Go 컴파일러가 해당 변수에 대한 키워드 생략과 함께 타입을 자동으로 부여해주는 편리한 연산자로 가독성을 증대시킬 수 있습니다.
(`:=` : 선언 + 할당 / `=` : 할당)
선언과 동시에 할당하는 방법이기 때문에 추후 해당 변수에 재사용은 불가능합니다. (변경의 경우, `=` 연산자를 사용해야 합니다.)
// Possible
var a int
a = 10
a = 15
// Possible
var b int = 20
b = 25
// Possible
var c = 30
c = 35
// Possible
d := 40
d = 50
// Impossible
e := 60
e := 70
💡 `:=` 연산자 사용 규칙
1. 함수(func) 영역 외 영역에서 사용 불가능
// Impossible
illegal := 42
// Possible
// `var` keyword 필요
var legal = 42
func foo() {
alsoLegal := 42
}
2. (동일 Scope 내) 중복 정의 불가능
":="는 "새 변수"를 나타내기 때문에 두 번 사용하더라도 다시 선언되는 것은 불가능합니다.
legal := 42
legal := 42 // <-- error
3. 다중 변수 선언 & 할당 가능
foo, bar := 42, 314
jazz, bazz := 22, 7
4. 변수 중 하나가 새로운 변수라면 다중 변수 선언 중복 사용 가능
기존 변수에는 새 값을 재할당하고 새 변수는 새롭게 선언하는 방식입니다.
foo, bar := someFunc()
foo, jazz := someFunc() // <-- jazz is new
baz, foo := someFunc() // <-- baz is new
5. 짧은 선언 → 새로운 범위에서 새롭게 변수 선언 가능
이미 동일한 이름으로 선언된 경우에도 짧은 선언(":=")을 통해 새로운 범위에서 선언 가능합니다.
var foo int = 34
/*
just reassigning new values to the existing variables
(+ declaring new variables at the same time)
*/
func some() {
// `foo` is in new scope of func
foo := 42 // <-- Possible
foo = 314 // <-- Possible
}
6. 짤은 명령어 블록에서 동일한 이름을 선언 가능
서로 다른 영역에 있는 것으로 취급됩니다.
foo := 42
if foo := someFunc(); foo == 314 {
// foo is scoped to 314 here
// ...
}
// foo is still 42 here
func
기본적으로 함수를 선언하는 방식입니다.
💻 예시 코드
/*
It's methodA
It's methodB
It's methodC
*/
func main() {
methodA()
println(methodB())
println(methodC("methodC"))
}
func methodA() {
println("It's methodA")
}
func methodB() string {
return "It's methodB"
}
func methodC(name string) string {
return "It's " + name
}
for (반복문)
Go 언어에서는 반복문은 "for" 한 종류뿐입니다.
또한 "range"를 통해 컬렉션을 활용할 수 있습니다. (인덱스까지 함께 반환합니다.)
💻 예시 코드
func main() {
sum := 0
for i := 1; i <= 10; i++ {
sum += i
}
println("sum :", sum) // sum : 55
i, sum := 0, 0
for i < 10 {
i++
sum += i
}
println("sum :", sum2) // sum : 55
}
func main() {
numbers := []int{1, 2, 3}
/*
0 1
1 2
2 3
*/
for index, num := range numbers { //range
println(index, num)
}
}
func main() {
numbers := []int{1, 2, 3}
/*
1 2
2 3
*/
for index, num := range numbers {
if index == 0 {
continue
}
println(index, num)
}
}
switch
변수 혹은 어떠한 값을 기준에 대한 케이스 별 조건문을 작성하는 방법입니다.
독특한 것은 앞선 case를 만족하더라도 아래의 case들을 "fallthrough"키워드를 통해 추가적으로 실행할 수 있습니다.
💻 예시 코드
func main() {
num := 1
// num is 1 or 2
switch num{
case 1, 2:
println("num is 1 or 2")
case 3, 4:
println("num is 3 or 4")
default: // case를 모두 만족하지 않을 경우
println("num is more than 5")
}
/*
num is less than 10
num is less than 8
num is less than 5
*/
switch { // 변수 생략 가능
case num < 10:
println("num is less than 10")
fallthrough
case num < 8:
println("num is less than 8")
fallthrough
default:
println("num is less than 5")
}
}
defer
Go의 독특한 키워드 중 하나로 함수 내에서 사용할 경우, 해당 키워드의 코드는 함수 내에서 제일 마지막에 실행되도록 합니다.
복수의 defer문 호출이 발생할 경우에는 LIFO 순서로 호출됩니다.
일반적으로 함수의 마지막 Clean-up 작업을 위한 용도로 사용되며, 가장 많이 사용되는 케이스는 파일 입출력이라고 합니다.
(ex. 파일 생성 코드 직후 defer 지연 호출로 close를 호출하면 함수가 끝날 때 무조건 파일을 닫을 수 있도록 하는 방식)
이러한 방식을 활용하면 Java의 try~ catch문에서의 finally와 같이 어떤 분기든 중복으로 반드시 처리되어야 하는 요소들을 defer를 통해 해결할 수 있습니다.
💻 예시 코드
func hello() {
println("Hello")
}
func world() {
println("World")
}
/*
Hello
Hello
World
Hello
*/
func main() {
defer hello() // 나중에 실행
defer world() // 먼저 실행
hello()
hello()
}
type
새로운 타입을 정의하는 방법으로서 "struct"와 "interface"키워드와 함께 사용됩니다.
- "struct" : 변수를 묶어 정의한 자료형
- "interface" : 메서드를 묶어 정의한 자료형
💻 예시 코드
package main
import "fmt"
type reserve interface {
twoday() int
}
type hotel struct {
name string
price int
}
type airbnb struct {
name string
price int
coupon int
}
func (h hotel) twoday() int {
return h.price * 2
}
func (a airbnb) twoday() int {
return a.price*2 - a.coupon
}
func main() {
a := hotel{"aaa", 1000}
b := airbnb{"bbb", 1000, 500}
makeReserve(a, b)
}
func makeReserve(r ...reserve) {
for _, v := range r {
fmt.Println(v.twoday())
}
}
(참고로 Go에서도 오버로딩이 지원됩니다.)
go & chan & select
- "go" : 해당 키워드로 함수를 호출하면 goroutine 실행
- "chan" : 채널 선언
- "select" : 다중 채널에서 대기 및 값 수신 이후 실행 (보장)
- select~ case 문의 채널에 값이 들어올 때까지 실행은 select문에서 블록이 됩니다.
- "default"를 추가해 다른 로직을 실행하게 할 수도, "default"를 제거해 계속 수신을 기다리도록 할 수 있습니다.)
Channel에 대해서는 추후에 더 자세히 알아보겠습니다.
(본 아티클에서는 간단하게 "값을 주고 받는 통로"라고 생각하면 좋을 것 같습니다.)
💻 예시 코드
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for{
time.Sleep(5 * time.Second)
c1 <- "one"
}
}()
go func() {
for{
time.Sleep(10 * time.Second)
c2 <- "two"
}
}()
for{
fmt.Println("start select------------------")
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
fmt.Println("end select-------------------\n\n")
}
}
import (
"fmt"
"time"
)
func process(ch chan string) {
time.Sleep(10 * time.Second)
ch <- "process successful"
}
func scheduling(){
//do something
}
func main() {
ch := make(chan string)
go process(ch)
for {
time.Sleep(1 * time.Second)
select {
case v := <-ch:
fmt.Println("received value: ", v)
return
default:
fmt.Println("no value received")
}
scheduling()
}
}
goto
특정 레이블로 이동시키는 키워드로 레이블들은 동일 Scope 함수 내에서 정의합니다.
주의할 점은 goto가 실행될 경우, 본래 호출했던 영역으로 돌아가지 않습니다.
콜백이 아닌 말 그대로 특정 레이블로 이동시키는 키워도로 이해하시면 좋을 것 같습니다.
💻 예시 코드
// TRUE 레이블 내 코드 실행
func main() {
num := 1
if num == 1 {
goto TRUE
} else {
goto FALSE
}
TRUE:
...
go END
FALSE:
...
END:
}
🚨 Run / Build 유의 사항
주의할 점은 프로그램이 처음 로드될 때, 기본적으로 "main" 함수를 기준으로 실행이 되고 이 실행파일의 package명 또한 "main"이 되어야 합니다.
즉, main 패키지 내부에 각 go 실행 파일 내부에는 main 함수를 기본적으로 선언하여 해당 파일이 실행될 수 있도록 해야합니다.
Spring 프레임워크에서의 public static void main()과 root 패키지의 의미와 동일하다고 생각하시면 될 것 같습니다.
(위 규칙이 지켜지지 않은채로 build 혹은 run을 실행했을 때, 아래와 같은 메시지와 함께 오류가 발생할 수 있습니다.)
/* package command-line-arguments is not a main package 혹은 package go-practice is not a main package */
간단하게 Go의 개념과 문법 구조를 알아봤습니다.
추후에 Go 학습을 거듭하면서 활용방법, 심화 문법과 서버 어플리케이션으로서의 기능과 구현 방법을 살펴보고 공유드리겠습니다.
감사합니다.
참고
- Go 공식 문서 - Tutorials
- 유튜브 :: Tucker Programming - 컴맹을 위한 Go언어 기초 프로그래밍 강좌
- Medium :: Short variable declarations rulebook
- Medium :: Learn Go Variables - A Visual Guide
- Tistory :: HAMA 블로그