19. 함수 고급편
19. 함수 고급편
✅ 1. 가변 인수 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "fmt"
func sum(nums ...int) int {
fmt.Printf("nums Type: %T\t", nums)
sum := 0
for _, v := range nums {
sum += v
}
return sum
}
func main() {
fmt.Println(sum(1, 2, 3)) // nums Type: []int 6
fmt.Println(sum()) // nums Type: []int 0
}
...
키워드를 이용해서 가변 인수를 처리할 수 있다- 인수 타입 앞에 붙이며, 가변 인수는 함수 내부에서 슬라이스 타입으로 처리된다
interface{}
타입으로 여러 타입의 가변 인수를 받을 수 있다1 2 3 4 5 6 7 8
func Print(args ...interface{}) string { for _, arg := range args { switch arg.(type) { case bool: ... } } }
✅ 2. defer 지연 실행
- 파일을 생성하거나 읽을 때 사용하는 파일 핸들은 OS 내부자원이므로 사용 후 OS에 돌려줘야 한다
- 이렇게 함수 종료 전에 처리해야 하는 코드가 있을 때
defer
를 사용하여 실행할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Create("test.txt")
if err != nil {
fmt.Println("Failed to create a File")
return
}
defer fmt.Println("반드시 호출됩니다")
defer f.Close()
defer fmt.Println("파일을 닫았습니다")
fmt.Println("파일에 Hello World를 씁니다")
fmt.Fprintln(f, "Hello World")
}
// 파일에 Hello World를 씁니다
// 파일을 닫았습니다
// 반드시 호출됩니다
defer
는 함수 종료 전에 호출되며 역순으로 호출된다
✅ 3. 함수 타입 변수
- 함수를 값으로 가지는 변수
CPU 내부 프로그램 카운터(PC)는 다음에 실행될 라인을 나타내는 레지스터다.
f()
함수가 호출되면 PC는 함수의 시작 포인트를 가리켜 명령어를 실행한다. 즉 함수 시작 지점 역시 숫자로 표현할 수 있으며, 이 시작 지점을 함수 포인터(function pointer)라고 부른다- 함수 타입은 함수명과 함수 코드 블록을 제외한 함수 정의로 표시한다
ex. func(int, int) int
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func mul(a, b int) int {
return a * b
}
func getOperation(op string) func(int, int) int { // 함수 정의(function signature)
switch op {
case "+":
return add
case "*":
return mul
default:
return nil
}
}
func main() {
var operator func(int, int) int
operator = getOperation("*")
fmt.Println(operator(2, 3)) // 6
}
- 함수 타입에도 별칭을 사용할 수 있으며, 함수 정의에서 매개변수명은 생략 가능하다
1 2
type opFunc func(int, int) int type withParameter func(a, b int) int
✅ 4. 함수 리터럴
- 함수 리터럴은 이름 없는 함수로, 함수명을 적지 않고 함수 타입 변수값으로 대입되는 함수값을 의미한다
- 함수명이 없어 함수명으로 호출할 수 없고 함수 타입 변수로만 호출된다
- 다른 프로그래밍 언어에서는 익명 함수 또는 람다라고 부른다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import "fmt"
type opFunc func(int, int) int
func getOperation(op string) opFunc {
switch op {
case "+":
return func(a, b int) int {
return a + b
}
case "*":
return func(a, b int) int {
return a * b
}
default:
return nil
}
}
func main() {
fn := getOperation("+")
if fn != nil {
fmt.Println(fn(1, 2))
}
}
- 함수명을 적지 않고 함수 리터럴로 반환한다
fn
함수타입 변수를 초기화하고 변수를 이용해서 함수를 호출한다- 함수 리터럴은 아래와 같이 직접 호출도 가능하다
1 2 3 4 5
result := func(a int, b int) int { return a + b }(3, 4) fmt.Println(result)
4.1 함수 리터럴 내부 상태
1 2 3 4 5 6 7 8 9 10 11 12
func main() { i := 0 f := func() { i += 10 } i++ f() fmt.Println(i) // 11 }
- 함수 리터럴 내부에서 외부 변수에 접근할 수 있다.
- 이 때 외부 변수는 값 복사가 아닌 인스턴스 참조 형태로 가져오게 된다
- 함수 리터럴에서 외부 변수를 가져오는 것을 캡쳐(capture)라고 한다
4.2 함수 리터럴 캡쳐 주의 사항 (1.22 이후 해결)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func CaptureLoop() {
f := make([]func(), 3)
fmt.Println("CaptureLoop")
for i := 0; i < 3; i++ {
f[i] = func() {
fmt.Println(i)
}
}
for i := 0; i < 3; i++ {
f[i]()
}
}
func main() {
CaptureLoop()
}
- 책에서는 위 코드가 3 / 3 / 3으로 출력될 것이라고 함
- for 문을 순회하면서 i값이 증가하고, 함수 리터럴에서 받는 i는 인스턴스 참조이기 때문에 최종적으로 i=3에서 멈추기 때문
- 위 현상은 1.22 버전 이후부터는 루프 변수를 반복마다 새 변수로 바꾸도록 수정되어 발생하지 않는다
4.3 의존성 주입
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"os"
)
type Writer func(string)
func WriteHello(writer Writer) {
writer("Hello World")
}
func main() {
f, err := os.Create("test.txt")
if err != nil {
fmt.Println("Fail")
return
}
defer f.Close()
WriteHello(func(msg string) {
fmt.Fprintln(f, msg)
})
}
WriteHello()
메서드 입장에서는 Writer 함수 타입 변수를 받아 호출할 뿐, Writer에 파일에 쓸지, 네트워크에 전송할지, 프린터로 찍을지 알 수 없다. 이처럼 외부에서 로직을 주입하는 것을 의존성 주입이라고 한다
This post is licensed under CC BY 4.0 by the author.