Post

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.