Kotlin - 함수, 여러가지 함수

이동수·2024년 8월 27일

Kotlin

목록 보기
9/33
post-thumbnail

함수(function), 서브루틴(subroutine), 루틴(routine), 메서드
(method), 프로시저(procedure)는 소프트웨어에서 특정 동작을 수행
하는 일정 코드 부분을 의미


함수

일반적인 프로그래밍 언어에서 함수의 기본동작방식
함수를 호출

  • 함수가 갖는 특정 변수에 값을 전달 할 때
    • 특정 변수를 매개변수(parameter) - 선언할때
    • 전달되는 값은 인자(argument) - 사용할때 부르는거
      //annualSalary: Long, tax: Float ← 파라미터
      fun paycheck(annualSalary: Long, tax: Float) = (annualSalary.div(12) -(annualSalary.div(12)).times(tax)).toLong()
      fun main(){
      	var salary = paycheck()
      	 salary = paycheck(90_000_000, 0.07f,3_000_000) //90_000_000, 0.033f ← 인자
      }

호출된 함수의 계산을 실행 후 원래 루틴으로 돌아 감

  • 함수가 값을 리턴 할 수도 있음(return value)

Kotlin 함수 정의 규칙

  1. 함수를 선언할 때 반드시 fun 키워드를 사용해야 한다
  2. 함수선언은 클래스 블록({})뿐 아니라 Top-Level(최상위수준)에 구현 할 수
    있다
  3. 함수이름(,,,)안에 함수 파라미터(name:Type)를 정의할 수 있다
  4. 리턴 타입이 존재한다면 콜론( : )다음에 선언한다 - fun 함수명(매개변수명 : 타입) : 반환타입{ … }
  5. 리턴 값이 존재하지 않는다면 Unit(대부분 생략)요소가 올 수 있다
  6. 함수본문({ , , , , ,})이 존재한다
  7. 코틀린에선 함수본문에 식(expression)이 올 수 있다(타입추론)
  8. 람다함수는 이름이 없을 수 있다.( anonymous function,익명함수)
  9. 람다함수는 변수로 선언 될 수 있다
  10. 람다함수를 리턴 값으로 하거나 파라미터 인자 값으로 줄 수 있다
    (고차함수)

Kotlin 기본 함수 포맷

kotlin 함수 기본 형식

  • 생성자를 제외하고 함수 파라미터는 변경 불가능한 val로 인정
fun paycheck( salary:Int , tax: Float) : Int{
return (salary * tax).roundToInt()
}
  • return type없을 시 unit 명시가능 (보통 생략)
fun paycheck( salary:Int , tax: Float) : Unit{
val result = (salary * tax).roundToInt()
}
  • Single Expression Function(위에 있는 코드)
fun paycheck(salary:Int , tax: Float) : Int = (salary * tax).roundToInt()
  • Single Expression Function X인 예시
    • 함수블록이 2줄 이상 일때는 return타입을 명시 해야함 ———>???? ”왜지 (: Int) 이런표현하면 return타입 있어야 한다는 뜻인가?”
      • single expression function 적용 x

        fun paycheck(annualSalary:Long, tax:Float): Long { //- - - > 반드시 return type이 존재해야 함
        	var salary = annualSalary.div(12)
        	salary -= (salary.times(tax)).toLong()
        	return salary
        }
        fun main(){
        	val monthlySalary = paycheck(90_000_000, 0.07f)
        	println("이번달 월급은 $monthlySalary 입니다")
        }
      • single expression function 적용 o

        fun paycheck(annualSalary: Long, tax: Float) = (annualSalary.div(12) - (annualSalary.div(12)).times(tax)).toLong()
        fun main(){
        val monthlySalary = paycheck(90_000_000, 0.07f)
        println("이번달 월급은 $monthlySalary 입니다")
        }
        

함수 종류

중첩함수

(Nested function)

함수 안에 함수가 있는거

그냥 쓰지마. 복잡해.

멤버함수

(Member Function)

class 안에 구현된 함수 (엄청 쓰지)

oop에서 멤버함수는 behavior을 의미

멤버(instance)함수는 상태(필드,속성)와 캡슐화 되어 동작

class Employee{
	fun paycheck(annualSalary:Long):Long{
		var salary = annualSalary.div(12)
		salary -= (salary.times(0.033f)).toLong()
		return salary
	}
}
fun main(args: Array<String>) {
	val empObj = Employee()
	val salary = empObj.paycheck(90000000)
	println("$salary")
}

Infix 함수

prefix 전위 +ab

infix 중위 a+b

postfix 후위 ab+

  • 일반 프로그래밍에서 잘 안쓰는데, 객체지향에서는 사용함
  • infix함수 - 연산자처럼 쓸 수 있음
  • infix fun 자료형.이름() =
  • 중위, a+b, 함수를 연산자처럼 사용할 수 있다.
  • 조건
    • 클래스의 멤버함수로 선언하거나 확장함수일 때
    • 매개변수가 하나인 함수일 것
infix fun Int.abc(gogo: Int): Int = this * gogo
//class안에서 쓸때는 밑에처럼 표현
//infix fun abc(gogo: Int): Int = this * gogo

fun main(){
 var c = 5 abc 4 //infix함수 호출
    println(c)             //20
    println(6 abc 4)       //24
    println(6.abc(4))//24  위에랑 같음
}

