Kotlin

Kotlin 함수 정의하기 (3)

smileDeveloper 2024. 2. 27. 22:33
반응형
SMALL

2024.02.25 - [Kotlin] - Kotlin 언어 기초 (2)과 같이 기본 개념과 같이 Kotlin 만의 특징을 집중적으로 글을 써보도록 하겠습니다.

 

이번 주제는 함수(Function)라는 개념입니다. 기본적으로 함수는 어떤 입력[파라미터(Parameter)]을 받고 출력값을 반환(Return)할 수 있는 재사용이 가능한 코드 블록입니다. 함수의 구조를 살펴보겠습니다.

 

함수의 구조

fun plus(a:Int,b:Int):Int {
    return a + b
}
  • fun(Function) 키워드를 사용하여 선언
  • 괄호로 둘러싸여 있는 콤마(,)로 분리한 파라미터 목록이 존재
  • 괄호 뒤의 세미콜론 뒤에 반환 타입을 선언
  • 함수 본문(body) 는 {} 으로 감싼 블록이며, 함수의 구현을 기술
  • return 문 다음에 위치한 코드는 실질적으로 죽은 코드이며, 결코 실행되지 않음, IDE에서 해당 부분을 강조
  • 파라미터 정의는 암시적으로 함수가 호출될 때 자동으로 인자 값으로 초기화하는 지역변수로 취급
  • 코틀린 함수 파라미터는 무조건 불변, 값을 변경하면 컴파일 오류 발생
  • 값에 의한 호출 의미론(Call-By-Value) 사용, 하지만 파라미터가 참조(ex: 배열 타입)일 땐 참조만 복사
  • 파라미터에는 항상 타입을 지정, 반환 타입도 항상 타입을 지정해야 함, 반환 타입이 Unit (void의 의미)일 땐 생략 가능

식이 본문인 함수 ( Expression-Body )

fun plus(a:Int,b:Int):Int = a+b

fun minus(a:Int,b:Int):Int = {return a-b} // 컴파일 오류 발생
fun minus(a:Int,b:Int):Int = {a-b} // 추후에 서술
  • 함수가 단일 식으로 구현될 수 있을 때, return 키워드와 블록을 만드는 {} 중괄호를 생략할 수 있다.
  • 만일 {} 앞에 = 를 넣으면 람다로 해석하여 문제가 생긴다.

위치 기반 인자와 이름 붙은 인자 ( Positional Argument & Named Argument )

기본적으로 함수 호출 인자는 순서대로 파라미터에 전달된다. 이런 방식의 인자 전달을 위치 기반 인자(Positional Argument)라고 한다. 코틀린은 이름 붙은 인자(Named Argument)라고 불리는 방식도 지원한다. 아래의 코드처럼 이름 붙은 인자를 중간에 섞어 쓸 수 있다. 

fun plus(a:Int,b:Int,c:Int):Int = a+b+c

fun main() {
    println(plus(1,2,3))
    println(plus(a=1,2,3))
    println(plus(1,b=2,3))
    println(plus(1,2,c=3))
    println(plus(a=1,b=2,3))
    println(plus(1,b=2,c=3))
    println(plus(a=1,2,c=3))
}

 

오버로딩과 디폴트 값 

오버로딩은 이름이 같은 함수를 여러개의 정의로 작성할 수 있다는 뜻이다. 컴파일러가 처리하기 위해서 파라미터의 타입이 모두 달라야 한다. 여러개의 오버로딩된 함수 중에서 실제로 호출할 함수를 결정할 때 컴파일러는 오버로딩 해소 규칙(Overloading Resoultion)과 비슷한 다음 규칙을 따른다.

1. 파라미터의 개수와 타입을 기준으로 호출할 수 있는 모든 함수를 찾는다.
2. 후보 목록에서의 어떤 함수의 파라미터 타입이 다른 함수의 파라미터 타입의 상위 타입인 경우 제외하는 것을 반복한다.
3. 후보가 하나로 압축되면 이 함수를 호출하고 후보가 2개 이상이라면 컴파일 오류가 발생된다.

 

이런 오버로딩을 쓰지 않아도 더 우아한 해법인 디폴트 파라미터가 있다. 파라미터 뒤에 변수 초기화 식을 추가하면 원하는 파라미터에 디폴트 값을 제공할 수 있다. 디폴트 값이 있을 땐 인자 개수가 가변적이어서 오버로딩 해소가 복잡해질 수도 있다...

VarArg

파라미터 정의 앞에 vararg 변경자를 붙이게 되면 파라미터를 적절한 배열 타입으로 사용할 수 있다. 이러한 파라미터에 스프레드(Spread) 연산자인 *를 사용하여 배열을 가변 인자 대신에 넘길 수 있다. *스프레드는 배열을 복사한다. 이 때는 얕은(Shallow) 복사가 이루어져 참조가 복사된다. 이름 붙은 인자에 스프레드를 사용해서 넘길수도 있다.

함수의 영역과 가시성

