본문 바로가기
캠프 개발일지

TIL - 23.12.05

by JHBang 2023. 12. 5.

오늘은 Kotlin 3주차 강의를 수강했다.

3주차부터 본격적으로 kotlin문법에 대해 배운다. 예전에 배웠던 C++이 생각나는 내용이였다.

 

1. 메소드

메소드는 클래스 내에서 사용하는 함수라고 생각하면 될 거같다.

fun add(num1:Int, num2:Int){
    println("${num1} + ${num2} = ${num1+num2}")
}

이런 형태로 사용한다.

 

 

2. 클래스

kotlin은 객체지향 언어(OOP)이다. 모든 코드를 클래스 형태로 객체화 시킬수 있으며 객체들간의 결합을 통해 유지보수를 쉽게 할 수 있다. 

OOP의 5대 키워드는 클래스, 추상화,  캡슐화, 상속, 다형성이다.

클래스는 각 요소별 설계도라고 볼 수 있다. 하나의 파일 형식으로 저장한다.

클래스는 기본적으로 정보(프로퍼티) + 행위(메소드)로 구성된다.

여기서 프로퍼티만 담고있는 클래스를 데이터 클래스라고 한다.

data class {
  // 정보만 가지고 있는 클래스
}

이런 식으로 클래스 앞에 data를 추가하면 된다. 

씰드 클래스, 에넘 클래스, 오브젝트 클래스 등 다양한 유형이 있지만 후에 심화과정에서 다룰 것이라고 한다.

 

 

 

3. 생성자

클래스를 실체화하는걸 인스턴스화 한다고 한다. 이 떄 실행되는 최초의 로직을 생성자라고 한다.

기본 생성자와 명시적 생성자가 있으며 기본 생성자는 기존 클래스를 생성하는 것 처럼 하면 생성된다.

명시적 생성자는 2개로 나뉘는데, 주 생성자와 부 생성자로 구분한다.

 

3.1 주 생성자 init{...}

주 생성자는 클래스 선언부에 생성자를 명시한다. 매개변수를 생성자로 넘겨서 초기화하지 않는다.

정해진 매개변수가 아니면 오류. 반드시 생성자에서 명시된 형태의 매개변수를 줘야함.

// 선언부에 생성자 명시
class Character(_name:String, _hairColor:String, _height:Double) {
    var name:String = ""
    var hairColor:String = ""
    var height:Double = 0.0

    init {
        println("매개변수없는 생성자 실행 완료!")
    }
}

 

 

 

3.2 부 생성자 constructor{...}

 

부 생성자는 생성자에 프로퍼티를 넘기는 데 사용한다. 클래스 선언부에 생성자를 명시할 필요가 없다.

그렇기때문에 여러 형태로 클래스를 인스턴스화 할 수 있음.(constructor로 정의한 생성자에 한해서!)

fun main() {
		// 불마법사로 객체화
    var magicianOne = Character("불마법사", "red", 180.2)
	println("${magicianOne.name}의 머리색상은 ${magicianOne.hairColor}입니다")
	magicianOne.fireBall()

		// 냉마법사로 객체화
    var magicianTwo = Character("냉마법사", "blue", 162.2, 25, "여")
	println("${magicianTwo.name}의 머리색상은 ${magicianTwo.hairColor}이고 나이는 ${magicianTwo.age}입니다.")
	magicianTwo.fireBall()
}

class Character {
    var name: String = ""
    var hairColor: String = ""
    var height: Double = 0.0
    var age: Int = 0
    var gender: String = ""

    // 명시적 생성자 (Constructor)
    // _name, _hairColor, _height 3개만 받는 형태로 생성자 실행
    constructor(_name: String, _hairColor: String, _height: Double) {
        println("${_name}을 생성자로 넘겼어요")
        println("${_hairColor}를 생성자로 넘겼어요")
        println("${_height}를 생성자로 넘겼어요")
    }

    // _name, _hairColor, _height, _age, _gender 5개를 받는 형태로 생성자 실행
    constructor( _name: String, _hairColor: String, _height: Double, _age: Int, _gender: String)
    {
        println("${_name}을 생성자로 넘겼어요")
        println("${_hairColor}를 생성자로 넘겼어요")
        println("${_height}를 생성자로 넘겼어요")
        println("${_age}를 생성자로 넘겼어요")
        println("${_gender}를 생성자로 넘겼어요")
    }
}

 

위 코드를 보면 불마법사는 이름, 머리색, 키만 매개변수로 준 상태로 객체화했으며, 첫번째 부 생성자로 변수를 넘긴다.

냉마법사는 나이, 성별까지 총 5개의 매개변수를 줬으며 두번째 부 생성자로 변수를 넘긴다.

 

이상은 3주차 중간까지의 내용이며 내일은 3주차 끝까지, 4주차, 가능하면 5주차까지 진도를 나갈 생각이다.

 

 

 

 

 

아래는 오늘 공부를 하면서 추가적으로 찾아본 내용이다.