Generic 함수

  • 꺾쇠(<>)괄호를 사용하여 타입이 고정되지 않는 범용적 함수를 구현
    할 때 사용하며 범용 고차함수 구현 시 자주사용
fun <T> selectedList(item: T): List<T>{ /*, , , ,*/

확장함수

(Extension Function)

  • 클래스를 상속받지 않고 기본 클래스에 함수를 추가 구현할 수 있는
    방법을 제공
  • Kotlin 표준 라이브러리는 확장 함수를 이용하여 많은 부분이 구현
    되어 있다
  • OOP의 상속과는 다르며 기존 클래스 이름에 새로 확장되는 함수를
    정의하고 구현하면 된다
  • 확장함수도 해당 클래스의 멤버(instance) 함수가 됨
    • fun .() (상속이랑은 아예 다름)

재귀함수

(Recursive Function)

사용하는 이유

  • 함수의 구현 내부에서 자신을 호출하려고
  • FP에서는 Loop 대신 재귀로 주어진 문제를 해결하는 것이 원칙

사용하는 곳

특징

  • 함수 안에 자기가 한번 더 들어있다
  • FP에서는 loop 대신 재귀를 사용
  • FP는 어떻게(How)가 아닌 무엇(What)을 선언해야 할 지를 고민 (선언형 프로그래밍)

    Loop(for, while, iteration 등)는 어떻게 동작해야 하는지를 명령하는 구문 (명령형 프로그래밍)

  • FP에서는 Loop 대신 재귀로 주어진 문제를 해결하는 것이 원칙
  • 장점 : 반복문에 비해 복잡한 알고리즘을 간결하게 표현
    단점 : 성능이 느림, StackOverflowError
  • 재귀함수 구현 시 반드시 종료조건이 1개 이상 존재해야 한다

사용법

  • 일반적인 재귀구현
    fun factorial(n: Int): Int {
        return if (n <= 1) {
            1
        } else {
            n * factorial(n - 1)
        }
    }
    • 보통의 재귀는 고정 메모리 할당이나 값의 변경없이 Stack Frame을 활용
    • 보통의 재귀 함수는 기본적으로 성능 및 StackOverFlowError를 발생 (위 예제에서 65536 넣으면 너무 수가 커서 발생한다)

꼬리 재귀

(Tailrec, Tail Recursive Function)

사용하는 이유

사용하는 곳

특징

  • 자신의 함수를 호출하면서도 그 호출이 마지막인 것을 의미
  • tailrec 함수는 반드시 맨 마지막 코드에서 자신을 호출 해야함 (마

사용법

  • tailrec 붙여주고, 함수안에서 마지막 코드에 자기자신 호출하기
tailrec fun factorial(n: Int, accumulator: Int = 1): Int {
    return if (n <= 1) {
        accumulator  //이자리에 꼬리재귀함수 와도 상관없음
    } else {
        factorial(n - 1, n * accumulator)
    }
}
  • 문제
    • stack overlfow가 발생 할 수 있음(크기 이상으로 쌓이는거)
    • 마지막 재귀함수는 반드시 재귀함수 콜이어야 함(마지막 return이 재귀함수가 되야함)

일반 재귀 함수와 꼬리 재귀 함수의 차이점

  1. 구조:
    • 일반 재귀: 재귀 호출이 마지막 연산이 아니며, 이후에 추가 연산이 필요합니다.
    • 꼬리 재귀: 재귀 호출이 마지막 연산이므로, 추가 연산이 필요 없습니다.
  2. 스택 사용:
    • 일반 재귀: 각 재귀 호출은 새로운 스택 프레임을 추가하며, 재귀 깊이가 깊어지면 스택 오버플로우가 발생할 수 있습니다.
    • 꼬리 재귀: 꼬리 호출 최적화를 통해 컴파일러는 현재 스택 프레임을 재사용할 수 있어, 재귀를 반복문으로 변환하고 스택 오버플로우를 방지할 수 있습니다.
  3. 성능:
    • 일반 재귀: 깊은 재귀 호출에서 비효율적일 수 있으며, 메모리 사용량이 많아질 수 있습니다.
    • 꼬리 재귀: 메모리 효율성이 높으며, 최적화를 통해 성능이 향상됩니다.

결론
꼬리 재귀를 사용할 수 있다면 tailrec 키워드를 사용하여 컴파일러가 이를 최적화하도록 하는 것이 좋다. 이는 스택 오버플로우를 방지하고 함수의 성능을 최적화하는 데 도움이 됨

Inline function

사용하는 이유

  • 고차(람다) 함수를 사용할 때 발생하는 런타임 오버헤드를 없애는 효과를 낼
    때 주로 사용
    - 고차함수를 실행할 때 마다 객체를 생성(Heap에 할당 == 오버헤드)

사용하는 곳

특징

  • noinline
  • crossinline
  • refied

사용법

  • fun앞에 inline 붙이면됨 (inline fun a(){ .. }

번외

init 함수

클래스에서 오는 함수

생성자와 함께 자세히 배우자

마지막으로 함수란?

  • 내부적인 기능을 가진형태
  • 외부적으로 봤을때는 파라미터를 넣는다는 점 외에는 자료형이 결정된 변수라는 개념으로 접근하자

0개의 댓글