Yummy Story

Go 알아보기 - 키워드/연산자 본문

Development

Go 알아보기 - 키워드/연산자

꿀맛규동 2025. 1. 24. 01:36

안녕하세요. 꿀맛규동 정동규입니다.

 

지난 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 학습을 거듭하면서 활용방법, 심화 문법과 서버 어플리케이션으로서의 기능과 구현 방법을 살펴보고 공유드리겠습니다.

 

감사합니다.

 


참고