오늘은 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 |