golang 기초 - 함수

한나리·2023년 9월 25일

Go

목록 보기
9/19
post-thumbnail

함수

다른 어떤 언어들 처럼 자주 사용되는 코드를 묶어서 함수를 만든다. 그러면 같은 기능을 매번 다시 만들 필요 없이 재사용할 수 있다.

함수란 특별한 목적의 작업을 수행하는 코드 묶음이다. 일정 범위의 코드를 묶어서 함수를 만들면 같은 코드를 여러 번 작성하지 않고 한 번만 작성해 재활용할 수 있다.

함수의 장점

  • 코드의 재사용성이 높아진다.
  • 코드 가독성이 높아진다.
  • 코드를 유지보수하기 편해진다.

함수의 정의

함수는 함수 키워드, 함수명, 매개변수, 반환 타입, 함수 코드 블록으로 구성된다.
함수의정의

  • 함수 키워드 : func 함수 정의를 알림
  • 함수명 : 함수 이름
  • 매개변수 : 함수 코드 수행시 필요한 입력 값, 필요하지 않으면 비워둠
  • 반환 타입 : 반환하는 값이 있으면 적고, 아니면 비워둠
  • 함수 코드 블록 : 중괄호로 함수 코드 블록을 표시. Go언어에는 함수 코드 블록의 시작을 알리는 중괄호 {가 함수를 정의하는 라인과 항상 같은줄에 있어야 함

    함수명과 변수명 명명 규칙 :
    첫 글자가 대문자인 함수는 패키지 외부로 공개되는 함수

package main

import "fmt"

func Add(a int, b int) int {
	return a + b
}

func main() {
	c := Add(3,6)
    fmt.Println(c)    
}

함수 호출

  • 인수(argument) : 함수를 호출할 때 입력하는 값
  • 매개변수(parameter) : 함수가 외부로부터 입력받는 변수

함수를 호출할때 입력값으로 쓰인 인자는 그대로 사용되는 것이 아니라 값이 복사되어 사용된다.
다음과 같이 Add()함수를 호출할 때 인수가 매개변수에 복사되어 전달된다.

package main

import "fmt"

func Add(a int, b int) int { // 2️⃣ 매개변수 생성 및 초기화
	return a + b // 3️⃣ 값 반환 (복사)
}

func main() {
	c := Add(3,6) // 1️⃣ 함수 호출
    fmt.Println(c)    
}

1️⃣ Add()함수를 호출한다.
2️⃣ 매개변수를 선언하고 입력한 인수의 값을 복사한다. 여기서는 3과 6이 a와 b에 값으로 복사된다.
3️⃣ return 키워드를 사용해서 함수 결과가 반환된다.

package main

import "fmt"

func Add(a int, b int) int {
	return a + b 
} // 5️⃣ 로컬 변수 삭제

func main() {
	c := Add(3,6) // 4️⃣ 복사
    fmt.Println(c)    
}

4️⃣ 반환된 값은 함수가 호출된 곳을 대체하는 것과 같다.
5️⃣ 호출한 함수가 종료되면 함수에서 사용한 지역변수에 접근할 수 없다. return으로 함수 결과가 반환되면서 함수가 즉시 종료되어 함수를 호출했던 호출 위치로 명령 포인터가 되돌아가서 수행된다.

명령 포인터 (Instruction Pointer) :
명령 포인터 혹은 프로그램 카운터로 불리는 것으로, 다음 명령을 수행할 위치를 나타내는 내부 레지스터

package main

import "fmt"

func Add(a int, b int) int {
	return a + b 
} 

func main() {
	c := Add(3,6) // 6️⃣ 대입 연산
    fmt.Println(c) // 9   
}

6️⃣ c에 반환값(9)이 대입(복사)된다.

"인수는 매개변수로 복사"된다는 뜻은, "매개변수와 함수 내에서 선언된 변수는 함수가 종료되면 변수 범위를 벗어나서 접근하지 못한다"와 같은말이다.

함수를 쓰는 이유

함수를 쓰면 반복 사용되는 코드를 묶을 수 있다. 함수를 이용해서 중복 코드를 제거하여 코드를 간결하게 만들어 가독성을 높일 수 있고 유지보수 또한 용이해진다.

다음의 코드는 수학, 영어, 역사 시험 성적의 평균 점수를 출력하는 예제이다.

package main

import "fmt"

func main() {
	math := 80
    eng := 74
    history := 95
    fmt.Println("A의 평균 점수는", (math + eng + history) / 3, "입니다.")
    
    math := 88
    eng := 92
    history := 53
    fmt.Println("B의 평균 점수는", (math + eng + history) / 3, "입니다.")
    
    math := 78
    eng := 73
    history := 78
    fmt.Println("C의 평균 점수는", (math + eng + history) / 3, "입니다.")
}        

이 예제의 문제점 :
각 변수에 점수를 입력하는 코드와 점수를 출력하는 코드가 학생 수 만큼 반복되었다.

package main

import "fmt"

func PrintAvgScore(name string, math int, eng int, history int) {
	total := math + eng + history
    avg := total / 3
    fmt.Println(name, "의 평균 점수는", avg, "입니다.")
}

func main() {
	PrintAvgScore("A", 80, 74, 95) //A의 평균 점수는 83입니다.
    PrintAvgScore("B", 88, 92, 53) //B의 평균 점수는 77입니다.
    PrintAvgScore("C", 78, 73, 78) //C의 평균 점수는 76입니다.
    
}        

함수 사용 후 :
4줄의 코드가 1줄의 코드 입력으로 바뀌었다.
코드의 파악이 쉽고 추후 유지 보수도 용이하다.

멀티 반환 함수

함수는 값을 여러 개 반환할 수 있는데, 반환값이 여러개 일 때는 반환 타입들을 소괄호로 묶어서 표현한다.

package main

import "fmt"

func Divide(a, b int) (int, bool) { // 1️⃣ 함수 선언
	if b == 0 {
    	return 0, false // 2️⃣ 제수가 0일 때 반환
    }
    
    return a / b, true // 3️⃣ 제수가 0이 아닐 때 반환
}

func main() {
	c, success := Divide(9, 3) // 4️⃣ 제수가 0이 아닌 경우
    fmt.Println(c, success) // 3 true
    
    d, success := Divide(9, 0) // 5️⃣ 제수가 0인 경우
    fmt.Println(d, success) // 0 false
}

1️⃣ 함수를 정의한다. 이 함수는 int 타입 a,b를 매개변수로 받고 int 타입과 bool타입을 반환한다.
2️⃣ 나눗셈 제수가 0이면 false를 반환
3️⃣ 나눗셈 제수가 0이 아니면 나눗셈 결과와 true를 반환
4️⃣,5️⃣ Divide()함수를 호출하고, 그 결과를 변수 c와 success로 받는다. 첫 번째 반환값은 c, 두 번째 반환값은 success에 대입

변수명을 지정해 반환하기

함수 선언부에 반환 타입을 적을 때 변수명까지 지정해주면, return문으로 해당 변수를 명시적으로 반환하지 않아도 값을 반환할 수 있다.

package main

import "fmt"

func Divide(a, b int) (result int, success bool) { // 1️⃣ 반환할 변수명 명시
	if b == 0 {
    	result = 0
        success = false
    	return // 2️⃣ 명시적으로 반환할 값을 지정하지 않은 return문
    }
    
    result = a / b, 
    success = true 
    return 
}

func main() {
	c, success := Divide(9, 3)
    fmt.Println(c, success) //3 true
    
    d, success := Divide(9, 0) 
    fmt.Println(d, success) //0 false
}

1️⃣ 첫 번째 반환할 변수로 result, 두 번째 반환할 변수 success로 지정. 지정된 result, success는 함수 내부에서 변수로 동작
2️⃣ 함수 결과를 반환할 때 명시적으로 result, success를 지정하지 않았지만 두 값이 반환

📍 변환할 변수에 이름을 지정할 경우 모든 반환 변수에 이름을 지정하거나 모두 지정하지 않거나 해야함

재귀 호출

재귀 호출 : 함수안에서 자기 자신 함수를 다시 호출하는 것

package main

import "fmt"

func printNo(n int) {
	if n == 0 { // 2️⃣ 재귀 호출 탈출 조건
    	return
    }
    fmt.Println(n)
    printNo(n-1) // 3️⃣ 재귀 호출
    fmt.Println("After", n) // 4️⃣ 재귀 호출 이후 출력
}

func main() {
	printNo(3) // 1️⃣ 함수 호출
}

/*
결과값 
3
2
1
After 1
After 2
After 3
*/

1️⃣ printNo() 함수를 호출
2️⃣ 탈출 조건인지 확인. n이 0이 아니면
3️⃣ printNo() 함수 재호출

호출 순서를 보면 printNo(3)이 먼저 호출되고 이어서 printNo(2), printNo(1), printNo(0) 순으로 호출된다. 최종적으로 printNo(0)이 호출 됐을 때 2️⃣의 탈출 조건을 만족해 return되어 종료되고, 재귀 호출된 printNo() 함수가 종료되면 호출자인 printNo()의 4️⃣의 위치로 반환되고 거기에서 명령을 수행해서 "After" 메시지가 출력

호출순서

위 그림처럼 printNo() 함수 내에서 printNo(n-1)을 호출하면, main() 함수에서 printNo(3)을 호출한 뒤 printNo(2), printNo(1), printNo(0)을 차례로 호출한다. printNo(0)에서는 재귀 호출 탈출 조건 (n ==0)을 만족하므로 자신을 호출한 위치로 차례대로 연속해서 돌아가게 되어 최종적으로 최초 호출했던 main()함수 위치로 되돌아간다.

재귀 호출을 사용할 때는 항상 탈출 조건을 정해야 하는데, 앞 예제의 1️⃣처럼 재귀 호출이 종료되는 시점을 명확하게 하지 않으면 무한루프에 빠짐

결론

  1. 함수란 특수한 코드 묶음을 말한다. 함수를 만들면 코드를 재사용할 수 있다.
  2. 멀티 반환 함수는 값을 여러 개 반환할 수 있는데, 반환 타입 자리에 소괄호로 여러 반환 타입을 묶어서 표시한다.
  3. 재귀 호출은 함수 안에서 같은 함수를 또 호출하는 기법이다. 재귀 호출 시에는 항상 탈출 조건을 명확히 해야 한다.
profile
내가 떠나기 전까지는 망하지 마라, 블록체인 개발자

0개의 댓글