패키지/임포트
- 모든 golang 프로그램은 패키지로 구성. 코드 시작 시 패키지 명시
import "{경로}/{패키지명}"
- 파이썬에서 _로 시작하는 인스턴스가 * import되지 않듯이, 대문자로 시작하는 것만 import됨
- 대문자로 시작하지 않는 것들은 외부 접근 불가
package main
# 이렇게
import "fmt"
import (
"fmt"
"net/http"
)
함수
- func로 선언,
({이름} {타입})
형식으로 매개변수 선언
- 같은 타입이면 마지막에만 타입을 명시하고 그 이전의 타입은 생략가능
- 복수 값 반환 가능
- 매개변수를 두번 선언하는 방식으로 매개변수와 반환 값 선언 가능
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
- 함수 자체로 값으로 취급해서 전달 가능, 반환 가능, 인수로 사용 가능
func add(a int, b int) {
return a + b
}
func calc(fn func(int, int) int) int {
return fn(3, 4)
}
변수
var {변수명} {타입}
변수명 := {변수값}
, 함수 안에서만 사용 가능
- 초깃값 사용 가능, 초깃값이 있다면 타입 생략 가능
- 없다면 기본값 있음(0, "", false 등)
- 기본 타입
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte
rune
float32 float64
complex64 complex128
타입({변수/값}
으로 타입캐스팅, 명시적인 캐스팅 반드시 필요
- 상수는
const
를 붙여서 선언, :=
사용 불가
반복문
- C 스타일의 for만 있음
(
, )
필요 없고, 중괄호 필수
- 초기화 및, iteration 후 구문은 필수가 아님 → while처럼 쓸 수 있음
- 조건이 없으면 무한루프
range
를 통해서 슬라이스(후술) 또는 맵(후술) 순회 가능
var nums = []int(1, 2, 3, 4, 5)
for i, value := range nums (
fmt.Printf("%d %d", i, v)
)
for _, value := range nums (
fmt.Printf("%d", value)
)
for i := range nums {
fmt.Printf("%d, i)
}
조건문
- C 스타일의 if
(
, )
필요 없고, 중괄호 필수
;
으로 비교 전 구문 사용 가능 (Walrus Operator와 유사) → 모든 else, 구문에서 사용 가능
if v := 1; v 10 {
return v
}
- C 스타일의 switch case default
- 위에서 아래 순서로 맞는 거 하나만 실행, break 자동
- switch 조건이 없는 것은 switch true와 같음
Defer
- 지정한 함수 인자만 평가하고, 실행은 함수 종료 후에 이루어짐
func main() {
defer fmt.Println("world")
fmt.Println("Hello ")
)
포인터
- C 스타일 사용법
- *붙여서 선언
- &를 붙여서 피연산자의 포인터 생성
- *를 붙여서 해당 포인터가 가리키는 주소에 저장된 값
- 포인터 산술을 지원하지 않음 → 뭔얘길까
구조체
- 필드의 집합
- {} 안에 값을 넣어서 초기화
- .으로 접근가능
배열
- 고정 길이의 값 집합
[{길이}]{타입}
으로 선언
- {} 안에 값 넣어서 초기화
var a [6]arr
슬라이스
- 가변 길이의 값 집합, 배열을 다루는 방식(보는 방식)
- 슬라이스를 수정하면 참조하는 배열의 요소가 수정됨
- 원본 배열의 크기를 초과할 수 없음. 이 제한 크기를 용량이라고 부름
- 파이썬과 같은 인덱싱 방법(음수 처리 제외)
- 슬라이스에 다시 인덱스해도 해당 슬라이스의 원본을 기준으로 인덱싱됨
- 그 자체로는 어떤 것도 저장하지 않음, 슬라이스만 선언해도 해당 슬라이스는 익명 배열을 참조하는 형태
- zero value는
nil
[]{타입}
으로 선언
nums := [6]int{1,2,3,4,5,6}
var slice []int = nums[1, 3]
- 내장
make
함수로 동적 배열 선언 가능. 이 함수는 그 동적 배열을 참조하는 슬라이스를 반환
a := make([]int, 5)
b := make([]int, 0, 5)
- 슬라이스는 모든 타입을 담을 수 있음(다른 슬라이스 포함)
- append 함수를 통해 슬라이스에 데이터를 추가할 수 있음
var s []int
s = append(s, 0)
s = append(s, 1)
s = append(s, 2, 3, 4)
맵
- 키 - 값 매핑
- zero value
nil
, 키/값 추가 불가
make
함수를 통해 초기화된 사용 가능한 맵 만들기 가능
type Point struct {
x, y int
}
var m map[string]int
m = make(map[string]int)
var m2 = map[string]Point{
"name": Point{1, 2},
"hello": Point{2,3},
}
var m3 = map[string]Point{
"name": {1, 2},
"hello": {3, 4},
}
m[key] = elem
elem = m[key]
delete(m, key)
elem, exists = m[key]
elem, exists := m[key]
메소드
- Golang은 클래스가 없지만 메소드는 사용 가능
- 메서드는
receiver
가 있는 함수(python의 self
에 해당)
type Point struct {
x, y float64
}
func (p Point) Abs() float64 {
return math.Sqrt(p.x * p.x + v.y + v.y)
}
- 구조체가 아니어도 메서드를 선언할 수 있음
- 보통 원본 수정이 필요해서 포인터로 선언하는 경우가 많음
- 포인터로 선언해도 값과 동일한 방식으로 리시버를 해석함
- 단 동일한 패키지에 유형이 정의되어야 함
type CustomFloat float64
func (f float64) Abs float64 {
if f < 0 {
return -f
}
return f
}
func (f CustomFloat) Abs float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
인터페이스
- 메서드 시그니처의 집합
- 제네릭 없음
- 명시적으로 어떤 것을 implement 했다고 적을 필요가 없음
- duck typing 방식, 호출 시 해당 타입이 해당 메서드가 있다면 해당 인터페이스를 구현한 것으로 침.
- 인터페이스의 값은 할당된 값과 그 값의 원본 타입의 튜플과 비슷하다.
- python에서
self
와 cls
두 가지를 동시에 가졌다고 이해하면 될 것 같다. Duck Typing 방식으로 인해 할당된 값(self
) 자체로는 메서드와 연결되지 않기 때문.
- 인터페이스의 값으로 메소드를 호출하면, 원본 타입의 메서드로 실행된다.
- 인터페이스의 할당된 값이 없다면 nil 리시버로 호출된다.
- 여기서 인터페이스의 값이 nil인 것은 아니다. 값이 0이고, 원본 타입은 있는 것이다.
- 인터페이스의 값이 nil이라면 메소드 호출 시 런타임 에러를 일으킨다.
- 메서드가 정의되지 않은 인터페이스를 빈 인터페이스라고 한다.
- 빈 인터페이스는 모든 타입의 값을 가질 수 있다.
- 알 수 없는 값을 처리하는데 사용된다. (
fmt.Printf
가 대표적)
{변수} = {인터페이스}.({타입})
으로 인터페이스의 할당된 값에 대한 접근이 가능하다.
{변수}
가 {타입}
이며, 그 값은 {인터페이스}
에 할당된 값이라고 선언하는 것.
- 해당 타입을 갖지 않을 경우 panic 상태가 된다.
var i interface{} = "hello"
s1 := i.(string)
s2 := i.(int)
s3, ok := i.(string)
s4, ok = i.(int)
- 타입 스위치를 쓸 수 있다. 일반 스위치문과 같지만, 인터페이스의 타입에 대한 switch다.
switch v := i.(type) {
case int:
...
case string:
...
default:
...
}
type Point struct {
x string
y int
}
func (p Person) String() string {
return fmt.Sprintf("Point (%v, %v)", p.x, p.y)
}
func run() error {
}
고루틴(Goroutine)
- Go 런타임이 관리하는 경량 쓰레드
- go 키워드로 함수를 실행해서 실행할 수 있으며, 매개변수 평가까지만 현재 고루틴에서 일어나고 함수 실행은 새 고루틴으로 위임됨
- 채널 연산자
<-
를 통해 값을 주고 받을 수 있음
- 채널 또한
make
함수로 생성 후 사용할 수 있음
ch := make(chan int)
- 파이썬
yield
문과 유사
- 전송과 수신은 다른 쪽이 준비될 때까지 블록 상태.
- 채널은 버퍼를 가질 수 있음. 송신 쪽에서는 차 있을 때만 블록되고, 수신 쪽에서는 비어 있을 때만 블록됨.
ch := make(chan int, 3)
- 전송 쪽에서 close 내장 함수를 통해서 채널을 닫을 수 있음
- 닫힐 채널에서 전송하면 panic이 발생
- 항상 닫을 필요는 없음. 수신 쪽에 더 이상 값이 없음을 알릴 때 사용
- range 문을 이용해서 채널이 닫힐 때까지 받아오도록할 수 있음
ch := make(chan int)
go func() {
for i := 0, i < 5; i++ {
ch <- i
}
close(c)
}()
for i := range ch {
fmt.Println(i)
}
select
- 다중 고루틴을 대기하는 문법
- 여러 case들 중에서 준비된 case를 실행, 준비된 것이 여럿이라면 무작위 case 수행.
select {
case <- a:
fmt.Println("case a")
case i := <- b:
fmt.Println("case b %v", i)
default:
fmt.Println("Nothing Finished")
}