[자바의 정석 스터디] Ch3. 연산자/ Ch4. 조건문과 반복문
연산자와 피연산자
- 연산자: 연산을 수행하는 기호 (+, -, *, /)
- 피연산자: 연산자의 연산수행 대상
모든 연산자는 연산결과를 반환한다.
- 즉, 연산결과를 반환하지 않으면 연산자가 아니다.
- 예를 들어, () 괄호는 연산자가 아니다.
연산자의 종류
연산자는 총 5가지 종류가 있다.
- 자바에서 not(!) 사용법: 느낌표로 묶어주면 된다.
- ex) !(x > 3)
instanceof는 객체지향에서 쓰는 연산자이다.
연산자의 우선순위, 결합법칙
두괄식 정리하면, 연산자의 우선순위와 결합법칙은 3가지만 기억하면 된다.
- 산술(+,-) > 비교(>,<) > 논리(&&,||) > 대입(=)
- 대입 연산자는 제일 마지막에 수행된다
- 단항 (1) > 이항 (2) > 삼항 (3)
- 단항 연산자의 우선순위가 이항 연산자보다 높다
- (단항: 피연산자가 1개/ 이항: 피연산자가 2개 …)
- 단항 연산자, 대입 연산자를 제외한 모든 연산의 진행방향은 (→) 이다.
연산자의 우선순위
하나의 식에 연산자가 둘 이상 있을 때, 어떤 연산을 먼저 수행할지에 대한 규칙이다.
- 괄호를 이용해서 우선순위를 바꿀 수 있다. 수학을 배웠다면 상식적인 개념
- 거기서 Java에 맞게 조금만 업데이트하면 된다.
(연산자의 우선순위와 결합규칙)
연산자의 결합규칙
결합규칙: 우선순위가 같은 연산자가 있을 때, 어떤 것을 먼저 할건지에 대한 얘기이다.
3+4-5 라는 식이 있을 때, 덧셈 연산자(+)와 뺄셈 연산자(-)는 우선순위가 같다.
그럼 어떻게 하지? 마찬가지로 우린 자연스럽게 알고있다.
- 왼쪽에서 오른쪽 (→) 으로 나아간다
- 혹은 연산자는 자신의 오른쪽에 있는 피연산자에 붙는다 (결합된다)
위의 정리표를 보면 대입 연산자, 단항 연산자를 제외하면, 기본적으로 모두 왼쪽에서 오른쪽 (→) 방향이다.
형변환 연산자
형변환: 변수 또는 상수의 타입을 다른 타입으로 변환하는 것
바꾸는 방법은 쉽다: (타입) 피연산자
double d = 85.4;
int score = (int)d;
System.out.println(score); // 85
- 실수 → 정수일 때는 소숫점 아래를 자동으로 버린다.
자동 형변환
변수와 리터럴의 타입은 일치해야 하지만, 앞에서 배웠듯 Java는 유도리 있게 자동 형변환을 해준다.
float f = 1234; // 리터럴은 int이지만 float에 저장됨
- float f = (float)1234; 으로 (float)이 자동으로 적용된 것이다.
- 이는 컴파일러가 알아서 수행해준다.
그러나 더 큰 표현범위의 리터럴을 낮은 표현범위의 변수에 넣으려고 하면 에러가 발생한다.
int i = 3.14f; // 에러
int i = (int)3.14f; // ok
- 실수를 정수에 넣으면 소수점 아래를 버리는 값 손실이 발생하는데, 컴파일러가 이를 그냥 수행할 수는 없도록 설계된 것이다.
- 그래서 사용자가 직접 ‘형변환 해!' 하고 형변환 연산자를 넣어준 경우에만 가능하다.
마찬가지로, byte → int 는 형변환 연산자를 생략 가능하다 (자동 형변환)
- int → byte는 직접 형변환 연산자’(byte)’를 넣어줘야 한다.
자동 형변환 정리
형변환을 생략하면 컴파일러가 자동으로 형변환을 한다.
어떤 타입으로? → 기존의 값을 최대한 보존할 수 있는 타입으로 (큰 쪽으로)
왼쪽 타입의 리터럴을 오른쪽 타입의 변수에 담을 때는 모두 자동 형변환.
반대 방향은 값손실이 있을 수 있으므로 자동 형변환 되지 않고, 유저가 수동 형변환 해야한다.
사칙 연산자
뭔지 알겠지?: +, -, *, /
유의할 점:
(int) 10/ (int) 4 = 2.5가 아니라 (int) 2 가 나온다. 이러한 경우, 2.5를 얻으려면 피연산자 중 하나를 float로 바꿔줘야 한다.
그럼 나머지 하나도 float으로 취급되면서 (float) 10/ (float) 4= float 값인 2.5가 나온다.
왜지? → 산술변환 규칙 때문에!
나머지 연산자 (%)
피연산자를 나누고 남은 나머지를 반환한다.
int a = 11;
int b = 3;
System.out.println(a%b); // 2
% 연산자는 몇가지 특징을 지닌다.
- %의 양쪽 피연산자는 정수가 아니어도 된다. (수업에서는 정수만 허용된다고 하는데, 해보니까 되는데..?)
- 나누는 피연산자(오른쪽) 부호는 무시된다.
// %의 양쪽 피연산자는 정수가 아니어도 된다.
double a = 11.0;
double b = 3.1;
System.out.println(a%b); // 1.6999999999999997
// 나누는 피연산자(오른쪽) 부호는 무시된다.
int a = 11;
int b = -3;
System.out.println(a%b); // 2
int c = -11;
int d = 3;
System.out.println(c%d); // -2
산술 변환⭐️
산술 변환: 연산 전에 피연산자들의 타입을 일치시키는 Java의 기능
- 타입 다른 애들끼리 연산할 때, 사용자가 일일이 변수 타입 바꿔주긴 귀찮으니깐..
산순 변환에는 두 가지 중요한 규칙이 있다. (암기)
- 두 피연산자의 타입을 같게 일치시킨다. (둘 중에 더 큰 타입으로)
- 피연산자의 타입이 int보다 작은 타입이면 int로 변환된다.
하나씩 살펴보자.
1. 두 피연산자의 타입을 같게 일치시킨다. (값손실을 최소화하기 위해, 둘 중에 큰 타입으로 일치)
2. 피연산자의 타입이 int보다 작은 타입이면 int로 변환된다. (byte, char, short)
잘 이해해보자.
이를 이해하지 않으면 초보자때 실수를 종종 하게 된다.
int a = 1_000_000;
int b = 2_000_000;
long c = a * b;
System.out.println(c); // -1454759936 (오버플로우)
‘결과값이 int 범위를 넘어가지만 long 타입 변수에 담았으니 됐겠지?’ 라고 생각했겠지만 오버플로우 된 다음에 long c 에 담기는 거라 상관없다.
이럴 때 a나 b 중에 하나를 (long)으로 바꿔주었다면, 자동으로 나머지도 산술 변환 처리되면서 long 타입의 결과값이 나왔을 것이다.
int a = 1_000_000;
int b = 2_000_000;
long c = (long)a * b;
System.out.println(c); // 2,000,000,000,000
- a가 (long)이니까, b도 자동으로 (int → long)이 된다.
- long 간의 연산이 되어 정상적으로 연산이 처리된다.
Q. ‘2’ - ‘0’ 는 무엇이 될까?
A. (int) 2
둘 다 char 타입으로, 유니코드로 변환하면 50, 48이다.
char은 int보다 작은 타입이다. 따라서 자동으로 int로 바뀐다.
때문에 (int) 50 - (int) 48 = (int) 2가 된다.
반올림 - Math.round()
반올림은 round()라는 메소드를 통해 가능하다.
이 메소드는 Math라는 클래스에 소속되어 있다.
Math.round() 를 그냥 쓰면 소수점을 반올림한 정수를 반환한다.
long a = Math.round(4.5); // a에 5가 저장된다.
원하는 자리에서 반올림하기
살짝 구식으로 해야한다.
만약 소수점 셋째자리까지를 원한다면,
- 1000을 곱해주고 → round() 먹인 뒤에 → 다시 1000을 나눠준다
double a = 4.52152;
double result = Math.round(a*1000); // 4521.52 기준으로 반올림
System.out.println(result/1000); // 4.522
프린트할 때 int인 1000으로 나눠주었으나 result가 double임. 따라서 산술변환에 의해 double 값이 나온다.
double a = 4.52152;
System.out.println(Math.round(a*1000)/1000);
// 4 -> (int)4522/(int)1000 이기 때문
System.out.println((double)Math.round(a*1000)/1000);
// 4.522 -> (double)4522.0/(int)1000 이기 때문
따로 변수에 넣어줄 게 아니고 바로 print해야 하는 상황이라면, 이처럼 형변환 연산자를 활용해줘도 된다.
임의의 정수(난수) 만들기
랜덤한 수를 생성한다. 게임, 섞기 등을 구현할 때 자주 쓴다.
이 때 쓰이는 메서드는 Math 클래스의 random() 메서드이다.
Math.random()
- 0.0과 1.0 사이의 임의의 double값을 반환한다
- 0.0 ≤ Math.random() < 1.0
- 0.0 ~ 0.9999 ..
원하는 정수를 얻는 법:
예를 들어 1~3 중에 random한 정수를 얻고싶다면?
- 각 변에 3을 곱한다.
0.0 * 3 <= Math.random() * 3 < 1.0 * 3
= 0.0 <= Math.random() * 3 < 3.0
- 지금 얻을 수 있는 값의 범위는 0.0 ~ 2.999.. 이다
- 우리는 1부터 얻고 싶다.
- 각 변을 int형으로 변환한다.
(int) 0.0 <= (int) Math.random() * 3 < (int) 1.0 * 3
= 0 <= (int) Math.random() * 3 < 3
- 지금 얻을 수 있는 값의 범위는 0, 1, 2 이다.
- 각 변에 1을 더한다.
0 + 1 <= (int) Math.random() * 3 + 1 < 3 + 1
= 1 <= (int) Math.random() * 3 + 1 < 4
- 지금 얻을 수 있는 값의 범위는 1, 2, 3 이다.
a ~ b 사이 정수를 얻는 방법을 일반화하면 다음과 같다.
int num = 0;
num = (int)Math.random()*(b-a+1) + a;
- 원하는 범위 내 값의 개수만큼 곱하기 (b-a+1)
- int로 형변환
- 최솟값만큼 더하기 (a)
예시1) 1~100 사이 정수 출력하기
Scanner scanner = new Scanner(System.in);
System.out.println("난수 생성 횟수를 입력하세요.");
int times = scanner.nextInt();
int num = 0;
// 난수 범위: 1~100, 정수
for (int i=0; i < times ; i++) {
num = (int)(Math.random()*100) + 1;
System.out.println(num);
}
이름붙은 반복문
break는 하나의 반복문만 벗어날 수 있다.
그러나 중첩 반복문을 벗어나야 하는 상황도 있다.
- 그런 경우 반복문에 이름을 붙여서 하나 이상의 반복문을 벗어날 수 있다
최상단 for문 앞에 Loop1 이라는 이름을 줬다.
- continue Loop1 또는 break Loop1 와 같이 활용 가능하다.
- break와 함께 쓰는 경우가 절대다수이다. (남궁성 강사님도 continue는 안써보심)
예제) 4반의 4번을 불러선 안 돼..
Loop: for (int i=1; i<=10; i++) {
System.out.printf("%d반 - ",i);
for (int j=1; j<=10; j++) {
if (i == 4 && j ==4) {
System.out.printf(" ");
break Loop;
}
System.out.printf("%d ",j);
}
System.out.println();
}