함수

함수 에서 Go

1개의 연습 문제

함수 소개

함수를 사용하면 코드를 재사용할 수 있는 단위로 묶을 수 있어요. 함수는 func 키워드, 함수 이름, 그리고 괄호 안에 쉼표로 구분된 0개 이상의 매개변수와 타입 목록으로 이루어져요.

함수 매개변수

모든 매개변수는 타입을 명시해야 해요. 매개변수에는 타입 추론이 없어요. 매개변수에는 기본값이 없어서 모든 함수 매개변수가 필수예요.

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// One parameter
func PrintHelloName(name string) {
  fmt.Println("Hello " + name)
}

같은 타입의 매개변수는 뒤에 타입을 한 번만 적어 함께 선언할 수 있어요.

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

매개변수와 인자

자주 헷갈리는 두 용어인 parameters와 arguments를 잠깐 짚어볼까요? 함수 매개변수는 함수 시그니처에 정의된 이름이에요. 위의 PrintGreetingName 함수에서 greeting과 name이 그 예예요. 함수 인자는 함수를 호출할 때 함수 매개변수에 전달되는 구체적인 값이에요. 예를 들어 아래 예제에서 "Hello"와 "Katrina"는 greeting과 name 매개변수에 전달되는 인자예요.

PrintGreetingName("Hello", "Katrina")

반환 값

함수 매개변수 뒤에는 0개 이상의 반환 값이 오는데, 이것도 타입을 명시해야 해요. 반환 값이 하나면 괄호 없이 쓰고, 여러 개면 괄호로 감싸요. 값은 return 키워드를 사용해 함수에서 호출한 코드로 반환돼요. 함수 안에는 여러 개의 return 문이 있을 수 있어요. 함수의 실행은 그중 하나의 return 문에 도달하는 즉시 끝나요. 함수에서 여러 값을 반환할 때는 쉼표로 구분해요. 여러 개의 반환 값을 관용적으로 사용하는 방법에 대한 더 자세한 내용은 링크된 개념에서 확인할 수 있어요.

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

함수 호출하기

함수를 호출할 때는 함수 이름을 쓰고, 괄호 안에 함수의 각 매개변수에 대응하는 인자를 전달해요.

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

이름 있는 반환 값과 naked return

매개변수와 마찬가지로 반환 값에도 이름을 붙일 수 있어요. 이름 있는 반환 값을 사용하면, 인자 없는 return 문이 그 값들을 반환해요. 이것을 'naked' return이라고 해요.

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

값에 의한 전달과 참조에 의한 전달

값에 의한 전달과 참조에 의한 전달이라는 개념도 짚고 넘어가는 게 좋아요.

먼저 값에 의한 전달을 살펴볼까요? 아래 예제에서 val 변수를 MultiplyByTwo 함수에 전달하면, val의 복사본이 전달돼요. 그래서 newVal은 갱신된 값 4를 갖지만 원래 변수 val은 여전히 2예요. 내부적으로 Go는 원본 값의 복사본을 만들어서, 이 복사본, 즉 v만 함수에 의해 수정돼요.

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

엄밀히 말하면 Go에서는 모든 인자가 값에 의해 전달돼요. 즉, 함수에 제공된 값이나 데이터의 복사본이 만들어져요. 하지만 함수에 전달된 데이터의 복사본을 만들지 않고 함수 안에서 데이터를 변경하고 싶다면, 인자로 pointers를 사용해야 해요. 이것이 참조에 의한 전달이에요.

포인터

참조에 의한 전달을 하려면 pointer를 사용해요. 함수에 pointer 인자를 전달하면, 데이터의 복사본에만 작업하는 대신 함수에 전달된 원본 데이터를 수정할 수 있어요.

지금은 함수 시그니처에서 타입 앞에 오는 *로 포인터 타입을 알아볼 수 있다는 것만 알아두면 충분해요.

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

pointer 개념이 헷갈려도 걱정하지 마세요. 실라버스 뒤쪽에 포인터를 완전히 익히는 데 도움이 되는 별도의 섹션이 있어요.

예외

slices와 maps는 앞서 언급한 규칙의 예외라는 점에 유의하세요. slice나 map을 함수의 인자로 전달하면, 타입에 명시적인 *가 없더라도 포인터 타입으로 다뤄져요. 즉, 슬라이스나 맵을 함수에 전달해서 그 원본 데이터를 수정하면, 변경 사항이 원래 슬라이스나 맵에 반영돼요.

다른 종류의 함수

Go에서 함수는 일급 시민으로 취급되기 때문에 매우 강력해요. 메서드와 익명 함수처럼 함수와 관련된 여러 다른 개념들이 있는데, 이는 앞으로의 여정에서 만나게 될 거예요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요

함수 배우기