ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3-1. 함수 정의하기 : 함수
    Study(종료)/Kotlin 22.09.13 ~ 12.18 2022. 9. 26. 12:50

    3장 정리

    3-1. 함수 정의하기 : 함수

    3-2. 함수 정의하기 : 조건문

    3-3. 함수 정의하기 : 루프

    3-4. 함수 정의하기 : 예외 처리


    코딩할때 함수없이는 작성할 수 없다고 확신할 만큼 함수는 중요하다고 생각한다.

    처음 언어를 시작할 때 Hello World! 를 출력하기 위해서 벌써 두 개의 함수를 만나게 된다

    일반적으로 가장 먼저 실행되는 코드인 main()

    원하는 내용을 표준 입출력으로 출력하는 println()

    이 두 함수를 이용해서 Hello World!를 출력하게 된다.

    그럼 함수에 대해 공부해보자


    3.1 함수

    코틀린 함수는 어떤 입력(=파라미터)을 받아서 자신한 코드 쪽에 출력값을 반환할 수 있는 재사용 가능한 코드 블럭이다

     

    1) 코틀린 함수의 구조

    먼저 원의 둘레를 계산하는 함수를 보며 한줄씩 자세히 설명해보겠다. 원의 둘레는 영어로 Circumference 라고 한다

    import kotlin.math.PI
    
    fun calCircumference(radius: Int): Double{
        return PI * 2 * radius
    }
    
    fun main() {
        val r = 20
        val circumference = calCircumference(r)
        println("Circumference : $circumference") 
        // 125.66370614359172 
    }

    여기서 원주율은 코틀린의 import 디렉티브를 사용하면(첫번째 줄) 코드에서 PI라고 간단하게 호출할 수 있다.

    - fun 키워드는 function(함수)에서 유래한 키워드로 컴파일러에게 함수 정의가 뒤따라온다고 알려준다.

    - 함수 이름은 변수 이름과 동일하게 원하는대로 작성할  수 있다

     (위 코드에서는 calCircumference 라고 작성했지만, cal로 작성할 수도 있다)

    - 함수 이름 다음에는 파라미터 목록이 온다. 여기서 파라미터는 괄호( ) 로 둘러쌓여 있고,

     파라미터가 한 개 이상이라면 콤마, 를 통해 구분한다

    - 파라미터 다음에는 콜론: 이 붙고 그 다음 반환할 변수의 타입이 온다.

     위 코드에서는 원주율이 계산식에 포함되어 있으므로 반환 타입이 실수형 타입인 Double이 온다

    - 타입 지정을 생략해도 되는 변수와 달리 함수의 파라미터는 val이나 var이 아닌 변수 타입을 명시해야 한다.

    - 반환할 변수의 값(함수의 결과값)은 return 키워드를 통해 작성할 수 있다(2 * PI * radius)

    - 함수가 파라미터를 받지 않는 경우에도 파라미터를 감싸는 괄호( )는 꼭 있어야 한다( fun main () )

    - return문은 함수 실행을 끝내고 함수를 호출한 코드로 제어를 돌려준다.

    - 반환 타입도 명시해야 한다. 반환 타입은 추론이 가능하지만 함수의 반환 지점을 보고 반환 타입을 알아내기 어려울 수 있고

     함수 정의의 첫 줄만 보고도 함수의 반환 타입을 알아낼 수 있는 문서화 역활을 한다.


    코틀린은 값에 의한 호출을 사용한다.

    즉 호출 인자로 전달한 변수를 변경해도 호출된 내부의 파라미터 값에는 영향이 없다는 뜻 이다.

    하지만 파라미터가 참조(ex 배열 타입)라면 호출한 쪽의 데이터는 그대로 남아있고, 이 데이터에 대한 참조만 복사된다.

    말이 어려운데, 코드를 통한 예시를 보면 이해가 쉬울 것 같다.

     

    값에 의한 호출의 경우

    fun main() {
        val x = 20
        plusTwo(x)
        println(x) // 20
    }
    
    fun plusTwo(a: Int): Int{
        return a+2
    }

    참조에 의한 호출의 경우

    fun main() {
        val x = intArrayOf(1, 2, 3, 4)
        plusArray(x)
        println("x : ${x[0]} ${x[1]} ${x[2]} ${x[3]}")
        // x : 3 2 5 5
    }
    
    fun plusArray(a: IntArray){
        a[2] = a[2] + 2
        a[0] += a[1]
        ++a[3]
    }

     

    경우에 따라 반환 타입을 생략할 수 있는 방법이 두 가지가 있다.

    첫번째는 유닛(Unit) 타입을 반환하는 경우이며 자바의 void에 해당하는 코틀린 타입이다.

    반환값이 유닛 타입인 경우 함수가 의미 있는 반환값을 돌려주지 않는다는 뜻 이다.

    즉 아래의 두 함수는 동일한 의미를 가진다.

    fun plusArray(a: IntArray){
        a[2] = a[2] + 2
        a[0] += a[1]
        ++a[3]
    }
    
    fun plusArray(a: IntArray): Unit{
        a[2] = a[2] + 2
        a[0] += a[1]
        ++a[3]
    }

    두번째는 식이 본문인 (expression-body) 함수이다.

    어떤 함수가 단일 식으로만 구현될 수 있다면 return 키워드와 중괄호 { } 를 생략하고 함수를 작성해도 된다.

    여기서 반환 타입과 본문 사이에 = 이 들어간다는 것을 유의해야 한다.

    fun calCircumference(radius: Int): Double = PI * radius * 2 
    // 정상동작
    fun calCircumference(radius: Int): Double = { return PI * radius * 2} 
    // 컴파일 에러
    fun calCircumference(radius: Int) = {PI * radius * 2} 
    // 람다로 해석 > 원하는 값 x
    // 위 식을 람다로 해석해 컴파일러가 아래의 식으로 변환해준다
    fun calCircumference(radius: Int): () -> Double = {PI * radius * 2}
    // 원하는 값 x

    위에서 두번째 함수의 경우 식이 본문인 함수는 return문이 금지되기 떄문에 컴파일 에러가 난다.


    2) 위치 기반 인자와 이름 붙은 인자

    기본적으로 함수 호출 인자는 순서대로 파라미터에 전달된다.

    첫 번째 인자 > 첫 번째 파라미터 , 두 번째 인자 > 두 번째 파라미터라는 얘기이다.

    코틀린은 이와 같은 방식의 인자 전달을 위치 기반 인자(positional argument)라고 한다.

    fun triangleArea(width: Double, height: Double): Double{
        return width * height / 2
    }
    
    fun main() {
        val w = 10.0
        val h = 4.0
        println("Triangle Area : ${triangleArea(w, h)}")
    }

    코틀린은 이름 붙은 인자(named argument)라고 불리는 방식도 제공한다.

    이는 위치가 아니라 파라미터의 이름을 명시함으로써 인자를 전달하는 방식이다

    예를들면, 위 식을 다음과 같이 호출할 수 있다.

    // 위치 기반 인자
    println("Triangle Area : ${triangleArea(w, h)}")
    // 이름 붙은 인자
    println("Triangle Area : ${triangleArea(width = w, height = h)}")
    println("Triangle Area : ${triangleArea(height = h, width = w)}") // 순서를 바꿀 수 있다

    이름 붙은 인자를 사용하면, 실제 순서는 중요하지 않다. 위의 함수 호출은 모두 동일한 의미를 가진다.

    나아가 한 호출 안에서 위치 기반 인자와 이름 붙은 인자를 함께 사용할 수 있다.

    다만 이 경우에는 원래 인자가 들어가야 할 위치에 이름 붙은 인자를 지정해야 정상 처리되며,

    그렇지 않은 경우 위치 기반 인자의 타입이 어긋나거나 이미 할당된 인자를 재할당하기 때문에 컴파일 오류가 발생한다.

    이를 이용하면 아래와 같은 코드를 작성해볼 수 있다.

    fun swap(s: String, from: Int, to: Int): String{
        val chars =  s.toCharArray()
        val tmp = chars[from]
        chars[from] = chars[to]
        chars[to] = tmp
        return chars.concatToString()
    }
    
    fun main() {
        println(swap("Hello", 1, 2)) // Hlelo
        println(swap("Hello", from = 1, to = 2)) // Hlelo
        println(swap("Hello", to = 3, from = 0)) // lelHo
        println(swap("Hello", 1, to = 3)) // Hlleo
        println(swap(from = 1, s = "Hello", to = 2)) // Hlelo
        println(swap(s = "Hello", from = 1, to = 2)) // Hlelo
        println(swap(s = "Hello", 1, to = 2)) // Hlelo
    
        println(swap(s = "Hello", 1, from = 2)) // 컴파일 에러
    	// error : An argument is already passed for this parameter
    	// 이미 from에 1이 할당됬는데, from에 다시 2를 할당하려 하므로 에러
        println(swap(1, 2 s = "Hello")) // 컴파일 에러
    	// 위와 동일한 에러로 s에 이미 1이 할당되고, from에 2가 할당됨
    }

    3) 오버로딩과 디폴트 값

    코틀린 공식 홈페이지에는 오버로딩의 정의에 대한 내용을 찾기가 힘들어서 w3schools를 참고하자면, 

    With method overloading, multiple methods can have the same name with different parameters

    메서드 오버로딩을 사용하면 매개변수가 다른 여러 메서드가 동일한 이름을 가질 수 있다고 해석이 된다.

    fun plus(a: Int, b: Int): Int{ // 1
        return a + b
    }
    
    fun plus(a: Double, b: Int): Double{ // 2
        return a + b
    }
    
    fun plus(a: Float, b: Double): Double{ // 3
        return a + b
    }
    
    fun main() {
        val x = 10 // Int
        val y = 2.0 // Double
        val z = 5F // Float
        
        println(plus(x ,x)) // call 1
        println(plus(b = x, a = y)) // call 2
        println(plus(z ,y)) // call 3
    }

    앞에서 배운 이름 붙은 인자 방식도 사용해 보았다.

    위와같이 같은 plus 함수여도 매개변수의 타입의 따라 다른 함수를 호출하는 것을 확인할 수 있다.

     

    함수 인자 중 일부를 생략해 미리 정해진 기본값을 사용하고자 메서드를 오버로딩해야 하는 경우가 있다.

    코틀린에서는 디폴트 파라미터를 사용하면, 원하는 파라미터에 디폴트 값을 제공할 수 있다.

    디폴트 파라미터를 이용하면 위에서 작성했던 덧셈 코드를 아래와 같이 사용할 수 있다.

    fun plus(a: Int , b: Int = 3): Int{ 
        return a + b
    }
    
    fun main() {
        val x = 10
        println(plus(x)) // 13
        println(plus(x, x)) // 20
    }

    4) vararg

    인자의 갯수가 정해지지 않은 함수를 만들 수 있다. 방법은 파라미터 정의 앞에 vararg 변경자를 붙이면 된다.

    또한 스프레드(spread) 연산자인 *를 사용하면 배열을 가변 인자 대신 넘길 수 있다.

    원래 함수에서 파라미터로 배열을 넘기는 경우 원본 배열의 참조를 넘겨주고,

    함수 내에서 배열의 내용을 변경하면 원본 배열에도 반영이 되었지만, 

    스프레드 인자는 배열을 복사하므로 파라미터 배열의 내용을 바꿔도 원본 배열에는 영향이 없다.

    fun printPlusOne(vararg items: Int){
        for(item in items){ // items 배열을 출력하는 반복문
            print("${item+1} ")
        }
        println()
    }
    
    fun printArray(vararg items: Int){
        for(item in items){ 
            print("$item ")
        }
        println()
    }
    
    fun main() {
        printPlusOne(1, 3, 4, 5, 10) // 2 4 5 6 11 
    
        val a = intArrayOf(1, 3, 4, 5, 10)
    //  printPlusOne(a)
    // 	error
        printPlusOne(*a) // 2 4 5 6 11 
        printArray(*a) // 1 3 4 5 10 
    }

    둘 이상의 vararg 파라미터 선언은 금지된다.

    하지만 vararg 파라미터에 콤마로 분리한 여러 인자와 스프레드를 섞어서 전달하는 것은 가능하다.

    호출 시 이런 호출은 원래의 순서가 유지되는 단일 배열로 합쳐진다.

    fun main() {
        val a = intArrayOf(1, 3, 4, 5, 10)
        printArray(1,2,*a, 5, 6) //1 2 1 3 4 5 10 5 6     
        printArray(2, 5, *intArrayOf(10, 24)) // 2 5 10 24 
    }

    vararg 파라미터가 맨 마지막에 있는 파라미터가 아니라면, vararg 파라미터 이후의 파라미터는 이름 붙은 인자로만  전달할 수 있다.

    디폴트 값과 비슷하게 vararg 파라미터도 파라미터 목록의 맨 뒤에 위치시키는것이 좋은 코딩 스타일이다.

    vararg 파라미터를 이름 붙은 인자로 전달할 수는 없다. 단, 이름 붙은 인자에 스프레드를 사용해서 가변 인자를 전달할 수는 있다.

    fun printPlusOne(vararg items: Int, subItem: Double){
        for(item in items){
            print("${item+1} ")
        }
        println()
    }
    
    fun printPlusTwo(subItem: Double, vararg items: Int){ // 좋은 코딩 스타일
        for(item in items){
            print("${item+2} ")
        }
        println()
    }
    
    fun main() {
        val a = intArrayOf(1, 3, 4, 5, 10)
        // printPlusOne(*a, 10.0)
        // error : The floating-point literal does not conform to the expected type Int
        printPlusOne(*a, subItem = 10.0) // 정상 동작
    }

    5) 함수의 영역과 가시성

    자바의 접근 제한자(Access modifiers)는 코틀린에서 가시성 변경자(Visibility modifiers)로 불린다.

    아마 function visibility scope를 그대로 번역해서 함수의 영역과 가시성 으로 제목이 적힌것 같은데,

    처음 들으면 무슨 내용일지 감이 잡히지 않아 번역이 살짝 아쉽다.

    좀 더 다양한 예시를 보기 위해 코틀린 공식 사이트를 참고해 정리해 보았다.

    https://kotlinlang.org/docs/visibility-modifiers.html#class-members

     

    Visibility modifiers | Kotlin

     

    kotlinlang.org

    정리내용

    더보기

    클래스, 오브젝트, 인터페이스, 생성자, 함수, 프로퍼티, 세터(Setters)는 Visibility modifiers를 가진다.

    코틀린에는 네가지 Visibility modifiers가 있다

    private, protected, internal, public이 있으며, 기본값(명시하지 않은 경우) public이 적용된다

     i ) 패키지

    함수, 프로퍼티, 클래스, 오브젝트, 인터페이스는 패키지 내부의 top-level에 선언할 수 있다.

    package foo
    
    fun baz() { ... }
    class Bar { ... }

    만약 modifier를 명시하지 않는다면, public이 기본값으로 사용된다.

    이 다음 설명에서 선언은 위에 작성한 함수, 프로퍼티, 클래스 등을 의미한다.

    public : 선언이 어디서든 어디서든 호출할 수 있다.

    (declarations will be visible everywhere)

    private : private선언이 포함된 파일 내부에서만 호출할 수 있다.

    (visible inside the file that contains the declaration)

    internal : 동일한 모든 모듈이 호출할 수 있다.

    (visible inside the file that contains the declaration)

    protected : top-level에서 선언할 수 없다

    (is not available for top-level declarations)

    다른 패키지의 top-level 선언을 호출하고 싶은 경우 반드시 해당 패키지를 import 해야한다.

    (To use a visible top-level declaration from another package, you should import it.)

    // file name: example.kt
    package foo
    
    private fun foo() { ... } // visible inside example.kt
    
    public var bar: Int = 5 // property is visible everywhere
        private set         // setter is visible only in example.kt
    
    internal val baz = 6    // visible inside the same module

     ii) 클래스 멤버

    private : 모든 멤버를 포함해 이 클래스 내에서만 호출할 수 있다.

    (the member is visible inside this class only (including all its members))

    protected : 모든 멤버는 private과 동일하지만, 하위 클래스에서도 protected 멤버를 호출할 수 있다.

    (the member has the same visibility as one marked as private, but that it is also visible in subclasses)

    internal : 이 선언을 본 모듈 내부의 모든 클라이언트는 internal 멤버를 호출할 수 있다.

    (any client inside this module who sees the declaring class sees its internal members)

    public : 클래스 선언을 본 모든 클라이언트는 해당 클래스의 public 멤버를 호출할 수 있다.

    (any client who sees the declaring class sees its public members)

    코틀린에서 외부 클래스는 내부 클래스의 private 멤버를 볼 수 없다(호출할 수 없다)

    (In Kotlin, an outer class does not see private members of its inner classes)

    open class Outer {
        private val a = 1
        protected open val b = 2
        internal open val c = 3
        val d = 4  // public by default
    
        protected class Nested {
            public val e: Int = 5
        }
    }
    
    class Subclass : Outer() {
        // a is not visible
        // b, c and d are visible
        // Nested and e are visible
    
        override val b = 5   // 'b' is protected
        override val c = 7   // 'c' is internal
    }
    
    class Unrelated(o: Outer) {
        // o.a, o.b are not visible
        // o.c and o.d are visible (same module)
        // Outer.Nested is not visible, and Nested::e is not visible either
    }

    iii) 모듈

    모듈은 internal modifier를 설명하기 위한 내용으로, 동일한 모듈 내에서 호출가능하다. 

    여기서 모듈은 함께 컴파일된 Kotlin 파일 세트이다. 예를들면 다음과 같다.

    • An IntelliJ IDEA module.
    • A Maven project.
    • A Gradle source set (with the exception that the test source set can access the internal declarations of main).
    • A set of files compiled with one invocation of the <kotlinc> Ant task.

    다음 글

    2022.09.26 - [코틀린 공부/코틀린_스터디] - 3. 함수 정의하기 : 조건문

    반응형

    댓글

Designed by Tistory.