본문 바로가기

GO

[Go] A Tour of Go - Basics (Function, Method and Interface)

Function

함수는 여러 문장을 묶어서 실행하는 코드 블럭의 단위이다.

Go에서 함수는 func 키워드를 사용하여 정의한다.

func 뒤에 함수명을 적고 괄호 ( ) 안에 그 함수에 전달하는 파라미터들을 적게 된다. 

각 파라미터는 파라미터명 뒤에 int, string 등의 파라미터 타입을 적어서 정의한다.

함수의 리턴 타입은 파라미터 괄호 ( ) 뒤에 적게 되는데, 이는 C와 같은 다른 언어에서 리턴 타입을 함수명 앞에 쓰는 것과 대조적이다.

함수는 패키지 안에 정의되며 호출되는 함수가 호출하는 함수의 반드시 앞에 위치해야 할 필요는 없다.

 

package main
func main() {
    msg := "Hello"
    say(msg)
}
 
func say(msg string) {
    println(msg)
}

 

Pass By Reference vs. Pass By Value

Pass By Reference

위의 예시에서는 msg 변수의 값이 복사되어 함수 say()에 전달된다.

따라서 main() 에서의 msg 변수는 변함이 없다.

 

Pass By Value

msg 변수 앞에 & 부호를 붙이면 msg 변수의 주소를 표시하게 된다.

흔히 포인터라 불리우는 이 용법을 사용하면 함수에 msg 변수의 값을 복사하지 않고 msg 변수의 주소를 전달하게 된다.

피호출 함수 say()에서는 *string 과 같이 파라미터가 포인터임을 표시하고 이때 say 함수의 msg는 문자열이 아니라 문자열을 갖는 메모리 영역의 주소를 갖게 된다.

msg 주소에 데이터를 쓰기 위해서는 *msg = "" 과 같이 앞에 *를 붙이는데 이를 흔히 Dereferencing 이라 한다.

즉, msg가 가리키는 메모리의 값을 바꾼다는 것.

 

cf. 역참조

역참조라는 거는 해당 메모리가 가리키는 값을 가져오는 것이다.

 

따라서, 위의 예시에서 *p = 21 을 입력하면, p가 가지는 메모리 주소가 가리키는 변수의 값을 가져오게 되고, 변수 값을 바꾼다는 것이다.

즉, 만약 해당 값을 변경한다면, 메모리 위치가 가리키는 값 자체가 변경된다.

 

(메모리 주소는 그대로이지만, 그 메모리 주소가 가리키는 값이 달라진다는 것)

 

package main
func main() {
    msg := "Hello"
    say(&msg) // msg 변수의 주소를 전달
    println(msg) //변경된 메시지 출력
}
 
func say(msg *string) { // 포인터 변수로 받음
    println(*msg) // *포인터 변수는 역참조이므로 "Hello"를 반환
    *msg = "Changed" //메시지 변경
}

 

Variadic Function (가변인자함수)

다양한 숫자의 파라미터를 전달하고자 할 때 가변 파라미터를 나타내는 ... (3개의 마침표)을 사용

 

package main
func main() {   
    say("This", "is", "a", "book")
    say("Hi")
}
 
func say(msg ...string) { // 가변인자함수
    for _, s := range msg { // idx, variable
        println(s)
    }
}

 

함수 리턴값

Go 프로그래밍 언어에서 함수는 리턴값이 없을 수도, 리턴값이 하나 일 수도, 또는 리턴값이 복수 개일 수도 있다.

C 언어에서 void 혹은 하나의 값만을 리턴하는 것과 대조적으로 Go 언어는 복수개의 값을 리턴할 수 있다.

Go 언어는 또한 Named Return Parameter 라는 기능을 제공하는데, 이는 리턴되는 값들을 (함수에 정의된) 리턴 파라미터들에 할당할 수 있는 기능이다.

 

package main
 
func main() {
    total := sum(1, 7, 3, 5, 9)
    println(total)
}
 
func sum(nums ...int) int { // 가변 인자 함수, int 타입 리턴
    s := 0
    for _, n := range nums {
        s += n
    }
    return s // 리턴값
}

 

Go에서 복수 개의 값을 리턴하기 위해서는 해당 리턴 타입들을 괄호 ( ) 안에 적어 준다.

예를 들어, 처음 리턴값이 int이고 두번째 리턴값이 string 인 경우 (int, string) 과 같이 적어 준다.

 

package main
 
func main() {
    count, total := sum(1, 7, 3, 5, 9)
    println(count, total)   
}
 
func sum(nums ...int) (int, int) { // 복수 개의 값을 리턴 (int, int)
    s := 0      // 합계
    count := 0  // 요소 갯수
    for _, n := range nums {
        s += n
        count++
    }
    return count, s // 복수 개의 값을 리턴 (int, int)
}

 

Go에서 Named Return Parameter들에 리턴값들을 할당하여 리턴할 수 있는데, 

이는 리턴되는 값들이 여러 개일 때, 코드 가독성을 높이는 장점이 있다.

함수 내에서는 이 count, total에 결과값을 직접 할당하고 있음을 볼 수 있다.

또한 마지막에 return 문이 있는 것을 볼 수 있는데, 실제 return 문에는 아무 값들을 리턴하지 않지만,

그래도 리턴되는 값이 있을 경우에는 빈 return 문을 반드시 써 주어야 한다 (이를 생략하면 에러 발생).

 

func sum(nums ...int) (count int, total int) { // Named Return Parameter
    for _, n := range nums {
        total += n // total 변수를 함수 내에서 직접 할당
    }
    count = len(nums)// count 변수를 함수 내에서 직접 할당
    return // return 값이 있으므로 비어있는 return 문을 반드시 써주어야 함!
}

 

근데 조금 신기해보이는 건, 위의 예시 코드에 return 문 다음에 아무것도 없는데 알아서 함수 내부에서 

초기화된 count와 total이 리턴된다는 것이다.

만약 return문 다음에 별도의 표현식이 없다면, Named Return Parameter에 할당된 변수들이 반환값으로 사용된다.

내부적으로는 Named Return Parameter를 사용하면 함수 호출 시에 지정된 변수들이 함수 스코프 내에서 선언되고 초기화된다.

함수 본문에서 변수들에 값이 할당되면, 해당 변수들의 값은 함수 호출이 끝난 후에 반환된다.

 

한편, 다른 예시코드도 보자

 

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 { // named parameter fn 
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

 

위의 함수에서, fn은 named parameter이고, 해당 변수의 타입은 func(float64, float64)이다. 그리고 리턴 타입은 float64인 함수이다. 

따라서, 정리하면 compute 함수의 매개변수로 "함수"를 받는다는 것이다. compute()의 리턴타입 또한 float64이다.

그래서 compute 내부에선 매개변수로 받은 함수를 실행하게되는 것이다.