Programming/Golang

Golang 기초 - III

chronosa 2021. 6. 30. 01:55

함수

1. defer (지연)

해당 함수가 실행을 완료했을 때 실행을 위해 호출 스케줄을 잡는다. defer는 어떤 식으로든 자원을 해제해야 할 때 자주 사용된다. 예를 들어, 파일을 열 때 나중에 해당 파일을 반드시 닫아야 한다. defer를 이용하면 다음과 같이 하면 된다.

f, _ := os.Open(filename)
defer f.Close()
이 방법에는 세 가지 장점이 있다. (1) Close 호출을 Open 호출 가까이에 둬서 이해하기가 쉽고, (2) 함수에 반환문이 여러 개 있더라도(if에 하나가 들어 있고, else에 하나가 들어 있는 것처럼) Close가 두 반환문 앞에서 모두 호출될 것이며, (3) 지연된 함수는 런타임 패닉이 일어나더라도 실행될 것이다.

 

2. panic & recover

python의 raise 및 Exception에 해당할 것으로 보인다.

import "fmt"
 
func main() {
    defer func() {
        str := recover()
        fmt.Println(str)
    }()
    panic("PANIC")
}

포인터

3. pointer & new

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&x)
    fmt.Println(x) // x는 0
}
Go에서 포인터는 *(애스터리스크) 문자 다음에 저장된 값의 타입으로 나타낸다. zero 함수에서는 xPtr이 int에 대한 포인터에 해당한다. *는 포인터 변수를 "역참조(dereference)"하는 데도 사용된다. 포인터를 역참조하면 해당 포인터가 가리키는 값에 접근할 수 있다. 
*xPtr = 0이라고 쓰면 "int 값 0을 xPtr가 참조하는 메모리 위치에 저장하라"라고 말하는 셈이다. 그렇게 하지 않고 xPtr = 0이라고 쓰면 컴파일로 오류가 발생하는데, xPtr은 int가 아니라 또 다른 *int만 할당할 수 있는 *int이기 때문이다.
마지막으로 변수의 주소를 구할 때는 & 연산자를 사용한다. &x는 *int(int에 대한 포인터)를 반환하는데, x는 int이기 때문이다. 이를 통해 원본 변수의 값을 변경할 수 있다. main 함수에 있는 &x와 zero 함수에 있는 xPtr은 동일한 메모리 위치를 참조한다.
func one(xPtr *int) {
   *xPtr = 1
}
func main() {
    xPtr := new(int)
    one(xPtr)
    fmt.Println(*xPtr) // x는 1
}

new는 인자로 타입을 하나 받아 해당 타입의 값에 맞는 충분한 메모리를 할당한 후 그것에 대한 포인터를 반환한다.

Go는 가비지 컬렉션을 지원하는 언어로서 new로 생성한 것을 아무것도 가리키는 것이 없으면 메모리가 자동으로 정리된다.

구조체와 메서드

구조체

type Circle struct {
    x, y, r float64
}

4. method

func (c *Circle) area() float64 {
    return math.Pi * c.r * c.r
}

func 키워드와 함수명 사이에 "수신자(receiver)"를 추가했다. 수신자는 이름과 타입이 있다는 점에서 매개변수와 비슷하지만 이런 식으로 함수를 생성하면 . 연산자를 이용해 해당 함수를 호출할 수 있다.

 

5. 포함 타입

type Android struct {
    Person
    Model string
}

a := new(Android)
a.Talk()

is-a 관계는 이처럼 직관적으로 동작한다. 즉, 사람은 이야기할 수 있고, 안드로이드는 사람이며, 따라서 안드로이드는 이야기할 수 있다.

 

 

6. 인터페이스

python의 추상 클래스에 해당하는 것으로 보인다.

 

구조체(struct)가 필드들의 집합체라면, interface는 메서드들의 집합체이다. interface는 타입(type)이 구현해야 하는 메서드 원형(prototype)들을 정의한다. 하나의 사용자 정의 타입이 interface를 구현하기 위해서는 단순히 그 인터페이스가 갖는 모든 메서드들을 구현하면 된다.

 

인터페이스

type Shape interface {
    area() float64
    perimeter() float64
}

인터페이스 구현체

//Rect 정의
type Rect struct {
    width, height float64
}
 
//Circle 정의
type Circle struct {
    radius float64
}
 
//Rect 타입에 대한 Shape 인터페이스 구현 
func (r Rect) area() float64 { return r.width * r.height }
func (r Rect) perimeter() float64 {
     return 2 * (r.width + r.height)
}
 
//Circle 타입에 대한 Shape 인터페이스 구현 
func (c Circle) area() float64 { 
    return math.Pi * c.radius * c.radius
}
func (c Circle) perimeter() float64 { 
    return 2 * math.Pi * c.radius
}

아래 예제에서 showArea() 함수는 Shape 인터페이스들을 파라미터로 받아들이고 있는데, 따라서 Rect와 Circle 처럼 Shape 인터페이스를 구현한 타입 객체들을 파라미터로 받을 수 있다. showArea() 함수 내에서 해당 인터페이스가 가진 메서드 즉 area() 혹은 perimeter()을 사용할 수 있다.

func main() {
    r := Rect{10., 20.}
    c := Circle{10}
 
    showArea(r, c)
}
 
func showArea(shapes ...Shape) {
    for _, s := range shapes {
        a := s.area() //인터페이스 메서드 호출
        println(a)
    }
}

 

7. empty interface (=dynamic type), type assertion

다양한 형을 담기 위한 dynamic type으로 사용된다.

메서드를 전혀 갖지 않는 빈 인터페이스로서, Go의 모든 Type은 적어도 0개의 메서드를 구현하므로, 흔히 Go에서 모든 Type을 나타내기 위해 빈 인터페이스를 사용한다. 즉, 빈 인터페이스는 어떠한 타입도 담을 수 있는 컨테이너라고 볼 수 있으며, 여러 다른 언어에서 흔히 일컫는 Dynamic Type 이라고 볼 수 있다.
Interface type의 x와 타입 T에 대하여 x.(T)로 표현했을 때, 이는 x가 nil이 아니며, x는 T 타입에 속한다는 점을 확인(assert)하는 것으로 이러한 표현을 "Type Assertion"이라 부른다.

 

참조

https://edu.goorm.io/learn/lecture/2010/%25ED%2595%259C-%25EB%2588%2588%25EC%2597%2590-%25EB%2581%259D%25EB%2582%25B4%25EB%258A%2594-%25EA%25B3%25A0%25EB%259E%25AD-%25EA%25B8%25B0%25EC%25B4%2588

 

한 눈에 끝내는 고랭 기초 - 구름EDU

이미 모두 갖추어진 실습환경에서 직접 코드를 작성하고 실행하며 Go lang의 기본을 다질 수 있는 프로그래밍 강좌입니다.

edu.goorm.io

 

https://edu.goorm.io/learn/lecture/2010/%ED%95%9C-%EB%88%88%EC%97%90-%EB%81%9D%EB%82%B4%EB%8A%94-%EA%B3%A0%EB%9E%AD-%EA%B8%B0%EC%B4%88/lesson/226534/%EB%A9%94%EC%86%8C%EB%93%9C%EC%9D%98-%EC%A7%91%ED%95%A9-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4