본문 바로가기
프로그래밍/코틀린

K012. 코틀린 함수(Functions) 깊게 살펴보기 - Functions 파트3

by K-인사이터 2024. 2. 6.
반응형

안녕하세요

K-IN 입니다.

 

코틀린 함수(Functions)에 대해서 알아보겠습니다. 

전체 강의 목록은 아래의 링크를 클릭해주세요.

 

K000. 코틀린 시리즈 (연재물)

안녕하세요 K-IN 입니다. 요즘 코틀린을 이용한 개발 프로젝트가 늘어나고 있습니다. 이에, 코틀린에 대해서 상세하게 정리하는 간행물을 제작하고자 합니다. 여기에 있는 링크들은 모두 코틀린

k-in.tistory.com

 

 

코틀린 함수(Functions) 

코틀린에서 함수에 대해서 이해하려면 아래의 개념들을 숙지해야합니다. "☆" 표기는 중요도를 나타냅니다.

분량이 많아 이글에서는 7~8번 항목까지만 다룹니다. 

 

이전 내용 파트1을 배우려면 아래의 링크로 이동해주세요. 

  1. 기본적인 함수 구조 ☆
  2. 함수 사용 방법 ☆
  3. 기본 인자(Default Arguments) ☆☆
  4. 이름지정 인자(Named Arguments) ☆☆
  5. 단일 표현식 함수(Single-expression Functions) ☆☆☆
  6. 명시적 리턴 타입(Explicit Return Types)
  7. 가변인자 (varargs) ☆☆☆
  8. 중위 표기법 (Infix Notation) ☆☆☆
  9. 함수의 범위 (Function Scope) ☆☆☆
  10. 제너릭 함수 (Generic Functions) ☆☆☆

코틀린 함수의 가변인자(varargs, Variable number of arguments)

우리는 함수를 정의할 때, 변수의 개수가 정해지지 않은 함수를 작성할 때가 있습니다. 

예를들어 로또 번호를 연속적으로 입력하여 컬렉션을 초기화하는 경우가 대표적인 예일 것입니다. 

 

혹은, 시험친 과목들의 점수들을 입력받아 평균을 내는 프로그램을 작성할 때도 변수의 개수가 정해지지 않는 평균을 내는 함수를 작성하고자 할 것입니다. 

 

코틀린은 이를 "가변인자"라고 부르며 varargs 라고 표기합니다.

즉, 가변적인 개수의 인자를 함수가 받을 수 있도록 해주는 기능을 말합니다. 

 

함수를 정의할 때 특정 파라미터에 대해 가변인자로 지정할 수 있습니다. 

vararg 라는 modifier(수정자)를 활용합니다. 

 

앞서, 연속적으로 입력되는 숫자를 담는 컬렉션을 예로 들었습니다. 

이는 아래와 같이 구현할 수 있습니다. 

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts 는 배열(Array)입니다. 
        result.add(t)
    return result
}

val list = asList(1, 2, 3, 4, 5)
println(list)

 

혹은, 과목별로 평균을 구하는 함수를 작성할 수 있습니다. 

이과 과목들과 문과 과목들을 각각 평균을 내는 함수를 가변인자를 이용해 구현할 수 있습니다. 

fun scoreAverage(vararg scores: Int): Int {
    var total = 0
    for (score in scores) 
    	total += score
    return (total / scores.size)

}

println("이과")
println(scoreAverage(
    60, // 화학
    70, // 물리
    30, // 수학
))
println("문과")
println(scoreAverage(
    20, // 국어
    70, // 사회
))

 

vararg 파라미터를 좀더 유연하게 사용하려면 다음 특징들을 숙지하는게 좋습니다. 

  • 함수 하나당 하나의 vararg 파라미터만 허용됩니다.
  • vararg 파라미터가 마지막에 오지않을 경우에는 이름지정 파라미터 방식으로 인자에 값을 넘겨줘야합니다. 
  • 스프레드 연산자(*)를 통해서 배열을 가변인자에 전달할 수 있습니다. 

각각의 특징을 녹여낸 코드는 다음과 같습니다. 

fun <T> joinToString(vararg items: T): String {
    return items.joinToString(", ")
}

fun <T> joinToString02(a: String, vararg items: T): String {
    println(a)
    return items.joinToString(", ")
}

fun <T> joinToString03(vararg items: T, a: String): String {
    println(a)
    return items.joinToString(", ")
}

