와드의 블로그

Chapter 3. 연산자 본문

프로그래밍 언어/Java

Chapter 3. 연산자

Ward 2022. 11. 24. 14:42

1. 연산자

1.1 연산자와 피연산자

연산자는 연산을 수행하는 기호를 말한다. 연산자가 연산을 수행하려면 반드시 연산의 대상이 있어야 하는데, 이것을 피연산자라고 한다. 피연산자로 상수, 변수 또는 식 등을 사용할 수 있다. 연산자는 피연산자로 연산을 수행하고 나면 항상 결괏값을 반환한다.

1.2 식과 대입 연산자

연산자와 피연산자를 조합하여 계산하고자하는 바를 표현한 것을 식이라고 한다. 그리고 식을 계산하여 결과를 얻는 것을 식을 평가한다고 한다.

public class Operator {
	public static void main(String[] args) {
		int x = 5;
		int y = 4 * x + 3;
		System.out.println(y); // 23
	}
}

4 * x + 3이라는 식이 평가되어 23이라는 결과를 얻었지만, 이 값을 대입 연산자를 사용해서 변수와 같이 값을 저장할 수 있는 공간에 결과를 저장해야 여러 번 사용이 가능하다.

1.3 연산자의 종류

연산자는 위 표에서 알 수 있는 것처럼, 크게 산술, 비교, 논리, 대입 4가지로 나눌 수 있다.

 

※ 피연산자의 개수에 의한 분류

피연산자의 개수로 연산자를 분류하기도 하는데, 피연산자의 개수가 하나면 단항 연산자, 두 개면 이항 연산자, 세 개면 삼항 연산자라고 부른다.

1.4 연산자의 우선 순위와 결합 규칙

※ 연산자의 우선 순위

식에 사용된 연산자가 둘 이상인 경우, 연산자의 우선순위에 의해서 연산 순서가 결정된다. 연산자의 우선순위는 산술 > 비교 > 논리 > 대입 순이고, 단항 > 이항 > 삼항 순이다.  만약 우선순위가 확실하지 않다면, 먼저 계산되어야 하는 부분을 괄호로 묶어주면 된다.

 

※ 연산자의 결합 규칙

하나의 식에 같은 우선순위의 연산자들이 여러 개 있는 경우, 아무거나 먼저 처리하는 것이 아니고 나름대로의 규칙을 가지고 있는데, 그 규칙을 연산자의 결합 규칙이라고 한다.

연산자의 결합 규칙은 연산자마다 다르지만, 대부분 왼쪽에서 오른쪽 순서로 연산을 수행하고, 단항 연산자와 대입 연산자만 그 반대로 수행한다.

1.5 산술 변환

이항 연산자는 두 피연산자의 타입이 일치해야 연산이 가능하므로, 피연산자의 타입이 서로 다르다면 연산 전에 형 변환 연산자로 타입을 일치시켜야 한다. 앞서 배운 것과 같이 작은 타입에서 큰 타입으로 형 변환하는 경우, 자동적으로 형 변환되므로 형 변환 연산자를 생략할 수 있다. 이처럼 연산 전에 피연산자의 타입의 일치를 위해 자동 형 변환되는 것을 산술 변환이라고 한다. 산술 연산의 규칙은 다음과 같다.

  1. 두 피연산자의 타입을 같게 일치시키되 보다 큰 타입으로 일치시킨다.
  2. 피연산자 타입이 int보다 작은 타입이면 int로 변환된다.

2. 단항 연산자

2.1 증감 연산자

증감 연산자(++, --)는 피연산자에 저장된 값을 1 증가 또는 감소시킨다. 증감 연산자의 피연산자로 정수와 실수가 모두 가능하지만, 상수는 값을 변경할 수 없으므로 가능하지 않다.

대부분의 연산자는 피연산자 값을 읽어서 연산에 사용할 뿐, 피연산자의 타입이나 값을 변경시키지 않는다. 오직 대입 연산자와 증감 연산자만 피연산자의 값을 변경한다.

일반적으로 단항 연산자는 피연산자의 왼쪽에 위치하지만, 증감 연산자는 양쪽 모두 가능하다. 피연산자 왼쪽에 위치하면 전위형, 오른쪽에 위치하면 후위형이라고 한다. 전위형과 후위형 모두 피연산자의 값을 1 증가 또는 감소시키지만, 증감 연산자가 수식이나 메서드 호출에 포함된 경우 전위형일 때와 후위형일 때의 결과가 다르다.

public class Operator {
	public static void main(String[] args) {
		int i = 5;
		int j = i++;
		System.out.println("i=" + i + ", j=" + j); // i=6, j=5
		i = 5;
		j = ++i;
		System.out.println("i=" + i + ", j=" + j); // i=6, j=6		
	}
}

전위형 증감 연산자는 값이 참조되기 전에 증감시킨다. 하지만 후위형 증감 연산자는 값이 참조된 후에 증감시킨다.

2.2 부호 연산자

