Golang 기본 문법

maintain·2021년 7월 21일
0

패키지/임포트

  • 모든 golang 프로그램은 패키지로 구성. 코드 시작 시 패키지 명시
  • import "{경로}/{패키지명}"
  • 파이썬에서 _로 시작하는 인스턴스가 * import되지 않듯이, 대문자로 시작하는 것만 import됨
    • 대문자로 시작하지 않는 것들은 외부 접근 불가
package main

# 이렇게
import "fmt"

// 권장되는 스타일, 'factored' import라 한다.
import (
	"fmt"
	"net/http"
)

함수

  • func로 선언, ({이름} {타입}) 형식으로 매개변수 선언
  • 같은 타입이면 마지막에만 타입을 명시하고 그 이전의 타입은 생략가능
  • 복수 값 반환 가능
  • 매개변수를 두번 선언하는 방식으로 매개변수와 반환 값 선언 가능
    // 매개변수는 sum, 반환은 x,y
    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 // uint8 별칭
    rune // int32 별칭, 유니코드의 code point?
    float32 float64
    complex64 complex128
    // int와 unintptr은 시스템의 비트 따라감
  • 타입({변수/값} 으로 타입캐스팅, 명시적인 캐스팅 반드시 필요
  • 상수는 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

  • 지정한 함수 인자만 평가하고, 실행은 함수 종료 후에 이루어짐
    • python에서 컨텍스트와 유사
    func main() {
    	defer fmt.Println("world")
    	fmt.Println("Hello ")
    )
    // Hello world

포인터

  • C 스타일 사용법
    • *붙여서 선언
    • &를 붙여서 피연산자의 포인터 생성
    • *를 붙여서 해당 포인터가 가리키는 주소에 저장된 값
    • 포인터 산술을 지원하지 않음 → 뭔얘길까

구조체

  • 필드의 집합
  • {} 안에 값을 넣어서 초기화
  • .으로 접근가능

배열

  • 고정 길이의 값 집합
  • [{길이}]{타입} 으로 선언
  • {} 안에 값 넣어서 초기화
    var a [6]arr
    

슬라이스

  • 가변 길이의 값 집합, 배열을 다루는 방식(보는 방식)
    • 슬라이스를 수정하면 참조하는 배열의 요소가 수정됨
    • 원본 배열의 크기를 초과할 수 없음. 이 제한 크기를 용량이라고 부름
    • 파이썬과 같은 인덱싱 방법(음수 처리 제외)
    • 슬라이스에 다시 인덱스해도 해당 슬라이스의 원본을 기준으로 인덱싱됨
  • 그 자체로는 어떤 것도 저장하지 않음, 슬라이스만 선언해도 해당 슬라이스는 익명 배열을 참조하는 형태
  • zero value는 nil
  • []{타입} 으로 선언
    nums := [6]int{1,2,3,4,5,6}

    // 슬라이스 2, 3
    var slice []int = nums[1, 3]
  • 내장 make 함수로 동적 배열 선언 가능. 이 함수는 그 동적 배열을 참조하는 슬라이스를 반환
    // 용량 제한 x, 크기 5
    a := make([]int, 5)

    // 용량 5, 크기 0
    b := make([]int, 0, 5)
  • 슬라이스는 모든 타입을 담을 수 있음(다른 슬라이스 포함)
  • append 함수를 통해 슬라이스에 데이터를 추가할 수 있음
    var s []int

    // nil 슬라이스에 사용 가능
    s = append(s, 0)

    // 용량이 최대라도 용량이 갱신된 슬라이스를 반환
    s = append(s, 1)

    // 한 번에 여럿 추가
    s = append(s, 2, 3, 4)

  • 키 - 값 매핑
  • zero value nil, 키/값 추가 불가
  • make 함수를 통해 초기화된 사용 가능한 맵 만들기 가능
    type Point struct {
    	x, y int
    }

    //nil 맵
    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)

    // 존재 확인하기
    // 없다면 exists false, elem은 Zero Value
    // 있다면 exists true, elem은 요소 값
    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에서 selfcls 두 가지를 동시에 가졌다고 이해하면 될 것 같다. Duck Typing 방식으로 인해 할당된 값(self) 자체로는 메서드와 연결되지 않기 때문.
    • 인터페이스의 값으로 메소드를 호출하면, 원본 타입의 메서드로 실행된다.
  • 인터페이스의 할당된 값이 없다면 nil 리시버로 호출된다.
    • 여기서 인터페이스의 값이 nil인 것은 아니다. 값이 0이고, 원본 타입은 있는 것이다.
  • 인터페이스의 값이 nil이라면 메소드 호출 시 런타임 에러를 일으킨다.
    • 구체적인 메소드를 알려줄 타입이 없기 때문.
  • 메서드가 정의되지 않은 인터페이스를 빈 인터페이스라고 한다.
    • 빈 인터페이스는 모든 타입의 값을 가질 수 있다.
    • 알 수 없는 값을 처리하는데 사용된다. (fmt.Printf 가 대표적)
  • {변수} = {인터페이스}.({타입})으로 인터페이스의 할당된 값에 대한 접근이 가능하다.
    • {변수}{타입}이며, 그 값은 {인터페이스}에 할당된 값이라고 선언하는 것.
    • 해당 타입을 갖지 않을 경우 panic 상태가 된다.
    var i interface{} = "hello"

    // s는 string타입이며 i 인터페이스의 값이 할당된다.
    s1 := i.(string)

    // i는 int타입이 아니므로 panic이 발생한다.
    s2 := i.(int)

    // s3는 string타입이며 i 인터페이스의 값이 할당되고, 인터페이스의 타입이
    // string 타입이 맞으므로 ok는 true가 된다.
    s3, ok := i.(string)

    // i는 int가 아니므로, ok는 false가 되고, s4엔 int의 Zero Value가 할당된다.
    // panic은 발생하지 않는다.
    s4, ok = i.(int)
  • 타입 스위치를 쓸 수 있다. 일반 스위치문과 같지만, 인터페이스의 타입에 대한 switch다.
    // 타입 자리에 type 명시해서 사용
    switch v := i.(type) {
    case int:
    	...
    case string:
    	...
    default:
    	...
    }
  • 예시
    type Point struct {
    	x string
    	y int
    }

    // fmt. 패키지의 Stringer
    // python의 __str__나 __repr__ 매직 메소드와 유사
    func (p Person) String() string {
    	return fmt.Sprintf("Point (%v, %v)", p.x, p.y)
    }

    // error 타입으로 에러를 표현함
    // 값이 nil인 에러는 성공
    func run() error {
    }

