디스커버리 Go 언어
날짜: 기억이 안난다..
Chapter4. 함수
목차
값 넘겨주고 넘겨받기
- Go에서 함수(서브루틴)은 주로 스택으로 구현되어있음
-
Go에서는 값에 의한 호출(
Call By Value
)만 지원.- 함수 내에서 넘겨받은 변수값을 변경 하더라도 함수 밖에의 변수는 영향을 받지 않음
- 함수 밖의 변수 값을 변경 하려면 주소값을 넘겨 받아서 해당 주소에 값을 변경해야 함(
Call by Reference
)
값 넘겨주기
- Go는 값에 의한 호출만 지원하기 때문에 깞을 넘겨주어도 함수 밖 변수는 수정되지 않음
- 수정하기 위해서는 포인터를 넘겨줘서 해당 주소의 값을 변경해야 함
- 포인터로 넘어온 값은 *을 앞에 붙여서 값을 참조할 수 있음
- 변수 앞에 &를 붙이면 해당 변수에 담겨 있는 값의 포인터 값을 얻을 수 있음
- 결국 무엇이 값인지가 중요하며, 포인터 자료형으로 받더라도 그것은 주소값이 넘어와서 받는 포인터 변수가 담게 되는거로 이해하라
둘 이상의 반환값
- Go에서 함수는 둘 이상의 반환값을 리턴할 수 있음
- 반환값이 하나일 경우 괄호로 둘러쌀 필요가 없지만 둘 이상인 경우 괄호로 둘러싸야하며 반환값은 쉼표로 구별함
- 값을 받을 때에는 쉼표로 구분하여 반환값의 수에 맞게 받으면 됨
// 아래처럼 두 개 이상의 값을 리턴 할 경우 괄호를 써야 한다.
func WriteTo(w io.Writer, lines []string) (int64, error) {..}
에러값 주고 받기
-
에러를 표시 할 수 있는 방법은 다음과 같은 방법이 존재
- 정상적이지 않은 결과가 나올 경우 특정 값을 리턴하는 방식
- 호출 하는 쪽에서 에러값을 받고 싶은 변수의 포인터나 레퍼런스를 함수로 넘겨 받는 방식
-
Go에서는 관례적으로 에러는 가장 마지막 값으로 반환하며 이 에러를 돌려 받아서 처리하는 형식으로 작성 됨.
- 하지만 에러를 돌려받아서 처리 할 경우 반복적인 코드가 증가됨( 항상 에러를 체크해야 하므로.. )
- 다행히 아래와 같은 if문으로 코드를 조금이나마 줄일 수 있음
// 조건문 안에서 변수를 만들고, 조건문 안에서만 해당 변수를 사용할 수 있다. if err := MyFunc(); err != nil { return nil, err // 위 코드는 예외를 현재 문맥에서 처리 할 수 없는 경우 호출자에게 // 해당 에러를 그대로 반환하는 방식이다. }
-
새로운 에러를 생성 하는 방법은
errors.New
나fmt.Errorf
를 이용한다.// 1. errors.New return errors.New("blah blah blah") // 2. fmt.Errorf return fmt.Errorf("blah blah blah")
명명된 결과 인자
- 돌려주는 반환값에도 이름을 붙일 수 있다.
- 돌려주는 반환값은 기본값으로 초기화 된다.
- 함수가 끝나고 return 값을 생략하면 명명된 결과 인자들의 값이 리턴된다.
// 위에 예제와 같은 방식인데 n, err 로 넘겨준다는 뜻이다.
func WriteTo(w io.Writer, lines []string) (n int64, err error) {..}
가변인자
- 넘겨 받을 수 있는 인자의 개수가 정해져 있지 않을 경우에는 가변인자를 사용한다.
func WriteTo(w io.Writer, lines... string) (n int64, err error) {..}
값으로 취급되는 함수
- Go에서 함수는 일급 시민으로 분류된다. 이는 함수를 변수에 담을 수 있고 다른 함수로 넘기거나 돌려 받을수 있다는 것이다.
함수 리터럴
- 자바스크립트와 유사하다. 그냥 코드로 보자
// 1. 즉시 실행 함수
func(A, B) {
return A + B
}()
// 2. 함수 리터럴
adder := func(A, B) {
return A + B
}
고계 함수
- 고계 함수(
higher-order function
), 함수를 넘기고 받는 다는 의미이다. - 단순하게 생각하자. 자바스크립트처럼 함수의 인자에 함수를 넣어서 이를 활용한다 생각하면 된다.
클로저
- 클로저(
closure
), 외부에서 선언한 변수를 함수 리터럴 내에서 마음대로 접근할 수 있는 코드를 말한다. - 마찬가지로 자바스크립트로 생각하면 편하다.
생성기
- 클로저를 이용한 생성기(
generator
)가 소개되어 있다. - 그냥 코드로 생각하자.
func NewIntGenerator() func() int {
var next int
return func() int {
next++
return next
}
}
func ExampleNewIntGenerator() {
gen := NewIntGenerator()
fmt.Println(gen(), gen(), gen(), gen(), gen())
fmt.Println(gen(), gen(), gen(), gen(), gen())
// Output:
// 1 2 3 4 5
// 6 7 8 9 10
}
명명된 자료형
- 어떤 자료형을 새로운 이름으로 붙이는 것을 말한다. (EX
rune
은int32
의 별칭이다)
// type 예약어를 통해서 이름을 붙여줄 수 있다.
type rune int 32
- 자료형을 붙이면 프로그램을 직접 수행해보기 전에 컴파일 시점에서 버그를 찾을 수 있다.
- 서로다른 명명된 자료형끼리는 호환되지 않음
- 명명된 자료형을 이용하면 자료형을 하드 코드하는 것에 비하여 나중에 일괄적으로 해당 자료형의 표현을 변경할 수 있다
명명된 함수형
- 함수의 자료형도 사용자가 정의 할 수 있다.
- 함수 리터럴과 명명된 함수형 사이에는 자동으로 형변환이 이루어지지만 명명된 함수형 사이에서는 형변환이 일어나지 않는다.
인자 고정
- 흠 뭐라고 설명해야 하나.. 생략하자..
패턴의 추상화
- 일련의 코드를 추상화하는 과정이 나와있다.
- 과도한 추상화는 코드를 보기 어렵게 하지만, 자주 사용되는 패턴의 경우 추상화를 한다면 버그를 줄이고 더 이해하기 쉽게 한다.
자료구조에 담은 함수
- 자료구조에도 함수를 넣을 수 있다.
- 자바스크립트에서 오브젝에 함수 넣는거 생각하면 될것 같다.
// Map 에다가 함수를 담은 예제
opMap := map[string]BinOp{
"+": func(a, b int) int { return a + b },
"-": func(a, b int) int { return a - b },
"*": func(a, b int) int { return a * b },
"/": func(a, b int) int { return a / b },
}
메서드
- 함수에 리시버(
receiver
)가 붙으면 메서드가 된다. - 자료형 T에 대하여 메서드를 호출할 때 이 자료형 T에 대한 리시버가 함수 이름, 즉 메서드 이름 앞에 붙는다.
- 리시버란 개념을 잘 이해를 못하였는데.. 자료형마다 쓸수 있는 function으로 생각하자.
-
Go에서는 클래스가 없는 대신 리시버 인자를 갖는 함수로 메소드를 정의 할 수 있다.
- 잘 이해가 안되는데 아래에서 한번 참조하자.
- 프로그래머스
// 아래 코드에서 (recv T) 부분이 리시버 부분이다.
func (recv T) MethodName(p1 T1, p2 T2) R1
단순 자료형 메서드
- Go에서는 모든 명명된 자료형에서 메서드를 정의 할 수 있다.
type VertexID int
func ExampleVertexID_print() {
i := VertexID(100)
fmt.Println(i)
// Output:
// 100
}
문자열 다중 집합
- 자료형에 이름을 붙여야 메소드를 정의 할 수 있다
- 자료형을 붙임으로서 추상화를 설명하고 있음
포인터 리시버
- 포인터 리시버(
Pointer receiver
)는 자료형이 포인터형인 리시버를 말함 - 포인터로 전달해야 할 경우에는 포인터 리시버를 사용해야 한다.
공개 및 비공개
- 메서드의 이름이 대문자로 시작하면 해당 메서드는 다른 모듈에서 보이고 호출 가능
- 메서드의 이름이 소문자로 시작하면 다른 모듈에서 보이지 않는다.
- 위 법칙은 자료형, 변수, 상수, 함수에 모두 정의 된다.
- 한 모듈(package)은 여러 파일로 구성되며, 소문자로 시작하는 이름이라도 같은 모듈을 구성하는 다른 파일 에서도 보인다.
활용
타이머 활용하기
time.Sleep
함수를 사용하면 프로그램의 수행을 잠시 멈출 수 있다.- 넌블로킹을 동기(
synchronous
), 블로킹을 비동기(asynchronous
)라 한다. - Timer를 이용하면 넌 블로킹 타이머를 사용할 수 있다.
time.AfterFunc(5*time.Second, func() {
// 5초뒤에 실행해야 하는 코드
})
path/filepath 패키지
- 생략
정리
- 코드의 덩어리를 추상화 한것을
서브루틴
이라 한다. - 함수에 값을 넘겨주고 받을 수 있다.
- 포인터를 넘겨받으면 넘겨준 루틴의 변수의 값을 변경할 수 있다.
- 함수에서 받는 값은 여럿일 수 있고 받는 쪽에서 밑줄로 일부 값을 무시할 수 있다.
- 에러는 관례상 마지막 값으로 돌려준다.
- 가변 인자를 사용할때는 점 세개를 이용하며, 슬라이스에 점 세개를 이용하여 가변 인자처럼 넘겨줄 수 있다.
- 함수의 이름 부분을 삭제하면 익명 함수가 되어 함수 리터럴이 된다.
- 함수 리터럴은 값으로 취급되므로 변수에 담을 수 있고, 함수로 넘겨주고 넘겨받을 수 있다. 자료형에도 함수를 담을수 있다.
- 클로저는 함수 리터럴이 있는 스코프 내의 변수들에 접근 할 수 있다.
- 클로저를 이용하면 생성기 및 반복자를 만들 수 있다.
- 클로저를 이용하면 인자 고정을 할 수 있다.
- 자료형에 이름을 붙이면 명명된 자료형으로 구분하여 자료형 검사를 할 수 있다. 아울러 명명된 자료형에는 메서드도 정의할 수 있다.
- 함수로 자료이므로 함수형도 이름을 붙일 수 있다.
- 함수를 넘기고 받는 것을 활용하면 더 깊은 패턴의 추상화가 가능하다.
- 명명된 자료형에는 메서드를 정의할 수 있다.
- 메서드와 리시버는 값 또는 포인터를 사용할 수 있다. 포인터로 넘겨받아야 하는 경우에 포인터 리시버를 사용하면 된다.
- 패키지에 정의된 식별자 중에서 첫 글자가 대문자로 시작하는 것만 외부에서 접글 할 수 있다.