함수 정의 앞에 private와 internal이라는 키워드를 붙일 수 있다. 이런 키워드를 가시성 변경자(Visibility Modifer)라고 한다.

  • private는 비공개로 정의하는 것으로, 함수가 정의된 파일에서난 해당 함수를 사용할 수 있다.
  • internal은 함수가 적용된 모듈 내부에서만 함수를 사용할 수 있게 제한한다.
  • pubilc이라는 변경자가 있지만 디폴트로 공개 가시성을 가지고 있다.
  • 함수 내부에 함수를 정의하는 지역 함수가 있다. 지역 함수에는 가시성 변경자를 붙일 수 없다.

패키지와 임포트 ( Package & Import )

  • 코틀린 파일 맨 앞에 패키지 이름을 지정하면 파일에 있는 모든 최상위 선언을 지정한 패키지 내부에 넣을 수 있다. 패키지를 지정하지 않으면 파일이 디폴트 최상위 패키지에 속한다고 가정
  • 패키지 디렉티브는 package 키워드로 시작하고 점(.)으로 구별된 식별자들로 이뤄진 패키지 전체 이름(Qualified Name)이 옴, 뒤에 온 전체 이름은 전체 패키지 계층에서 루트 패키지로부터 지정한 패키지에 도달하기 위한 경로
    • 패키지 디렉티브를 사용하여 여러 파일을 같은 패키지에 넣을 수 있다.
  • 임포트 디렉티브를 사용하면 간단한 이름으로 해당 선언에 접근 가능
    • 임포트 별명(Alias)으로 임포트한 선언에 새 이름을 부여할 수 있다.
    • 어떤 영역에 속한 모든 선언을 한꺼번에 임포트하기 위해서는 전체 이름 뒤에 * 를 붙이면 된다.

조건문

fun max(a:Int,b:Int):Int {
    return if(a>b) a else b
}

코틀린은 삼항 연산자가 없지만 if를 식으로 쓸 수 있어 이 단점을 상쇄

범위, 진행, 연산

val chars = 'a'..'z';
val twoDigits = 10..99
val zero2One = 0.0..1.0
  • 범위를 가장 간단한 방법으로 만들때 수 값에 .. 연산자를 사용한다. 
  • in 연산을 사용하여 어떤 값이 범위에 들어있는지 알 수 있다. 이와 반대인 !in 도 있다.
val twoDigits = 10..99 // 10 <= x <= 99
val twoDigits2 = 10 until 100 // 10 <= x < 100
  • .. 연산으로 만들어지는 범위는 시작과 끝 값이 포함이 된다.
  • 끝 값이 포함되지 않는 연산은 until을 사용한다.
  • downTo 연산은 아래로 내려가는(Descending) 진행을 만들 수 있다.
  • step 연산으로 진행의 간격을 지정할 수도 있다.

When 문

fun numberDescription(n:Int):String = when {
    n == 0 -> "Zero"
    n == 1 || n == 2 || n == 3 -> "Small"
    n in 4..9 -> "Medium"
    n in 10..100 -> "Large"
    n !in Int.MIN_VALUE until 0 -> "Negative"
    else -> "Huge"
}

fun numberDescription2(n:Int):String = when (n) {
    0 -> "Zero"
    1,2 ,3 -> "Small"
    in 4..9 -> "Medium"
    in 10..100 -> "Large"
    !in Int.MIN_VALUE until 0 -> "Negative"
    else -> "Huge"
}

코틀린은 switch 문과 비슷한 블록인 when 문을 지원한다. when 키워드 안 블록의 내용에는 조건 -> 문 형태로 되어있는 여러 개의 가지와 else -> 문이 있을 수도 있다. else 문은 모든 조건이 거짓일 때 실행된다. switch 문과의 가장 큰 차이점은 임의의 조건을 검사할 수 있다는 점*폴스루(Fall-Through)를 지원하지 않는다는 점이다. 추가로 식의 대상을 변수에 연결(binding)할 수 있다. 이때 변수는 when 블록 내부에서만 사용할 수 있고 var로 선언할 수 없다.

 

*폴스루란 어떤 조건을 만족할 때 프로그램이 해당 조건에 대응하는 문을 실행하고 명시적으로 break를 만날 때까지 이후 모든 가지를 실행하는 것을 의미 

루프

  • 문자열과 배열에는 원소나 문자의 인덱스 범위를 제공하는 indices라는 프로퍼티가 존재
  • continue@outerLoop를 사용해 바깥쪽 루프의 이터레이션을 끝내고 다음 이터레이션으로 넘어갈 수 있다.
  • 레이블을 붙여 제어를 옮길 대상 루프를 지정할 수 있다.
  • tailrec 키워드를 함수 앞에 붙여 컴파일러가 재귀함수를 비재귀함수로 자동으로 변환

예외

  • 코틀린은 검사 예외 (Checked Exception)와 비검사 예외 (Unchecked Exception)를 구분하지 않는다. 
반응형
LIST

'Kotlin' 카테고리의 다른 글

Kotlin 클래스와 객체 (4)  (0) 2024.03.01
Kotlin 언어 기초 (2)  (1) 2024.02.25
Kotlin 개념 (1)  (0) 2024.02.21
Kotlin 시작  (1) 2024.02.15