코드를 짜던 중 var num1 = readLine()!!.toInt() 에서 "값을 입력하세요: " 라는 문구를 띄우고 싶었다.

print("값을 입력하세요: ")
var num3 = readLine()!!.toInt()
println(num3)

보통은 이렇게 쓴다. 하지만 좀 더 간결하게 쓰고 싶었다. print와 readLine을 한번에 쓸 수는 없을까?

 

chat gpt에 물어보니 다음과 같은 답을 주었다.

var num4 = print("값을 입력하세요: ").run { readLine()!!.toInt() }
println(num4)

run이란 함수를 사용해서 구현했는데, run함수는 kotlin의 확장 함수 중 하나로 객체를 사용할 때 명령문을 람다로 묶어서 람다 블록 안의 명령문을 객체가 사용될 때 실행하는 함수라고 한다.

즉 print라는 객체가 실행될 때 람다 블록 { readLine()!!.toInt() }이 실행되는 것이다.

람다는 또 뭐지?

람다는 익명함수 형태로 표현되는 코드 블록이라고 한다. 중괄호{ }안에 명령을 넣는 형식이며 { 매개변수 -> 실행할 코드 }의 형태로 사용한다. 아래는 람다블록 사용 예시.

val multiplyByTwo: (Int) -> Int = { x -> x * 2 }

받은 값을 2배로 만들어주는 코드이다. 람다 블록은 알겠는데, 처음보는 형식이 있다.

(Int) -> Int 

이 부분이였는데, 함수 타입을 나타내는 표기법이라고 한다. kotlin에선 자료형 중에 함수 자료형도 있는것이다!

(사실 다른 언어에서도 있는지 없는지 모른다.)

(Int)는 매개변수 목록이다. 여기선 Int형 1개를 나타내는 중이다.

-> Int 는 반환값의 타입을 나타낸다.

val concatenateFunction: (String, Int) -> String = { str, num -> "$str$num" }
val result2 = concatenateFunction("Year: ", 2023)
println(result2)

이런식으로 매개변수에 다른 자료형을 넣고, 이 자료형들을 string형태로 변환할 수도 있다.

 

이 함수를 공부하던 중 오늘 코드카타에서 풀었던 문제 하나가 생각났다.

두 정수를 받아서 나눈 후 1000을 곱하는 코드였는데, 예를 들어 3과 2를 받은 경우 3 / 2 = 1.5, 여기에 1000을 곱해 결과값이 1500이 나와야 하는 문제였다.

이 문제는 두 수를 정수형으로 받기 때문에 3/2 = 1이 나온다. 이를 해결하는게 문제의 요점이다.

fun solution(num1: Int, num2: Int): Int {
    if(num1 in 1 .. 100 && num2 in 1 .. 100){
        var answer: Int = num1*1000 / num2
        return answer
    }
    else return 0
}
println(solution(3,2))

위 코드는 내가 제출한 풀이였다. 첫 번째 정수에 1000을 곱하는 방법으로 식을 변형해서 해결했다.

하지만 뭔가 내 마음에 들지 않았다. 약간 편법을 사용한 느낌이였다. 물론 .toFloat() 코드를 사용하면  3/2 = 1.5로 나오겠지만 뭔가 이 방법도 사용는데 마음에 들지 않았다.

이 문제를 함수 타입을 이용해서 해결할 수 있지 않을까? 라는 생각이 들었다.

그래서 바로 코드를 작성해봤다.

fun newSolusion(num1: Int, num2: Int, operation: (Int, Int) -> Float): Float{
    if(num1 in 1 .. 100 && num2 in 1 .. 100){
        return operation(num1, num2)
    }
    else return 0f
}
val newResult = newSolusion(3,2) { x, y -> x / y }  // <<== 오류 발생!

하지만 오류가 발생했다.  오류 내용은 다음과 같다.

Type mismatch.
     Required:
     Float
     Found:
     Int

해석해보면 Float 형이 와야하는데 Int형이 왔다는 소리다.

(Int, Int) -> Float의 의미는 자료형의 변환이 아니였다. 단지 반환값의 자료형을 정하는 것이였고 명시한 자료형과 일치하지 않으면 오류가 발생하는 것이였다.

val newResult = newSolusion(3,2) { x, y -> (x / y).toFloat() }
println(newResult)

물론 이렇게 수정해도 1.5는 나오지 않는다. 이미 정수 / 정수 = 정수이기에 1.0이 나올 뿐이다.

 

 

 

 

 

해결했던 코드를 다른방법으로 재구현해 보는 것도 좋은 공부방법인 것 같다. 비록 실패했지만 지식은 는 것 같아서 그나마 좀 위안이 된다...

 

 

'캠프 개발일지' 카테고리의 다른 글

TIL - 23.12.11  (0) 2023.12.12
TIL - 23.12.06  (0) 2023.12.06
TIL - 23.12.04  (0) 2023.12.04
TIL = 23.12.01  (0) 2023.12.01
KPT 회고 - 1주차 미니프로젝트  (0) 2023.12.01