✏️ 0423
Java 문법 종합반 강의_2주차
[ 특강 ] 학습법
Java 문법 종합반_2주차
- Chapter 2
- 다양한 연산자에 대해서 학습하고 사용법 익히기
- 조건문과 반복문을 통해 프로그램의 문맥을 구성하는 방법
- 배열로 연속된 데이터들을 효율적으로 저장하고 관리하는 방법
- 다차원 배열로 좀 더 복잡한 데이터를 저장하고 관리하는 방법
- 컬렉션을 연속된 데이터들을 더욱 효율적으로 저장하고 관리하는 방법
- 여러 컬렉션들의 기능을 익히고 적절하게 사용하는 방법
산술 연산자 | +, -, *, / (몫), % (나머지) | |
비교 연산자 | >, <, >=, <=, ==, != | |
논리 연산 | &&, ||, ! | |
대입 연산자 | =, ++, -- | |
기타 연산자 | (type), ? :, instance of | |
비트 연산자 | <<(왼쪽으로), >>(오른쪽으로) |
2 + 1
2, 1 은 피연산자
+ 는 연산자
int x = 5;
int y = 10;
int z = x + y; // 5(피연산자) +(연산자) 10(피연산자)
System.out.println(z); // 15
- 논리 연산자
- 비교 연산의 결과값으로 받을 수 있는 boolean 값을 연결하는 연산자
- 조건을 연결하였을 때 boolean 값들을 조합하여 참(true) 또는 거짓(false) 값인 boolean 값을 출력
- &&(AND), ||(OR), !(NOT)
boolean flag1 = true;
boolean flag2 = true;
boolean flag3 = false;
// (1) 피연산자 중 하나라도 true이면 true => 또는, OR
System.out.println(flag1 || flag2 || flag3); // true
System.out.println((5 > 3) || (3 < 1)); // true || false = true
System.out.println((5 < 3) || (3 < 1)); // false || false = false
// (2) 피연산자가 모두 true이면 true => 그리고, AND
System.out.println(flag1 && flag2 && flag3); // false
System.out.println((5 > 3) && (3 > 1)); // true && true = true
System.out.println((5 > 3) && (3 < 1)); // true && false = false
// System.out.println(1 < 3 < 5); // 불가능, 무조건 두개씩 비교 가능
// (3) 논리 부정 연산자 => 아니다, !
System.out.println(!flag1); // false
System.out.println(!flag3); // true
System.out.println(!(5 == 5)); // false
이정도는 괜찮은데 길고 복잡한 코드를 작성해야 할 때는 조금 헷갈릴 것 같다...
- 대입 연산자
- 변수를 바로 연산해서 그 자리에서 저장하는(대입하는) 연산자
- =(기본연산자), +=, -=, *=....(복합 대입 연산자)
- ex) ++ : += 1 / -- : -= 1
// 복합 대입 연산자
number = 10;
number += 2; // number = number + 2
System.out.println(number); // 12
number -= 2; // number = number - 2
System.out.println(number); // 10
number *= 2; // number = number * 2
System.out.println(number); // 20
number /= 2; // number = number / 2
System.out.println(number); // 10
number %= 2; // number = number % 2
System.out.println(number); // 0
// ++, --
number++; // number = number + 1
System.out.println(number); // 1
number--; // number = number - 1
System.out.println(number); // 0
기본은 쉬우니까 생략하고
++, -- 의 경우 반복문에서 +1, -1를 이용해 반복 횟수를 count 할 때 주로 쓰는 것 같다
주의할 점은 ++, -- 를 사용할 때 ⭐위치가 중요하다
int a = 10;
int b = 10;
int val = ++a + b--; // 11 + 9 = 20 ??
System.out.println(a); // 11
System.out.println(b); // 9
System.out.println(val); // 21
System.out.println(a + b); // 20
++ 사인이 앞쪽에 있으면 먼저 +1을 a를 계산한 후에 계산을 하지만
-- 사인이 뒤쪽에 있으면 모든 연산이 끝난 후에 b를 계산한다
결과가 20인 것을 보고 싶으면 --를 앞으로 옮기거나 a와 b를 다시 호출해서 불러와야 한다
- 기타 연산자
- 형변환 연산자
- 삼항 연산자 : 비교 연산자와 항상 함께 쓴다
- instance of (3주차 -> 클래스, 객체 배울 때 더 자세히)
// (1) 형변환 연산자
int intNumber = 93 + (int)98.8;
System.out.println(intNumber); // 191
double doubleNumber = (double)93 + 98.8;
System.out.println(doubleNumber); // 191.8
// (2) 삼항 연산자
// 비교연산자의 결과 : true or false -> 이 결과의 값에 따라 결정되는 무언가
// 조건 ? 참 : 거짓
int x = 1;
int y = 9;
boolean b = (x == y) ? true : false;
System.out.println(b); // false
String s = (x != y) ? "정답" : "오답";
System.out.println(s); // 정답
int max = (x > y) ? x : y;
System.out.println(max); // 9
// (3) instance of(3주차 -> 클래스, 객체)
// 피연산자가 조건에 명시된 클래스의 객체인지 비교하여
// 맞으면 -> true
// 틀리면 -> false
삼항 연산자는 짧은 if문을 사용할 때 대체해도 된다
if 문을 사용하는 것보다 코드가 짧고 간단해서 쓰기 좋아 보인다
그런데 눈에 안 익으면 조건이 어디까지고 (물음표) 뭘 반환해 주지? (참 : 거짓)하면서
살짝 헷갈리는 것 같기도 하다... if문이 더 익숙해서 그런가
- 연산자의 우선순위
- 산술 > 비교 > 논리 > 대입
- 연산자 여러 개가 함께 있는 연산을 계산할 때는 우선순위가 있다
- 위 우선순위에 따라 최종적인 응답값이 결정된다
- 단, 괄호로 감싸주면 괄호 안의 연산이 최우선 순위로 계산된다
이 내용은 사실 학교 다니면서 수학을 배우다 보면 반사적으로 계산이 되는 내용이 아닐까 싶지만
이론으로 설명하면 그렇다고 한다
알고 있는 내용이어도 머릿속에 다시 한번 정리하기
- 두 피연산자의 데이터 타입이 다를 때
- 둘중에 저장공간 크기가 더 큰 타입으로 일치
- 피연산자의 타입이 int 보다 작은 short 타입이라면 int 로 변환
- 피연산자의 타입이 long 보다 작은 int, short 타입이라면 long 로 변환
- 피연산자의 타입이 float 보다 작은 long, int, short 타입이라면 float 로 변환
- 피연산자의 타입이 double 보다 작은 float, long, int, short 타입이라면 double 로 변환
이처럼, 변수 여러 개를 연산했을 때 결과값은 피연산자 중 표현 범위가 가장 큰 변수 타입을 가지게 된다
short x = 10;
int y = 20;
int z = x + y;
long lx = 30L;
long lz = z + lx;
float fx = x;
float fy = y;
float fz = z;
System.out.println(lz); // 60
System.out.println(fx); // 10.0
System.out.println(fy); // 20.0
System.out.println(fz); // 30.0
대망의 비트 연산....😱
- 비트 연산
- Bit 의 자리수를 옮기는 것을 비트 연산이라고 한다
- (Byte 8등분) Bit => Bit 는 0,1 둘중의 하나의 값만 저장하는 컴퓨터가 저장(표현)가능한 가장 작은 단위
- 컴퓨터의 가장 작은 단위인 Bit 이기 때문에 연산 중에서 Bit 연산이 제일 빠르다
- 0,1 값으로 산술연산을 하거나, 비교 연산을 할 수 있지만 비트 연산을 통해 자릿수를 옮길수도 있다
- 0,1 은 2진수 값이기 때문에
- => 자리수를 왼쪽으로 옮기는 횟수만큼 2의 배수로 곱셈이 연산되는 것과 동일
- => 자리수를 오른쪽으로 옮기는 횟수만큼 2의 배수로 나눗셈이 연산되는 것과 동
// `<<` (왼쪽으로 자리수 옮기기)
System.out.println(3 << 2); // 12
// 3 => 11 -> 1100 => 8 + 4 = 12
// `>>` (오른쪽으로 자리수 옮기기)
System.out.println(3 << 1); // 6
// 3 => 11 -> 110 => 4 + 2 = 6
솔직히 연산 중에 비트 연산이 가장 어려운 것 같은데 이것도 정리되면 아마 마냥 어렵지는 않을 것이다
이전에 네트워크 보안을 학습하면서 배운 방법이다
아무래도 IP 주소를 자주 만지기 때문에 bit 계산법을 공부한 적이 있다
조금 다른 이야기이긴 한데 다들 알다시피 IP주소는 192.168.5.50 이런 형식으로
총 4개의 Octet 이며 하나의 Octet 은 8bit 로 구성되어 있다
※ Octet : 8개의 bit 가 모인 것 / Byte와 같은 뜻
이것보다 더 쉬운 계산법이 있다면... 댓글로 알려줬음 좋겠다 😭😭
2진수 변환 계산법
2의 8승은 256이고 byte 데이터 타입이 표현 가능한 범위이기도 하다 ( IP는 0부터 시작하므로 0~255만 표현가능 )
위의 192.168.5.50 IP 주소를 2진수로 변환해보자
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | |
192 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
168 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
5 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
50 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 |
설명하자면 2의 제곱수를 나열해서 2진수를 구해야하는 값의 총합이 같은 걸 찾으면 된다
위의 내용을 토대로 다시 설명해 보자면,
만약 11 이라는 숫자의 2진수를 구하고 싶다고 하면
일단 16 을 넘지 않으니 그 앞에 숫자들은 전부 필요 없으니 없애준다
8 | 4 | 2 | 1 | |
11 | 1 | 0 | 1 | 1 |
11 = 8 + 0 + 2 +1
이런 식으로 하면 11의 2진수는 1011 이 되는 것이다
물론 이것보다 훨씬 간단한, 대부분 설명할 때 해주시는 나누기 방법도 있지만
나는 가끔 살짝 헷갈리는 편이기도 하고 여러번 2진수에 대해 배웠지만
처음으로 확실하게 머릿속으로 이해한 건 위의 방법이라 이렇게 표로 정리해서 계산하는 게 편하다
물론 나누기 방법보다 오래 걸릴 수 있다..😅
혹시라도 나처럼 나누기 방법이 이해하기 어렵다 싶은 사람은 먼저 이런 식으로 표로 작성해보고
적응했다 싶을 때 나누기 방법을 사용하면 훨씬 이야하기 쉬울 것 같다
조건문 & 반복문
조건문에는 if문과 switch문 두가지가 있다
if문이 평소에 좀 더 많이 보이지만 switch문을 사용하는 곳도 있다
- 차이점
- if문은 복합 조건이 가능하고, switch문은 단일 조건만 가능
- 상대적으로 if문이 코드 중복이 많고 switch문은 코드 중복이 적다
- 그렇기에 길어질수록 가독성은 switch문이 더 좋은 편
반복문에는 for 문과 while 문, do-while 문이 있다
- 차이점
- for문은 초기값이 존재하고 while 문은 조건문만 존재한다
- for문은 조건문으로 true&false 를 판단하고 만족할 때 실행, while문은 조건문이 false이 될 때까지 반복 실행
- 구하고자 하는 값이 정확한 조건(범위)가 있는 경우 for문
- 구하고자 하는 값이 정확한 조건(범위)를 모르는 경우, 유동적인 경우 while문
// 향상된 for 문
// 기존 : for 문 안에 3개의 표현이 들어감 -> (초기값 ; 조건문 ; 증가연산)
// 향상된 for 문 : 2개로 줄어듬
int[] numbers = {3, 6, 9, 12, 15};
for(int number: numbers) {
System.out.print(number);
}
// 위의 코드와 동일
for(int i=0; i < numbers.length; i++) {
System.out.print(numbers[i]);
}
아래의 코드 작성 방법에 대해선 알고 있었지만
위의 코드 처럼 적어도 되는 건지는 처음 알았다!
훨씬 간결하고 해석하기 쉬워 보인다
// do ~ while
// 초기 조건이 만족하지 않더라도 수행해야 하는 것이 있을 때
int number1 = 4;
do {
// 이 로직을 먼저 수행
System.out.println(number + " 출력!");
} while (number1 < 3);
do-while문은 처음 써봤다
초기 조건이 맞지 않더라도 최소 1번은 코드를 실행한다고 한다
사알짝 헷갈리는 것 같다
그래서 코드를 살짝 수정해서 다시 봤다
그럼 최초의 4일때 먼저 실행한 후, 조건문 검사하고 맞으면 다시 반복에 들어간다
6까지 출력되는 걸 볼 수 있다
역시 헷갈릴 때는 다른 것으로 바꿔서 해보는 것이 최고다
그런데 조건문 잘 못 수정하다가 무한 루프에 들어갈 수 있으니 조심하자 😅
난 무한 루프를 봤다 하하하
// continue(계속하다)
// continue 에 걸리는 순간 하위 로직들을 생략, 패스하고 다음 반복으로 넘어간다
int number3 = 0;
while(number3 < 3) {
number3++;
if (number3 == 2) {
continue; // 2일때 반복 패스
}
System.out.println(number3 + "출력");
}
break에 대해선 알고 있었지만 continue에 대해서는 처음 알게 되었다
신기하게 continue가 들어간 조건에 맞으면 그 아래있는 모든 코드를 생략, 패스하고 다음 반복문으로 넘어간다고 한다
그래서 위의 코드는 1과 3만 출력이 된다
배열
배열 Array
한 번에 많은 양의 데이터를 다루거나 계산할 때 사용
타입[ ] 변수; / 타입 변수[ ];
배열 선언과 생성을 동시에
//배열 생성후 초기화하면 배열의 주소가 할당된다
int[] intArray = new int[3];
intArray 라는 배열에 int 타입의 공간이 3칸 만들어졌다
배열은 생성될 때 각 타입별로 초기값으로 초기화되어 채워진다
int는 0, boolean 은 false, String 은 null 값과 같은 초기값이 정해져 있다
위의 배열에는 {0, 0, 0} 이 들어가 있는 것이 된다
// 초기화
int[] inArr = {1,2,3,4,5};
String[] strArr = {"A","B","C","D"};
// 배열의 주소를 모두 값은 값으로 초기화
// fill(배열이름, 바꿀 값);
Arrays.fill(intArr,1);
for (int j : intArr) {
System.out.println(j);
}
배열을 초기화할 때는 3가지 방법이 있다
중괄호 { } 를 사용해서 배열에 특정값 대입하는 것
for문을 사용해서 값을 대입하는 것
Arrays.fill 메소드를 사용하는 것
초기화하는 법은 알고 있어서 생략하려고 했는데
fill 메소드는 처음봐서 메모하는 용으로 추가했다
얕은 복사, 깊은 복사
처음 설명을 들었을 때
이건 정말 머리 위로 갈고리❓를 수십개 만든 상태로 설명을 들었고
여러번 되돌려서 다시 봤다
얕은 복사 : 주소값만 복사되고 실제 값은 1개로 유지되는 것
깊은 복사 : 주소값 복사가 아닌 똑같이 생긴 새로운 배열을 복사하는 것
정리하면 약간 이런 느낌 아닐까?
아님 말구...
// 얕은 복사
int[] a = { 1, 2, 3, 4 };
int[] b = a; // 얕은 복사 (같은 주소)
b[0] = 3; // b 배열의 0번째 순번값을 3으로 수정했습니다. (1 -> 3)
System.out.println(a[0]); // 3
// 깊은 복사
// 1. clone() 메서드
int[] a = { 1, 2, 3, 4 };
int[] b = a.clone(); // 가장 간단한 방법입니다.
// 하지만, clone() 메서드는 2차원이상 배열에서는 얕은 복사로 동작
// 2. Arrays.copyOf() 메서드
// 배열의 값과 길이를 전부 복사한 후 새로운 배열에 넣어준다
int[] c = Arrays.copyOf(a, a.length);
a[3] = 0; // a 배열의 4를 0으로 변경
System.out.println(a[3]); // 0
System.out.println(c[3]); // 4
문자(char)와 문자열(String)
String = char[ ]
- 기본형 변수 vs 참조형 변수
- 기본형 변수는 ' 소문자 시작 ', 참조형 변수는 ' 대문자 시작 '
- Wrapper class 에서 기본형 변수를 감싸줄 때 (boxting), int => Integer
- 기본형 변수는 값 자체를 저장
- 참조형 변수는 별도의 값을 저장 후 그 주소를 저장(=주소형 변수)
기본적으로 String 변수를 많이 쓰고 있고
그 이유는 String 이 가지고 있는 기능이 많기 때문이다
Wrapper class 와도 비슷하다
기본형 변수가 가지고 있는 기능은 제한되어 있지만
다양한 기능을 제공하는 Wrapper 을 감쌈으로써 추가 기능을 사용할 수 있다
// String 기능 활용 예시
String str = "ABCD";
// (1) length
int strLength = str.length(); // 4
// (2) charAt(int index)
char strChar = str.charAt(1); // B (두번째)
// (3) substring(int fromIdx, int toIdx)
String strSub = str.substring(0, 3); // ABC (0번째부터 2번째까지)
// (4) equals(String str)
// 문자 비교
String newStr = "ABCDE";
boolean strEqual = newStr.equals(str); // false
// (5) toCharArray() : String -> char[] 변환
char[] strCharArray = str.toCharArray();
// (6) 반대로 char[] -> String -> char
char[] charArray = {'a', 'b', 'c'};
String charArrayString = new String(charArray);
// charArray 배열의 내용물을 전부 합쳐서 새로운 String 으로 변환
※ char 배열에는 없는 String 이 가지고 있는 기능
메소드 | 응답값 타입 | 설명 |
length() | int | 문자열의 길이 반환 |
charAt(int index) | char | 해당 index의 문자를 반환 |
substring(int from, int to) | String | 해당 범위에 있는 문자열 반환 (from ~ to / to 는 범위에 포함X) |
equals(String str) | boolean | 문자열 내용이 같은지 확인 |
toCharArray() | char[ ] | 문자열을 문자 배열로 반환 |
new String(char[] charArr) | String | 문자 배열을 받아서 String으로 반환 |
다차원 배열
2차원 배열일 때 약간 함수 같기도..
int[][] array = new int[3][];
2차원 배열을 생성할 때 열의 길이를 생략할 수 있고 행마다 다른 길이의 배열을 요소로 저장 가능
array[0] = new int[2];
array[1] = new int[4];
array[2] = new int[1];
int[][] array2 = {
{10, 20},
{10, 20, 30, 40},
{10}
};
컬렉션 (Collection)
- 배열보다 다수의 참조형 데이터를 더 쉽고 효과적으로 처리할 수 있는 기능이 많다
- 기능 : 크기 자동조정 / 추가 / 수정 / 삭제 / 반복 / 순회 / 필터 / 포함확인 등...
- 종류 : List, Set, Queue, Map, Stack
- Collection 은 기본형 변수가 아닌 참조형 변수를 저장한다
- List
- 순서가 있는 데이터의 집합 => Array (최초 길이를 알아야 초기화 가능)
- 처음에 길이를 몰라도 만들 수 있음
- Array => 정적배열 / List(ArrayList) => 동적배열 (크기가 가변적으로 늘어난다)
- 생성 시점에 작은 연속된 공간을 요청해서 참조형 변수를 담아놓는다
- 값이 추가될 때 더 큰 공간이 필요하면 더 큰 공간을 받아서 저장하니까 상관없다
ArrayList<Integer> intList = new ArrayList<Integer>(); // 선언 + 생성
intList.add(99); // add 값 추가
intList.add(15);
System.out.println(intList.get(0)); // 맨 처음에 추가된 값이 출력 (index)
// 2번째 있는 값(15)를 바꿔보기
intList.set(1, 10); // .set(index, 바꿀 값)
// 삭제
intList.remove(0);
System.out.println(intList.get(0)); // 첫번째 값이 삭제되면서 두번째 값이 앞으로 온다
intList.clear(); // 전체 삭제
System.out.println(intList.toString()); // 전체 보기
- Linked list
- 메모리에 남는 공간을 요청해서 여기 저기 나누어서 실제 값을 담는다
- 실제 값이 있는 주소값으로 목록을 구성하고 저장하는 자료구조
- 기본적 기능은 ArrayList 와 동일
- linkedList 는 값을 여기 저기 나누어서 조회 속도가 느리다
- 대신 값을 추가하거나, 삭제할 때는 빠르다
LinkedList<Integer> linkedList = new LinkedList<Integer>();
추가, 수정, 삭제, 전체 삭제 등 기능 사용은 ArrayList 와 동일하다
- Stack
- 수직으로 값을 쌓아놓고 넣었다가 뺀다
- 나중에 들어간 것이 가장 먼저 나온다 (Last-In-First-out) ex) 바구니
- 최근 저장된 데이터를 나열하고 싶거나, 데이터의 중복 처리를 막고 싶을 때 사용
- push, peak, pop
Stack<Integer> intStack = new Stack<Integer>();
intStack.push(10); // 값 넣기
intStack.push(15);
// 다 지워질때까지 출력
// intStack.isEmpty() => 비어있으면 ture, 있다면 false
while (!intStack.isEmpty()) {
System.out.println(intStack.pop()); // 15, 10
}
// peak
System.out.println(intStack.peek()); // 맨 위에 있는 값을 불러옴
System.out.println(intStack.size()); // 사이즈 확인
- Queue
- 생성자가 없는 인터페이스
- 한쪽에서 데이터를 넣고 반대쪽에서 데이터를 뺄 수 있는 집합
- FIFO ( First In First Out ) : 먼저 들어간 순서대로 값을 조회 ex) 원기둥, 빨대
- add, peak, poll
Queue<Integer> intQueue = new LinkedList<>();
intQueue.add(1); // 값 추가
intQueue.add(5);
while (!intQueue.isEmpty()) {
System.out.println(intQueue.poll()); // 처음 입력한 값이 먼저 나온다
}
// peak
System.out.println(intQueue.peek()); // 맨 처음에 들어간 값
System.out.println(intQueue.size()); // 사이즈 확인
- Set
- 집합과 닮음 : 순서 없고, 중복 없음
- 순서가 보장되지 않는 대신 중복을 허용하지 않도록 하는 프로그램에서 사용할 수 있는 자료구조
- Set 은 생성자가 없는 껍데기라서 바로 생성할 수 없음
- Set -> 그냥 쓸 수 있음, 그러나 HashSet, TreeSet 등으로 응용해서 같이 사용 가능
- HashSet : 가장 빠르며 순서를 전혀 예측할 수 없다
- TreeSet : 정렬된 순서대로 보관하며 정렬 방법을 지정할 수 있다
- LinkedHashSet : 추가된 순서, 또는 가장 최근에 접근한 순서대로 접근 가능
Set<Integer> intSet = new HashSet<Integer>();
intSet.add(1);
intSet.add(12);
intSet.add(5);
intSet.add(9);
intSet.add(1);
intSet.add(12);
for (Integer value : intSet) {
System.out.println(value); // 1, 5, 9, 12
}
// contains
System.out.println(intSet.contains(2)); // 2를 포함하고 있는지 질문
System.out.println(intSet.contains(5)); // true or false 값을 출력함
- Map
- key - value pair 중요
- key 값을 기준으로 vlaue를 조회 가능
- key 값 단위로 중복을 허용하지 않는다
- Map -> hashMap, TreeMap 으로 응용
- HashMap : 중복을 허용하지 않고 순서를 보장하지 않음, 키와 값으로 null이 허용
- TreeMap : key 값을 기준으로 정렬을 할 수 있다 / 다만, 저장 시 정렬(오름차순)을 하기 때문에 저장시간이 다소 오래 걸림
Map<String, Integer> intMap = new HashMap<>();
// 키 값
intMap.put("일", 11);
intMap.put("이", 12);
intMap.put("삼", 13);
intMap.put("삼", 14); // key 중복
// key 값 전체 출력
for (String key : intMap.keySet()) {
System.out.println(key); // 중복된 key 는 생략
}
// value 값 전체 출력
for (Integer value : intMap.values()) {
// 중복된 key 는 마지막에 추가된 값으로 저장
System.out.println(value);
}
// "삼" 이라는 key 를 가진 value 를 가져옴
System.out.println(intMap.get("삼"));
컬렉션을 알아보았다
뭔가 외워야 할 것 같은 게 참 많다...
생성자가 없는 Queue 라던가, Set 이라던가, Map 은 어려워 보인다
3주차에 더 자세하게 설명한다고 되어 있는데 벌써부터 두렵다
2주차 숙제는 내일 한 번 더 정리해보고
풀어보는 것으로 해야겠다
슬슬 어려워 지려고 시동 거는 느낌이다!
'개발 일지 > TIL' 카테고리의 다른 글
[ #9 ] TIL (3) | 2024.04.25 |
---|---|
[ #8 ] TIL (0) | 2024.04.24 |
[ #6 ] TIL (0) | 2024.04.22 |
[ #5 ] TIL (0) | 2024.04.20 |
[ #4 ] TIL (0) | 2024.04.20 |