부호 연산자 -는 피연산자의 부호를 반대로 변경한 결과를 반환한다. 부호 연산자 +는 하는 일이 없으며, 쓰이는 경우도 거의 없다. 부호 연산자는 boolean형과 char형을 제외한 기본형에만 사용할 수 있다.

3. 산술 연산자

3.1 사칙 연산자

사칙 연산자에는 덧셈, 뺄셈, 곱셈, 나눗셈 연산자가 있다.

 

※ 나눗셈 연산자 관련 주의사항

 

나눗셈 연산자의 두 피연산자가 모두 정수형인 경우, 연산 결과 역시 정수형이다. 따라서 만약 나눈 결과가 실수라 하더라도 소수점 이하는 버려지고 정수형으로 결괏값을 반환한다. 그래서 올바른 연산 결과를 얻기 위해서는 두 피연산자 중 어느 한쪽을 실수형으로 형 변환해야 한다.

피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없다. 0으로 나누면 에러가 발생한다.

public class Operator {
	public static void main(String[] args) {
		int a = 10;
		int b = 4;
		int c = 0;
		
		System.out.println(a / b); // 2
		System.out.println(a / (float)b); // 2.5
		System.out.println(a / c); // ArithmeticException 발생
	}
}

 

※ 형 변환 관련 주의사항

연산자 +는 피연산자가 byte이면 int로 변환한 다음 연산을 수행한다. 따라서 이 결과를 byte형 변수에 저장하면 에러가 발생한다. 따라서 연산 결과를 byte형으로 명시적으로 형 변환해주어야 에러가 발생하지 않는다.

public class Operator {
	public static void main(String[] args) {
		byte a = 10;
		byte b = 20;
		byte c = a + b; // 컴파일 에러 발생
		System.out.println(c);
	}
}
public class Operator {
	public static void main(String[] args) {
		byte a = 10;
		byte b = 20;
		byte c = (byte)(a + b); 
		System.out.println(c); // 30
	}
}

 

※ 오버플로우 관련 주의사항

int 타입과 int 타입을 연산하면 연산 결과도 int 타입이다. 따라서 long 타입의 변수에 이 값을 저장하더라도 이미 오버플로우가 일어나서 전혀 다른 결과가 저장될 수 있다. 따라서 연산식에서 형 변환을 해주어야 한다.

public class Operator {
	public static void main(String[] args) {
		int a = 1000000;
		int b = 2000000;
		long c = a * b;
		System.out.println(c); // -1454759936
		c = (long)a * b;
		System.out.println(c); // 2000000000000
	}
}

또한 같은 의미의 식이라도 연산의 순서에 따라서 오버플로우가 일어나 다른 결과를 얻을 수  있다.

public class Operator {
	public static void main(String[] args) {
		int a = 1000000;
		int res1 = a * a / a;
		int res2 = a / a * a;
		System.out.println(res1); // -727
		System.out.println(res2); // 1000000
	}
}

3.2 나머지 연산자

나머지 연산자는 왼쪽 피연산자를 오른쪽 피연산자로 나누고 난 나머지 값을 결과로 반환하는 연산자이다. 나머지 연산자는 나누는 수로 음수도 허용하지만 부호는 무시되므로 결과는 음수의 절댓값으로 나눈 나머지와 같다. 나머지 연산자도 오른쪽 피연산자로 0을 사용할 수 없다.

4. 비교 연산자

4.1 비교 연산자

비교 연산자는 두 연산자를 비교하는 데 사용되는 연산자이다. 주로 조건문과 반복문의 조건식에 사용되며, 연산 결과는 오직 true와 false 둘 중 하나이다. 비교 연산자 역시 이항 연산자 이므로 비교하는 피연산자의 타입이 서로 다를 경우에는 자료형의 범위가 큰 쪽으로 자동 형 변환하여 피연산자의 타입을 일치시킨 후에 비교한다.

4.2 대소 비교 연산자

두 피연산자의 값의 크기를 비교하는 연산자이다. 참이면 true, 거짓이면 false를 결과로 반환한다. 기본형 중에서는 boolean형을 제외한 나머지 자료형에 다 사용할 수 있지만 참조형에는 사용할 수 없다.

  • >: 좌변의 값이 크면, true 아니면 false
  • <: 좌변의 값이 작으면, true 아니면 false
  • >=: 좌변의 값이 크거나 같으면, true 아니면 false
  • <=: 우변의 값이 작거나 같이면, true 아니면 false

4.3 등가 비교 연산자

두 피연산자의 값이 같은지 또는 다른지를 비교하는 연산자이다. 대소 비교 연산자와 달리, 모든 자료형에 사용할 수 있다.

  • ==: 두 값이 같으면, true 아니면 false
  • !=: 두 값이 다르면, true 아니면 false

실수형 변수를 등가 비교할 때, 오차로 인해 연산에 주의해야 한다. double 타입과 float 타입을 등가 비교하려면 double 타입의 값을 float 타입으로 형 변환한 다음에 비교해야 한다.

