티스토리 뷰

Programming Language/Go

[Go] 함수 & defer

SdardewValley 2021. 10. 20. 22:00
반응형
func 함수명(매개변수) (반환타입 또는 반환값) {
	...
}

Go에서 함수는 func 키워드 + 매개변수 + 반환타입을 순으로 선언한다.

 

매개변수를 생략할 수 있고, 반환값이 없을 때도 반환 값을 표기하는 부분이 없어도 된다.

 

func function(s string, a, b, c int) {
	...
}

변수 선언과 마찬지로 매개변수도 [변수 이름 + 타입]과 같이 표기한다. 여러 개의 매개변수를 선언할 때 같은 타입이 있다면 [매개변수1, 매개변수2, ..., 매개변수n, 변수타입]과 같은 형식으로 표기할 수 있다.

 

func function(integers ...int) {
	...
}

C++과 마찬가지로 go에도 가변 인자를 사용할 수 있다. 매개변수 타입 앞에 ...을 사용하여 여러 개의 값을 배열로 받을 수 있다.

 

import "fmt"

func return_one() int {
	return 1;
}

func return_two() (int, int) {
	return 1, 2;
}

func main() {
	fmt.Println(return_one())

	one, two := return_two()
	fmt.Println(one, two)
}

실행 결과

go에서 하나 이상의 값을 리턴할 수 있다. 단, 두 개 이상일 때는 반환값을 괄호로 묶어서 표기한다.

 

go의 함수에는 수행 결과와 에러 상태, 이렇게 두 개 이상 리턴하는 경우가 많다. 정상적으로 수행되면 nil을, 아닌 경우에는 에러 상태를 리턴한다.

 

문자열을 정수로 변환해 주는 Atoi 함수이다. 문자열을 받아서 int와 error 2개의 값을 리턴한다.

 

import (
	"fmt"
	"strconv"
)

func convertStringToInt(s string) {
	if v, e := strconv.Atoi(s); e != nil {
		fmt.Printf("%s는 정수가 아닙니다.\n", s);
	} else {
		fmt.Printf("%d는 정수입니다.\n", v);
	}
}

func main() {
	convertStringToInt("1024") // 1024는 정수입니다.
	convertStringToInt("hello") // hello는 정수가 아닙니다.
}

위의 코드를 보면 두번째 converStringToInt 함수를 호출할 때 hello는 nil이 아닌 error을 리턴한다. 따라서 두번째 호출해서는 "hello는 정수가 아닙니다."가 출력된다.

 

func return_numbers() (int, int, int) {
	return 1, 2, 3;
}

func main() {
	num, _, _ := return_numbers()
	fmt.Println(num)
}

go에는 _로 표기하는 blank identifier이 있다. go 컴파일러는 blank identifier을 변수로 인식하지 않는다.

 

return_numbers는 3개의 숫자를 리턴한다. 그리고 1만 필요할 때는 2와 3은 blank identifier을 사용하여 처리해주면 된다.

 

func returnNum() (num int) {
	num = 1
	return
}

func main() {
	num := returnNum()
	fmt.Println(num) // 1
}

함수 선언부에는 리턴 값의 이름과 타입이 명시되어 있고, 함수 바디에 return 키워드 뒤에는 값이 있지 않다. 위의 코드를 실행하면 1이 출력되는 것을 확인할 수 있다.

 

return 뒤에 따로 표기하지 않아도, 선언부에 리턴 값의 이름이 표기되어 있다면 해당 이름을 가지는 것이 리턴된다.

 

 

go는 call by value 방식으로 값을 호출한다.

💡call by value

함수를 호출할 때 인자에 변수 자체를 전달하는 것이 아닌, 값을 복사해서 전달하는 것

함수로 매개변수의 값을 변경하기 위해서는, 주소 연산자 &와 참조 연산자 *를 사용하면 된다.

 

func plusOne (num *int) {
	*num = *num + 1 // 포인터를 통한 값 참조
}

func main() {
	num := 99
	plusOne(&num) // 주소를 전달
	fmt.Println(num)
}

&를 사용하여 변수의 주소 값을 알 수 있고, *로 포인터를 만들어서 해당 값을 변경하면 된다.

 

func main() {
	plus := func(x, y int) int {
		return x + y
	}
	fmt.Println(plus(1, 2)) // 3

	func(x, y int) {
		fmt.Println(x + y) // 7
	}(3, 4)
}

go에서는 이름을 지정하지 않는 익명 함수, 클로저가 있다.

 

클로저를 활용해서 변수에 함수를 넣을 수도 있고, 바로 호출할 수도 있다.

 

func makeSuffix(suffix string) func(string) string {
	return func(name string) string { // 익명함수 호출
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

func main() {
	addZip := makeSuffix(".zip")
	addTgz := makeSuffix(".tar.gz")
	fmt.Println(addZip("go"))
	fmt.Println(addTgz("go"))
}

 

위는 factory method이다. makeSuffix는 익명 함수를 리턴하고, makeSuffix 함수로 addZip, addTgz 함수를 만들었다.

 

func printOne () {
	fmt.Println(1)
}

func printTwo () {
	fmt.Println(2)
}

func printN(print func()){
	print()
}

func main() {
	num := 1

	if num == 1 {
		printN(printOne)
	} else if num == 2 {
		printN(printTwo)
	}
}

go에서는 매개변수로 함수를 전달할 수 있다. 매개변수의 타입을 func()로 표현을 해줘야 한다.

 

go의 기본 라이브러리 중 strings.IndexFunc도 매개변수로 문자열과 함수를 받는다. 문자열에서 문자를 하나씩 차례대로 함수에 적용했을 때 true가 리턴되면, 해당 인덱스를 리턴한다.

 

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	f := func(c rune) bool {
		return unicode.Is(unicode.Hangul, c)
	}

	fmt.Println(strings.IndexFunc("Hello, 월드", f)) // 7
	fmt.Println(strings.IndexFunc("Hello, world", f)) // -1
}

 

f는 문자가 한글이면 true를 리턴한다. 한글이 있는 문자열은 인덱스를 리턴하였고 없는 문자열은 -1을 리턴하였다.

 

defer

defer 키워드가 붙은 구문은 스택에 차례대로 쌓였다가 함수가 종료되기 직전에 실행된다.

 

func stack() {
	for i := 0; i < 5; i++ {
		defer fmt.Printf("%d ", i)
	}
}

func def() {
	defer fmt.Print("Go!\n")
	fmt.Print("Hello ")
}

func main() {
	def() // Hello Go!

	stack() // 4 3 2 1 0
}

def 함수를 보면 Hello 출력하는 구문이 더 뒤에 있지만 Go를 출력하는 구문보다 먼저 실행되는 것을 알 수 있다.

 

stack 함수를 보면 처음에 나왔던 0이 가장 나중에 출력되고, stack에 호출된 구문들이 쌓이는 것을 알 수 있다.

 

defer을 사용하는 것 보다 처음에 순서를 잘 맞춰서 사용하면 좋을 것 같지만, defer은 사용하는 경우가 있다.

 

<defer을 사용하는 예>

  • 파일 스트림 닫기
  • 리소스 잠금 해제하기
  • 레포트에서 Footer 출력하기
  • 데이터베이스 커넥션 닫기

 

위의 경우 파일을 읽고, 그 밑에 줄에 defer을 사용하여 닫아주면 사용하기 편하고 가독성도 좋아진다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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 30
글 보관함