💙 들어가며
(TMI)순서가 조금 뒤죽박죽이 될 것 같아서 날짜를 붙여보려고 한다.
1.
앞으로 자바 번역기를 활용해서 여러 연산들을 처리하게 될 것인데,
이를 위한 준비물로 다양한 연산자와 형 변환을 하는 방법을 배우게 되었다.
2.
또, 실제 현업에서는 문자열을 많이 다루게 되겠지만,
그래도 초기 프로그래밍의 정수였던 숫자, 바이트, 곧 메모리를
효율적으로 사용할 수 있는 방식의 코딩도 경험해보면서 코딩에 조금씩 스며들어 보자.
✏️ 학습내용 정리
#문자열 다루는 연습문제
오늘 수업은 연습문제를 다루는 것으로 시작하였다. 제한 시간 10분...!
이 문제를 풀 줄 알게 되는 것이 오늘 학습 목표의 70% 이상이었다고도 볼 수 있겠다..!
(오늘 수업을 다 들은 뒤에 다시 풀어보니 새삼 감격스럽다..! 근데 10분 넘었던가 ㅎ..)
Q. students3.data파일을 만들어서 성을 제외한 나머지 문자열을 students3-out.data파일에 출력해보자.
1) 5회 반복이 되어야 하는데, 그냥 i<5라고 값을 고정한 후 반복문을 작성했다.
2) 마지막 줄은 출력되고 난 뒤에 개행(\n)되지 않도록 조건문을 넣었다.
// 사전에 students3.data 파일을 만들어 두었다.
파일 내용:
홍길동
김수한무거북이와
이순신
김진
얍
package com.newlecture.app.prorammers;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Scanner;
import java.util.jar.Attributes.Name;
public class Program {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("res/students3.data");
Scanner fscan = new Scanner(fis);
FileOutputStream fos = new FileOutputStream("res/students3-out.data");
PrintWriter fout = new PrintWriter(fos, true, Charset.forName("UTF-8"));
for(int i=0;i<5;i++) {
String name_ = fscan.nextLine();
String name = name_.substring(1);
String nameFinal = (name.length()>=1)?name:"오류";
if (i<4) {
fout.println(nameFinal);
} else {
fout.print(nameFinal);
}
}
fout.close();
fos.close();
fscan.close();
fis.close();
System.out.println("작업완료");
}
}
한 줄씩 다시 쳐보니 스트림 인스턴스의 사용 이유와 메소드의 작동원리를 조금씩 알 것 같다.
잘못된 부분이 있을 때 구조와 맥락을 파악해가는 능력이 조금씩 자라는 것 같다.
보다 🚨철저한 복습🚨을 위해 파일 스트림에 대해 배웠던 내용을 다시 한 번 상기해보자.
#파일 스트림 (인풋, 아웃풋)
FileInputStream fis = new FileInputStream("res/students1.data"); // 바이트로 읽어옴
Scanner fscan = new Scanner(fis); // 텍스트로 읽어옴 (통째로)
FileOutputStream fos = new FileOutputStream("res/students1-out.data"); // 바이트로 출력
PrintWriter fout = new PrintWriter(fos, true, Charset.forName("UTF-8")); // 텍스트로 읽어옴 (통째로)
fout.close(); // 가장 마지막에 선언한 것부터 close 해준다.
fos.close();
fscan.close();
fis.close(); // 가장 처음에 선언한 것이 마지막 close가 된다.
맨 먼저 파일을 읽어주기 위해서는 input스트림을, 파일로 출력하기 위해서는 output스트림을 썼다.
두 개의 특징은 하나씩 바이트 단위로 파일을 읽고, 출력한다는 것이다.
즉, 구체적으로 말하자면 input 스트림의 .read() 메소드와
output 스트림의 .print(), .write(), .flush() 메소드들은 모두 바이트 단위로 작업을 하고 결과를 출력한다.
때문에 input은 Scanner와 output은 PrintWriter와 함께 사용될 경우가 많겠다.
Scanner와 PrintWriter는 해당 값을 텍스트로 읽어오고 출력한다.
바이트와 다르게 통째로 읽기 때문에 문자열 작업을 할 때 훨씬 수월하겠다‼️
#형 변환 (명시적, 묵시적)
정수를 정수로 나누면 소수점이 나와도 0으로 처리된다(값 날아감)는 것을 알고 있었는지?
생각해보면 당연한데, 설명을 들으니까 괜히 낯설게 다가왔다.
때문에 소수점이 나오는 실수 형태로 출력이 될 경우에는
자료형을 정수에서 실수로 변환해줘야 하는데, 2가지 방식이 있다.
바로 명시적(그럴꺼야!), 묵시적(그러려니)의 방식이다.
- 명시적 형 변환
(우리가 수동으로)
데이터 타입이 맞지 않을 때 직접 선언하여 데이터 타입을 바꿔주는 것
(EX) avg = (float)(total / 3.0); // 자료형을 확실히 밝혀주고 float으로 계산함
- 묵시적 형 변환
(컴파일러가 자동으로)
컴파일 하면 컴파일러가 알아서 데이터 타입을 인식하고 바꿔줄 수 있게 표현
(EX) avg = total / 3.0f; // 컴파일러가 float이라고 이해하고 소수점까지 계산
Q. total/3.0f에서 total은 어떤 자료형으로 인식했을까?
avg = total / 3.0f;
위에 int형으로 total이 선언된 상태에서
total값을 float형인 3.0f으로 나눴다.
그렇다면 이 계산은 정수형/실수형으로 나눈 것일까?
정답은 No이다.
묵시적으로 total도 float이라고 인식하고 계산이 되었다.
즉 float(total)을 float(3.0f)로 나눈 것이다.
이로써, 다음과 같이 int로 선언된 total을 float로 나누는 식이 가능해지게 되었다.
public class Hello {
public static void main(String[] args) {
// 자료형
int kor1, kor2, kor3;
int total;
float avg;
// 변수 대입
kor1 = 50;
kor2 = 60;
kor3 = 80;
byte x = (byte)300; // byte는 2^7-1까지만 표현 가능함
// 묵시적 형변환
total = kor1 + kor2 + kor3;
avg = total / 3.0f;
// 나누는 값을 실수로 해서 total값도 실수로 생각하고 값도 실수형으로 반환
// 출력
System.out.printf("total is %d\n", /* 정수형 */ total);
System.out.printf("avg is %.2f\n", /* 실수형 */ avg);
// %.2f\n은 소수점 2자리까지 표시한다는 뜻
System.out.println(x);
}
#연산자와 피연산자
오늘부터는 본격적으로 자바로 코딩할 준비를 시작한 느낌이었다.
코딩을 하면서 필수로 알아야 하는 것.. 연산자..
연산자와 형변환에 대해서 좀 다뤄본 시간이었다.
아 그런데 선생님께서 피연산자라는 개념을 사용하셔서
명확히 개념을 짚고 넘어가려고 찾아보니 이런 연관되는 의미 차이가 있었다.
연산자(Operator) | 피연산자(Operand) |
연산을 수행하는 기호 | 연산에 참여하는 변수나 상수 |
생각해보니 단어의 이름(피)에서 이미 성격이 드러나는 것이었는데 왜 이리 낯설게 느껴진건지..
두 개를 두고 정리해보니 확실하게 이해가 되었다.
연산자에 따라 피연산자가 올 수 있는 종류와 개수도 달라지니 잘 이해해두자.
아! 그리고 선생님께서 연산이 맞게 되고 있더라도 적절하게 소괄호를 써주는 것이 좋다고 하셨다.
연산자를 활용하는 것과 동시에 소괄호로 구조화 시켜주는 것을 항상 습관들여야겠다.
#연산자의 종류와 우선순위
그럼 본격적으로 연산자의 종류에 대해서 알아보고,
동시에 여러 타입의 연산자가 사용될 경우 자바는 어떤 것을 우선으로 해석하는지 살펴보자.
종류 | 연산자 | 우선순위 |
증감 연산자 (increment and decrement operators) *유일한 단항 연산자 |
++, – |
1순위 |
산술 연산자 (arithmetic operator) |
+, -, *, /, % | 2순위 |
시프트 연산자 (Shift operator) |
>>, <<, >>> | 3순위 |
비교 연산자 (comparison operator) |
>, <, >=, <=, ==, != | 4순위 |
비트 논리 연산자 (bitwise operator) |
&, |, ^, ~ | ~만 1순위, 나머지 5순위 |
논리 연산자 (logical operator) |
&&, ||, ! | !만 1순위, 나머지 6순위 |
조건(삼항) 연산자 (ternary operator) |
?, : | 7순위 |
대입 연산자 (assignment operator) *~한 후에 대입한다. |
=(단순 대입) *=, /=, %=, +=, -=(복합 대입) |
8순위 |
인스턴스오브 연산자* (instanceof operator) |
true, false |
* instanceof 연산자
(나중에 객체지향파트에서 다시 배울거라 지금은 넘겨도 됨!)
원래 인스턴스의 형이 맞는지 여부를 체크하는 키워드이다. 맞으면 true 아니면 false를 반환한다.
보다 자세한 설명이 필요한 부분은 아래에서 세밀히 다뤄보겠다.
#단항 연산자
피연산자가 하나라는 뜻에서 단항 연산자라고 한다.
(모든 연산자 중에서 유일하게 피연산자가 1개이다. 나머지는 다 2개 이상!)
++이 피연산자의 앞에 붙을 수 있고 뒤에 붙을 수 있는데,
이 순서 차이로 출력값이 달라지는 결과를 낳는다.
++i(더하고 출력)와 i++(출력한 뒤에 더함)의 차이를 미리 이해해두자!
참고로 변형본 --i(빼고 출력)과 i--(출력한 뒤에 빼기)도 있다.
int i=1;
System.out.println(++i) // 출력값: 2 먼저 더한 뒤에 값을 출력
System.out.println(i++) // 출력값: 2 먼저 값을 출력한 뒤에 더함
System.out.println(i) // 출력값: 3 i++이 된 최종 값
#비교/논리 연산자
👍🏻오늘 알게 된 사실 중에 제일 유용했던 것‼️👍🏻
java, C언어에서는 변수 앞 뒤에 비교 연산자를 동시에 사용할 수 없다.
반드시 논리 연산자&&이나 ||와 같이 쓰면서 소괄호를 적용해야 한다.
(EX) 늘 보기 쉽고 구분되기 쉽게 쓸 것
- 연산자 사용 방법
Q. 자바에서 -2보다 크고 2보다 크거나 같은 x값을 표시해라
틀린 표현: -2 < x <= 2
맞는 표현: ( -2<x ) && ( x<=2 ) // 따로따로 소괄호를 써라 ( -2<x && x<=2 )라고 쓰지 않는다. - 소괄호 사용 방법
Q. 자바에서 2보다 작거나 4보다 큰 값 x를 표시해라
틀린 표현: ( x<2 || 4<x )
맞는 표현: ( x<2 ) || (4<x) // 조금 더 보기 편하게 다음과 같이 작성! - &&와 ||을 같이 쓰는 방법
Q. 자바에서 -2보다 작거나 2이상 4이하의 값 x를 표시해라
맞는 표현: ( x<-2 ) || ( ( 2<=x ) && ( x<= 4) ) // 사이값을 표시할 경우에는 변수를 가운데로!
#비트 논리 연산자
비트 논리 연산자는 사실 일반적으로 많이 사용하지 않지만
숫자(바이트)를 이용하는 경우에 사용하는 경우가 있으니 잘 이해해두자.
1. & // 교집합의 개념이다. (AND)
- 거짓+거짓 = 거짓
- 거짓+참 = 거짓
- 참+거짓 = 거짓
- 참+참 = 참
2. |: 합집합의 개념이다. (OR)
- 거짓+거짓 = 거짓
- 거짓+참 = 참
- 참+거짓 = 참
- 참+참 = 참
3. ^ : 차집합의 개념이다.
차이가 있어야 참 (1)
없으면 거짓 (0)
0011 & 0111 ▶ 0100이 된다.
4. ~ : 반대의 개념이다. (NOT)
~0011 ▶ 1100이 된다.
~0111 ▶ 1000이 된다.
#시프트 연산자
3 >> 2 ▶ 0011 >> 2 ▶ 0000 ▶ 0
뒤로 밀리면서 남은 부분은 맨 앞자리와 같은 숫자로 채운다.
0011에서 뒤로 두자리가 밀려나고 앞에 남은 부분은 맨 앞자리(0)와 같은 값으로 채운다. ▶ 0000
1010에서 뒤로 두자리가 밀려나고 앞에 남은 부분은 맨 앞자리(1)와 같은 값으로 채운다. ▶ 1110
#조건(삼항) 연산자
유일하게 항이 3개인 연산자이다.
조건에 맞으면 참에 설정한 값이 나오고,맞지 않으면 거짓에 설정한 값이 나오게끔 설정할 수 있다.
변수 = (조건)?[참일 경우 나올 값]:[거짓일 경우 나올 값]
(삼항 연산자를 변수에 대입하여 조건에 따른 기본값을 설정할 수도 있겠다.)
#Big Endian vs Little Endican
오늘 수업에서 많은 이들을 가장 고통스럽게 했던 개념이다..
이름의 뜻 자체는 계란을 깰 때 뭉툭한 끝(big-end)를 먼저 깨는 사람들(빅엔디언)과
뾰족한 끝(little-end)를 먼저 깨는 사람들(리틀 엔디언) 사이에
격론이 벌어진 데서 따온 것이라고 한다.
간단하게 설명하자면 데이터를 '요어했고수'와 '수고했어요' 중
어느 방식으로 저장할 것이냐의 차이라고 볼 수 있겠다.
따라서 저장 방식의 차이일 뿐 어느 것이 더 우수하다고는 할 수 없다.
두 가지 개념에 대해서 이해하기 전에 먼저 기본적으로 알아두어야 할 것은
H E L L O (→)왼쪽(H)에서 오른쪽(O)으로 갈수록 높은 주소라는 것이다.
정말 컴퓨터에 문외한이라서 아주 조금씩 baby-step으로 이해해나가고 있는데,
오늘 파악한 결과를 짧은 지식으로나마 정리해보자면
- Big Endian (CISC-M1 CPU)
'수고했어요'라고 저장되는 느낌 (왼 쪽 부 터 저 장)
데이터의 각 바이트를 배열처럼 취급할 때에는 빅 엔디안 방식이 더 적합
사람의 언어와 비슷해서 디버깅에 용이하다.
- Little Endian (LISC-Intel CPU)
'요어했고수'라고 저장되는 느낌 (오 른 쪽 부 터 저 장)
물리적으로 데이터를 조작하거나 산술 연산을 수행할 때에는 리틀 엔디안 방식이 더 효율적
계산이 빠르다.
사용중인 CPU의 저장 방식에 따라서 달라지겠지만
AMD를 사용하는 내 컴퓨터를 기준으로 설명해보자면,
파일을 저장할 때 Little Endian으로 저장된다.
그래서 우리가 보는 방식으로 출력하려면
쉬프트 연산자를 사용해서 거꾸로 print를 해야한다.
#비트맵 파일(24비트)의 파일 형식
그렇다면 비트논리 연산자와 시프트 연산자를 활용해서
비트맵(24) 이미지의 File Size와 Image Width(w), Image Height(h)를 구해
파일 형태로 출력해보자.
먼저 간단하게 비트맵 파일의 형식을 간단하게 설명하자면
Signature(확장자를 표시함)에 2바이트
File Size부터 나머지에 4바이트씩을 할당한 형식이다.
(한 줄당 4바이트인데 Signature만 반이라서 2바이트)
#비트맵 파일의 크기 출력 연습문제
그래서 파일 크기를 출력하려면
먼저 맨 위에 확장자를 표시하는 2바이트의 값을 날려버리고
그 다음에 순차적으로 오는 4바이트 값을 읽은 뒤에 출력하면 된다.
package com.newlecture.app.ex5.operator.bit;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;
public class Program {
public static void main(String[] args) throws IOException {
// FileInputStream 객체 fis를 생성해주시고요. 파일명은 res/pic1.bmp입니다.
FileInputStream fis = new FileInputStream("res/pic1.bmp"); // 바이트 단위로 읽어옴
/*Scanner sc = new Scanner(fis); // 통째로 읽어오기 때문에 여기서는 사용 안함*/
// 2바이트를 읽어서 버리세요. (확장자 정보 버리기)
fis.read();
fis.read();
// 추가로 4바이트는 정수형 변수 n1, n2, n3, n4에 저장해주세요. (파일 크기 읽기)
int n1 = fis.read();
int n2 = fis.read();
int n3 = fis.read();
int n4 = fis.read();
// 각 변수들을 "10, 20, 30, 40"과 같은 포맷으로 콘솔에 출력해주세요. (콘솔창에 출력)
System.out.printf("%d, %d, %d, %d", n1, n2, n3, n4);
/*sc.close(); //필요 없음, 스캐너 사용 안함*/
fis.close();
}
}
#(번외) .read()는 바이트를 읽는데 왜 int형일까?
Stream의 .read()메소드는 1바이트씩만 읽는다.
타입반환은 int라고 할지라도 읽는 것은 byte이다.
바이트를 읽어오는 .read()의 자료형은 int이다. 왜일까?
데이터가 없을 경우에 -1을 반환하기 때문에
바이트와 -1을 모두 포괄할 수 있는 자료형으로 int를 사용하게 되었다고 한다!
#비트맵 파일의 Width(w)xHeight(h) 구하기 연습문제
총 18번 바이트를 읽어서 버리는 것을 통해 Width와 Height를 읽을 수 있다.
바이트를 읽은 후에는 위의 Little Endian 개념을 적용하여,
시프트 연산자와 비트 연산자를 이용하여 거꾸로 값을 출력해주어야지만
우리가 보는 것과 동일한 형태로 값이 출력된다.
public class homework1_230612 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("res/pic1.bmp");
// Signature 날리기
{
fis.read();
fis.read();
}
// File Size 날리기
{
fis.read();
fis.read();
fis.read();
fis.read();
}
// Reserved 2개 날리기
{
fis.read();
fis.read();
fis.read();
fis.read();
}
// File Offset to PixelArray 날리기
{
fis.read();
fis.read();
fis.read();
fis.read();
}
// DIB Header Size 날리기
{
fis.read();
fis.read();
fis.read();
fis.read();
}
//Image Width(w), height(h) 읽기
{
int w1 = fis.read();
int w2 = fis.read();
int w3 = fis.read();
int w4 = fis.read();
int h1 = fis.read();
int h2 = fis.read();
int h3 = fis.read();
int h4 = fis.read();
// Little Endian이기 때문에 시프트 연산자&비트 연산자로 거꾸로 값을 출력해줘야 한다.
// 바이트이기 때문에 8비트 단위로 시프트 연산자를 사용했다.
int width = (w1<<0) | (w2<<8) | (w3<<16) | (w4<<24);
int height = (h1<<0) | (h2<<8) | (h3<<16) | (h4<<24);
System.out.printf("%d X %d\n", width, height);
}
fis.close();
}
}
#변수의 지역화, 좋은 코드란?
한 줄씩 코드를 치는 경험이 늘어갈 때마다
선생님께서 해주시는 좋은 코드란 무엇일지에 대한 조언을 좀 더 생각해보게 되는 것 같다.
오늘 선생님께서 말씀해주신 조언은
코드를 한 줄 적게 쓴다거나 메모리를 적게 쓰나거나 하는 것이 우선순위가 되는 것은 아니라는 것이다.
(문제 상황이 여러건이 있을 경우에는 적은 코드와 메모리 관리가 우선순위에 해당될 수 있겠다.)
좋은 코드란 유지관리하기 편한, 수정하기 편한 코드이다.
line = fscan.nextLine();
name = line.substring(1,3);
fout.println(name);
/* 변수를 하나 선언해서 공유할 수 있도록 한다.
String line = null;
String name = null;
*/
/* 중괄호를 해주면 지역변수가 된다. 같은 이름의 변수를 여러번 쓸 수 있게 된다.
{
String line = fscan.nextLine();
String name = line.substring(1,line.length());
fout.println(name);
}
이 지역 밖을 벗어나면 변수를 사용할 수가 없다.*/
오늘 유독 여러 변수를 선언할 일이 많았는데,
좋은 코드를 위해서 지역화와 변수 공유를 해보는 등의 방식도 도입해봐야겠다.
💙 마치며
오늘 배운 연산자들을 통해서 앞으로 하게 될 무궁무진한 언어의 능력을
경험해볼 것을 (무섭...)기대해야겠다.
오늘 수업 첫 시간에 연습문제를 내주신 선생님..
문제 앞에서 정지상태가 된 나를 보면서
공부를 잘 하고 있는 것이 맞나..하고 살짝 좌절했었는데
다시 정리하면서 한 줄씩 직접 써보는 것에 뿌듯함을 느꼈다.
내일은 더 뿌듯하게 해주세요!
'JAVA' 카테고리의 다른 글
[뉴렉처 6기] JAVA│반복문(while, for) 연습 문제│for를 쓰는 이유│인덱스와 넘버 (230614) (0) | 2023.06.15 |
---|---|
[뉴렉처 6기] JAVA│제어 구조에 대해 배워보자1 (while, if, for) (3) | 2023.06.14 |
[뉴렉처 6기] JAVA|자바 프로그래밍과 번역기(컴파일러)|자바 설치(JDK/JRE/JVM)|환경 변수 세팅하기 (0) | 2023.06.12 |
[뉴렉처 6기] JAVA|코드를 짜는 것과 정리하는 것|언어의 힘: CPU(ALU, CU, Register) 메모리(ROM, RAM) (0) | 2023.06.12 |
[뉴렉처 6기] JAVA|프로그램과 어플리케이션|프로그래머와 개발자 (0) | 2023.06.12 |