public class Operator {
	public static void main(String[] args) {
		float f = 0.1f;
		double d = 0.1;
		float f2 = (float)d;
		double d2 = (double)f;
		System.out.println(d == f); // false
		System.out.println(d == d2); // false
		System.out.println(d2 == f); // true
		System.out.println(f2 == f); // true
	}
}

 

※ 문자열의 비교

두 문자열을 비교할 때는, 비교 연산자 == 대신 equals()라는 메서드를 사용해야 한다. 두 문자열의 내용이 같더라도 다른 객체라면 비교 연산자 ==의 결과가 false로 나온다.

public class Operator {
	public static void main(String[] args) {
		String str1 = "abc";
		String str2 = new String("abc");
		
		System.out.println(str1 == str2); // false
		System.out.println(str1.equals(str2)); // true
	}
}

5. 논리 연산자

5.1 논리 연산자

※ 논리 연산자 &&, ||

논리 연산자는 둘 이상의 조건을 연결하여 하나의 식으로 표현할 수 있게 해 준다. 논리 연산자 &&는 두 피연산자가 모두 true일 때만 true를 결과로 얻는다. 논리 연산자 ||는 두 연산자 중 어느 한쪽만 true이어도 true를 결과로 얻는다. 그리고 논리 연산자는 피연산자로 boolean형 또는 boolean형 값을 결과로 하는 조건식만을 허용한다.

 

※ 효율적인 연산

논리 연산자의 또 다른 특징은 효율적인 연산을 한다는 것이다. || 연산의 경우, 두 피연산자 중 어느 한 쪽만 참이어도 전체 연산 결과가 참이므로 좌측 피연산자가 true이면, 우측 피연산자의 값을 평가하지 않는다.

&& 연산의 경우도 마찬가지로 어느 한 쪽만 거짓이어도 전체 연산 결과가 거짓이므로 좌측 피연산자가 거짓이면, 우측 피연산자는 평가하지 않는다.

 

※ 논리 부정 연산자

논리 부정 연산자 !는 피연산자가 true이면 false, false이면 true를 결과로 반환한다.

5.2 비트 연산자

※ 비트 연산자 |, &, ^

비트 연산자는 피연산자를 비트 단위로 논리 연산한다. 피연산자를 이진수로 표현했을 때 각 자리를 아래의 규칙에 따라 연산한다. 피연산자로 실수는 허용하지 않는다.

  • |: 피연산자 중 한쪽의 값이 1이면, 1을 결과로 얻는다. 그 외에는 0을 얻는다.
  • &: 피연산자 양 쪽이 모두 1이어야만 1을 결과로 얻는다. 그 외에는 0을 얻는다.
  • ^: 피연산자의 값이 서로 다를 때만 1을 결과로 얻는다. 같을 때는 0을 얻는다.

※ 비트 전환 연산자

비트 전환 연산자 ~는 피연산자를 2진수로 표현했을 때, 0은 1로, 1은 0으로 바꾼다.

 

※ 시프트 연산자

시프트 연산자는 피연산자의 각 자리(2진수)를 오른쪽 또는 왼쪽으로 이동시키는 연산자이다. << 연산자는 피연산자의 부호에 상관없이 각 자리를 왼쪽으로 이동시키며 빈칸을 0으로 채운다. >> 연산자는 각 자리를 오른쪽으로 이동시키며 부호를 고려해서 음수는 1로 채우고, 양수는 0으로 채운다.

2진수 n자리를 왼쪽으로 이동하면 피연산자에 2^n을 곱한 결과를, 오른쪽으로 이동하면 피연산자를 2^n으로 나눈 결과를 얻는다.

6. 그 외의 연산자

6.1 조건 연산자

조건 연산자는 조건식, 식1, 식2 모두 세 개의 피연산자를 필요로 하는 삼항 연산자이며, 삼항 연산자는 조건 연산자 하나뿐이다. 조건 연산자의 기본 형태는 조건식 ? 식1 : 식2이다. 조건 연산자는 첫 번째 피연산자인 조건식의 평가 결과가 true이면 식1이, false이면 식2가 연산 결과가 된다.

6.2 대입 연산자

대입 연산자는 변수와 같은 저장 공간에 값 또는 수식의 연산 결과를 저장하는 데 사용된다. 이 연산자는 오른쪽 피연산자의 값을 왼쪽 피연산자에 저장한다. 그리고 저장된 값을 연산 결과로 반환한다. 대입 연산자의 왼쪽 피연산자를 lvalue라고 하고, 오른쪽 피연산자를 rvalue라고 한다.

 

※ 복합 대입 연산자

대입 연산자는 다른 연산자(op)와 결합하여 op=과 같은 방식으로 사용될 수 있다.

 

'프로그래밍 언어 > Java' 카테고리의 다른 글

Chapter 6. 객체지향 프로그래밍 1  (0) 2022.12.05
Chapter 5. 배열  (0) 2022.12.03
Chapter 4. 조건문과 반복문  (0) 2022.11.28
Chapter 2. 변수  (0) 2022.11.23
Chapter 1. 자바의 시작  (0) 2022.11.23