val result = joinToString(1, 2, 3) // 개별 인자 전달
println(result)

val numbers = arrayOf(4, 5, 6)
val result2 = joinToString(*numbers) // 스프레드 연산자 사용
println(result2)

val numbers3 = arrayOf(7, 8, 9)
val result3 = joinToString02("케이인", *numbers3) // 가변인자가 맨뒤에 있는 경우
println(result3)

val numbers4 = arrayOf(10, 11, 12)
val result4 = joinToString03(*numbers4, a="케이인") // 가변인자가 맨앞에 있는 경우
println(result4)

 

코틀린 함수의 중위 표기법(Infix Notation)

중위 표기법은 특수한 함수 호출에 사용되는 문법입니다. 

"Hello" append "World" 라는 자연어에 가까운 가독성 있는 문법을 가능하게 해줍니다. 

 

일반적으로 함수나 메서드는 호출을 위해 점(".")이나 괄호("()")를 사용해야합니다 

그러나 중위 표기법은 인자들 사이에 함수이름을 두는 방식으로 호출됩니다. 

 

예시를 통해서 코틀린의 강력함을 체감할 수 있습니다. 

infix fun String.append(other: String): String = this + other

val result = "Hello" append "World"

println(result)

 

append 함수를 가운데 두고 두문자열 인자들이 배치되었습니다. 당연히 결과는 "HelloWorld" 입니다. 

이를 통해 개발자는 "A에 더하다 B를" 표현방식으로 편리하게 코딩을 할 수 있습니다. 

 

그렇다면 중위 표기법의 사용 조건에 대해서 알아보겠습니다. 

특별한 함수인 만큼 조건이 명확합니다. 

 

  • 함수가 멤버 함수이거나 확장 함수여야 합니다. 
  • 함수가 정확히 하나의 파라미터를 가지는 경우에만 사용할 수 있습니다. 
  • infix 키워드로 함수를 정의해야 합니다. 
  • 함수는 기본 인자를 가질 수 없습니다. 
infix fun String.append(other: String): String = this + other
    
val greet = "케이인 " append "세상에" append "오신걸 환영합니다"
println(greet) // 출력: "케이인 세상에 오신걸 환영합니다"

 

면접 질문 예상 ➡️ 중위 표기법의 우선순위(Precedence)

앞서 예시 코드에서 중위 표기법을 연달아 사용했고 원하던 출력을 얻었습니다. 

Infix 함수는 마치 연산자 처럼 동작합니다. 따라서 우선순위를 파악하고 있어야 합니다. 

 

가령, 1 shl 2 + 3 이라는 표현식이 있을때 어떻게 연산이 될까요?

이를 결정하는 것이 바로 우선순위(precedence)입니다. 

  • 산술 연산자(arithmetic operators)보다 infix 함수는 낮은 우선순위를 가집니다. 

따라서, 1 shl 2 + 3 은 1 shl ( 2 + 3 ) 과 동등합니다. 

 

  • infix 함수는 불린 연산(boolean operators)보다 높은 우선순위를 가집니다. 

따라서, a && b xor c 는 a && ( b xor c ) 와 동등합니다. 

 

클래스 내에서 Infix 함수 사용 시 주의할 점 

Infix 함수는 리시버(receiver)와 파라미터(parameter)를 항상 지정해야 합니다. 

만약, 메서드에서 infix 함수를 호출한다고 할때, 문법적으로 허용하거나 그렇지 않은 경우들이 있습니다. 

중요한 점은 this 를 명시적으로 사용해야 한다는 것입니다. 

 

만약 아래와 같은 에러들이 발생한다면 infix 함수의 주의사항(this 를 명시적으로 사용해야한다)을/를 지키지 않은 것입니다. 

  • Unexpected tokens (use ';' to separate expressions on the same line)
  • Function invocation 'add(...)' expected
  • No value passed for parameter 's'
class MyStringCollection {
    infix fun add(s: String) { /*...*/ }

    fun build() {
        this add "abc"   // 컴파일 성공
        add("abc")       // 컴파일 성공 
        //add "abc"        // 컴파일 에러: the receiver must be specified
    }
}

 

이번 시간에 배운 개념들을 종합한 코드입니다.

수정하면서 배운 개념들을 연습해봅니다. 

 

 

이상입니다.

K-IN 올림. 

반응형