본 게시글은 부스트코스의 코틀린 기본프로그래밍을

시청한 후 학습한 정보를 기록하는 목적의 게시글입니다.

부스트코스

 

다 함께 배우고 성장하는 부스트코스

부스트코스(boostcourse)는 모두 함께 배우고 성장하는 비영리 SW 온라인 플랫폼입니다.

www.boostcourse.org

코틀린 기본프로그래밍(1/2)

 

코틀린 프로그래밍 기본1/2(함수편)

부스트코스 무료 강의

www.boostcourse.org

본 게시글은 부스트코스 서포터즈 3기 활동 게시글입니다.


#4-3. 다양한 함수의 출격1! (익명함수, 인라인 함수)

- 들어가기 전에

이제 다양한 함수의 종류와 선언, 사용 방법에 대해 살펴볼 시간입니다. 이 때도 언제든 람다식이 이용될 수 있으므로 다양한 모습의 함수에 익숙해져야 합니다. 그럼 시작해봅시다.

 

- 핵심 키워드

  • 익명 함수(anonymous functions)
  • 인라인 함수
  • 비지역 반환(non-local return)
  • inline

 

1) 익명 함수란

fun(x: Int, y: Int): Int = x + y // 함수 이름이 생략된 익명 함수

익명 함수란 일반 함수이지만 이름이 없는 함수를 일컫는다.

람다식 함수가 이에 해당되며 일반 함수의 이름을 생략하고 사용할 수 있다.

람다식 함수에서는 return이나 break, continue처럼 제어문을 사용하기 어렵기 때문에

함수 본문에 조건식에 따라 함수를 중단하고 반환해야 하는 경우에 일반 익명 함수로 표현할 수 있다.

.

2) 인라인 함수의 개념

fun main() {
    // 인라인 함수 shortFunc의 내용이 복사되어 들어감
    shortFunc(3) { println("First call: $it") }
    shortFunc(5) { println("Second call: $it") }
}

inline fun shortFunc(a: Int, out: (Int) -> Unit) {
    println("Before calling out()")
    out(a)
    println("After calling out()")
}

인라인 함수는 함수가 호출되는 곳에 내용을 모두 복사해 넣는 함수로써,

함수의 분기 없이 처리되기 때문에 코드의 성능을 높일 수 있다.

대게 짧은 코드로 작성되며 람다식 매개변수를 가지고 있는 일반 함수 형태를 권장한다.

 

#4-3. 다양한 함수의 출격2! (인라인 함수, 확장 함수)

- 들어가기 전에

이제 다양한 함수의 종류와 선언, 사용 방법에 대해 살펴볼 시간입니다. 이 때도 언제든 람다식이 이용될 수 있으므로 다양한 모습의 함수에 익숙해져야 합니다. 그럼 시작해봅시다.

 

- 핵심 키워드

  • 인라인 함수의 제한 - noinline
  • 비지역 반환의 금지 - crossinline
  • 확장 함수(extension function)

 

1) 인라인 함수의 제한과 금지

inline fun sub(out1: () -> Unit, noinline out2: () -> Unit) {
...

noinline 키워드를 람다식 함수 매개변수 정의에서 사용하면

기본적으로 람다식 함수를 매개변수로 가진 함수가 inline으로 정의되어 있다 하더라도

사용할 때 람다식 함수를 inline으로 처리하지 않는다.

즉, nolinle이 있는 람다식 함수는 인라인으로 처리되지 않고 분기하여 호출된다.

 

! 비지역 반환이란?

코틀린에서 익명 함수를 종료하기 위해 return을 사용할 수 있다.

이 때, 특정 반환값 없이 return만을 사용하여야 한다.

그렇다면 람다식 함수를 빠져나오는 데에는 어떻게 해야할지 의문이 생길 수 있다.

람다식 함수를 인자로 사용하는 함수는 의도하지 않게,

람다식 함수 바깥에 있는 함수가 같이 반한되어 버리는 데 이를 비지역 반환이라 한다.

이런 비지역 반환을 금지하려면 crossinline이라는 키워드를 람다식 함수 앞에 사용하여

본문 블록에서 return이 사용되는 것을 금지할 수 있다.

fun main() {
    shortFunc(3) {
        println("First call: $it")
        return // ①
    }
}

inline fun shortFunc(a: Int, out: (Int) -> Unit) {
    println("Before calling out()")
    out(a)
    println("After calling out()") // ②
}

 

crossinline 키워드는 비지역 반환을 금지해야 하는 람다식에 사용하여야 한다.

inline fun shortFunc(a: Int, crossinline out: (Int) -> Unit) {
    println("Before calling out()")
    nestedFunc { out(a) }
    println("After calling out()")
}

 

2) 확장 함수의 개념과 예

클래스에는 다양한 함수가 정의되어 있는데, 이를 클래스의 멤버 메서드라고도 부른다.

그런데 기존 멤버 메서드는 아니지만 내가 원하는 함수를 하나 더 포함시켜 확장하고 싶을 때가 있을 수 있다.

코틀린에서는 이처럼 클래스처럼 필요로 하는 대상에 함수를 더 추가할 수 있는 확장 함수를 제공한다.

fun main() {
    val source = "Hello World!"
    val target = "Kotlin"
    println(source.getLongString(target))
}

// String을 확장해 getLongString 추가
fun String.getLongString(target: String): String =
        if (this.length > target.length) this  else target

 

확장 함수를 만들 때 만일 확장하려는 대상에 동일한 이름의 멤버 함수 혹은 메서드가 존재한다면

항상 확장 함수보다 멤버 메서드가 우선으로 호출된다.

 

#4-3. 다양한 함수의 출격3! (중위 함수, 꼬리재귀 함수)

- 들어가기 전에