고루틴(Goroutine)

  • Go 런타임이 관리하는 경량 쓰레드
    • 파이썬의 비동기 제너레이터와 유사
  • go 키워드로 함수를 실행해서 실행할 수 있으며, 매개변수 평가까지만 현재 고루틴에서 일어나고 함수 실행은 새 고루틴으로 위임됨
  • 채널 연산자 <- 를 통해 값을 주고 받을 수 있음
    • 채널 또한 make 함수로 생성 후 사용할 수 있음
        ch  := make(chan int)
  • 파이썬 yield 문과 유사
    • 전송과 수신은 다른 쪽이 준비될 때까지 블록 상태.
    • 채널은 버퍼를 가질 수 있음. 송신 쪽에서는 차 있을 때만 블록되고, 수신 쪽에서는 비어 있을 때만 블록됨.
        // 버퍼 크기 3짜리 채널
        ch := make(chan int, 3)
  • 전송 쪽에서 close 내장 함수를 통해서 채널을 닫을 수 있음
    • 닫힐 채널에서 전송하면 panic이 발생
    • 항상 닫을 필요는 없음. 수신 쪽에 더 이상 값이 없음을 알릴 때 사용
    • range 문을 이용해서 채널이 닫힐 때까지 받아오도록할 수 있음
        // 0 1 2 3 4 출력
        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")
    }

0개의 댓글