연산자를 구현할 때 자주 사용되는 중위 함수와 자기 자신을 호출하는 재귀 함수에 대해 이야기 해 봅시다.

 

- 핵심 키워드

  • 중위 함수 (infix function) - infix
  • 재귀 함수 (recursion function)
  • 꼬리 재귀 함수 (tail recursive function) - tailrec

 

1) 중위 함수의 개념과 예

중위 표현법이란 클래스의 멤버 호출 시 사용하는 점(.)을 생략하고 함수 이름 뒤에 소괄호를 붙이지 않는 표현법이다

즉, 중위 함수란 일종의 연산자를 구현할 수 있는 함수를 말한다.

중위 함수는 주로 비트 연산자에서 사용된다.

...
    // 중위 표현법
    val multi = 3 multiply 10
...

// Int를 확장해서 multiply() 함수가 하나 더 추가되었음
infix fun Int.multiply(x: Int): Int {  // infix로 선언되므로 중위 함수
    return this * x
}

 

2) 재귀의 개념과 예

재귀란 자기 자신을 다시 참조하는 방법이다.

자기 자신을 지속적으로 참조하면 무한하게 대상을 생성할 수도 있다.

재귀 함수는 자기 자신을 계속 호출하는 특징이 있기에 재귀 함수는 반드시 아래의 조건에 맞게 설계해야 합니다.

  • 무한 호출에 빠지지 않도록 탈출 조건을 만들어 둔다.
  • 스택 영역을 이용하므로 호출 횟수를 무리하게 많이 지정해 연산하지 않는다.
  • 코드를 복잡하지 않게 한다.

이를 고려하지 않는다면 Stack Overflow에 빠질 수 있다.

fun main(){
    val number = 4
    val result: Long
    
    result = factorial(number)
    println("Factorial: $number -> $result")
}

fun factorial(n: Int): Long {
    return if(n == 1) n.toLong() else n * factorial(n-1)
}

 

3) 꼬리 재귀 함수의 개념과 예

또 코틀린에서는 꼬리 재귀 함수를 통해 슥 오버플로를 예방할 수 있다.

이것은 스택에 계속 쌓이는 방식이 아닌 꼬리를 무는 형태로 반복되는 함수를 말한다.

이것을 사용하기 위해 tailrec 키워드를 사용할 수 있다.

fun main() {
    val number = 5
    println("Factorial: $number -> ${factorial(number)}")
}

tailrec fun factorial(n: Int, run: Int = 1): Long {
    return if (n == 1) run.toLong() else factorial(n-1, run*n)
}

꼬리 재귀를 사용하면 팩토리얼의 값을 그때 그때 계산하므로 스택 공간을 낭비하지 않기에

일반 재귀함수보다 안전한 코드를 만들 수 있다.

 

! 스텍오버플로란?

 

스택 오버플로 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기

ko.wikipedia.org

 

#4-4) 함수와 변수의 범위(Scope)

- 들어가기 전에

함수는 실행 블록({ })을 가지고 있습니다. 함수의 시작을 알리는 중괄호 시작 기호 ({)에서 함수가 실행되며 중괄호가 끝나는 지점(})에서 함수가 종료되면서 가지고 있는 지역 변수를 삭제합니다. 지역 변수란 블록 내에서만 사용하는 변수입니다. 이와 마찬가지로 블록 내에 또 다른 함수를 정의해 넣었을 때는 지역 함수가 됩니다. 이 지역 함수를 사용하려고 할 때는 항상 선언이 먼저 되어 있어야 합니다. 이 지역 함수도 마찬가지로 블록이 끝나면 같이 삭제됩니다. 이번 절에서는 이러한 함수와 변수의 범위에 대해 알아봅시다.

 

- 핵심 키워드

  • 지역 변수 (local variable)
  • 전역 변수 (global variable)
  • 최상위 레벨 함수 (top-level function)
  • 지역 함수 (local function)

 

1) 함수와 변수의 실행 범위

fun main() { // 최상위 레벨의 함수
    ...
    fun secondFunc(a: Int) { // 지역 함수 선언
        ... 
    }
    userFunc(4) // 사용자 함수 사용 - 선언부의 위치에 상관 없이 사용
    secondFunc(2) // 지역 함수 사용 - 선언부가 먼저 나와야 사용 가능
}

fun userFunc(counts: Int) { // 사용자가 만든 최상위 레벨의 함수 선언
    ...
}

 

2) 함수와 실행범위 예

- 최상위 및 지역 함수의 사용 범위

지역 함수들은 최상위 함수와 다르게 선언 순서에 따라 영향을 받는다.

아직 선언되지 않은 함수를 사용하려고 하면 'unresolved reference' 오류를 내는데,

컴파일러 입장에서 함수의 이름을 아직 모르기 때문이다.

반면에 최상위 함수는 메모리에 고정되어 있어 어느 함수 어떤 위치든 사용할 수 있다.

 

- 지역 변수와 전역 변수

보통 우리가 사용할 수 있는 변수는 사용 범위에 따라 지역 변수와 전역 변수로 나눌 수 있다.

특정 코드 블록 내에 사용할 때 우리는 지역 변수라고 하며 블록을 벗어나면

해당 변수는 프로그램 메모리에서 더 이상 사용되지 않고 삭제된다.

전역 변수는 최상위에 있는 변수로 프로그램이 실행되는 동안 삭제되지 않고 메모리에 유지된다.

값이 유지되므로 편리하지만 코드가 길어지면 여러 요소가 동시에 접근하는 경우에 잘못된 동작을 유발할 수 있다.

자주 사용되지 않는 전역 변수는 메모리 자원 낭비를 불러오기에,

전역 변수를 너무 많이 사용하는 것은 지양하는 것이 좋다.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기