들어가기 앞서

단항, 이항, 삼항 연산자

단항 연산자(unary operator)란 하나의 피연산자를 갖는 연산자들을 말합니다. 단항 연산자에는 부호 연산자, 증감 연산자 등이 있습니다. 그럼 이항 연산자(binary operator)는 무엇일까요? 두 개의 피연산자를 갖는 연산자들을 말합니다. 이와 마찬가지로 삼항 연산자(ternary operator)는 세 개의 피연산자를 갖는 연산자들을 말합니다.

부호 연산자

단항 연산자로 쓰이기도 하는 + 연산자와 - 연산자는 피연산자의 부호를 유지하거나 변경하기도 합니다.

다음 예를 보시면 손쉽게 이해하실 수 있습니다.

public class OperatorExamples {
	public static void main(String[] args) {
		int num1 = -20;
		int num2 = 10;
		
		System.out.println(+num1); // -20
		System.out.println(-num2); // -10
	}
}

코드를 보시면, 3행과 6~7행에 부호 연산자가 쓰였습니다. 6행의 출력 결과에서 피연산자 num1의 부호가 유지되고, 7행의 출력 결과에서 피연산자 num2의 부호가 바뀌었음을 확인할 수 있습니다.

증감 연산자

증감 연산자는 변수에 저장된 값을 증가시키거나 감소 시킬 때 사용되는 연산자입니다. 증감 연산자는 연산자의 위치에 따라서 전위(prefix), 후위(postfix)로 나뉘는데 아래의 표에 이를 정리해 두었습니다.

빠른 이해를 위해서 한번 다음의 코드를 컴파일 후 결과를 확인해보도록 하겠습니다.

public class JavaTutorial5 {
	public static void main(String[] args) {
		int num = 20;

		System.out.println(num++); // 20 출력 후 num의 값은 21
		System.out.println(num--); // 21 출력 후 num의 값은 20
		System.out.println(--num); // 19 출력 후 num의 값은 19
		System.out.println(++num); // 20 출력 후 num의 값은 20
	}
}

코드를 보시면, 5행~6행에선 후위 연산이 사용되었고, 7~8행에선 전위 연산이 사용되었습니다. 5행에서 후위 증가 연산을 통해 num의 원래 값인 20이 먼저 반환되고 1만큼 증가했습니다. 6행도 이와 마찬가지로 num의 값이 후위 감소 연산을 통해 num의 원래 값인 21이 먼저 반환되고 1만큼 감소했습니다.

7~8행에서 쓰인 증감 연산자는 전위의 형태로 후위 연산과는 반대로 원래 값이 먼저 반환되고 증감이 이루어지는 게 아니라, 증감이 먼저 이루어진 뒤에 바로 그 값을 반환합니다. 그러므로 현재 num의 값은 20인데 여기에서 7행의 전위 감소 연산으로 인해 1만큼 감소하고 감소된 num 값인 19가 출력됩니다. 8행도 마찬가지로 전위 증가 연산으로 인해 1만큼 증가된 뒤 증가된 num 값인 20이 출력되는 것입니다. 이번에는 식을 점점 변형 시켜 보겠습니다.

심화 문제

아래 연산식의 결과는 어떻게 될까? 이런 식을 마주칠 일은 없겠지만 완전한 이해를 돕기 위해서 파헤쳐 보도록 하자.

public class OperatorExamples {
	public static void main(String[] args) {
		int num = 11;

		num = num++ + ++num;
		System.out.println(num);
	}
}

위의 식을 분해해보면 아래와 같습니다. 최종적으로 답은 24가 됩니다.

그러면, 아래 연산식의 결과는 어떻게 될까요?

public class OperatorExamples {
	public static void main(String[] args) {
		int i = 0;

		i = i++ - --i + ++i - i--;
		System.out.println(i);
	}
}

위의 식을 분해해보면 아래와 같습니다. 최종적으로 답은 0이 됩니다.

비트 연산자

비트 연산자(bitwise operator)란 비트를 연산의 대상으로 하는 연산자를 말하며, 피연산자에는 반드시 정수가 와야 합니다. 비트 연산자의 종류와 기능, 사용 예를 간략하게 아래의 표에 정리해두었습니다.

더 확실히 알아보도록 하기 위해 비트 AND, 비트 OR, 비트 XOR, 비트 NOT 연산에 대한 진리표를 아래에 정리해두었습니다.

빠른 이해를 위해서 한번 다음의 코드를 컴파일 후 결과를 확인해보도록 하겠습니다.

public class OperatorExamples {
	public static void main(String[] args) {
		int num1 = 105; // 00000000 00000000 00000000 01101001
		int num2 = 22;  // 00000000 00000000 00000000 00010110

		System.out.println(num1 + " & " + num2 + " = " + (num1 & num2)); // 0
		System.out.println(num1 + " | " + num2 + " = " + (num1 | num2)); // 127
		System.out.println(num1 + " ^ " + num2 + " = " + (num1 ^ num2)); // 127
		System.out.println("~" + num1 + " = " + ~num1); // -106
	}
}

코드로 돌아가 왜 이런 결과가 나왔는지 생각해봅시다. 우선 4편에서 int형의 크기는 4바이트라고 했었습니다. 1바이트는 8개의 비트로 구성되는데, 이는 즉 int형은 32개의 비트로 구성된다는 말과 같습니다. 참고로, 비트(bit)는 컴퓨터의 최소 단위이며 2진수의 0과 1로만 구성되는 한자리를 말합니다. 105와 22를 2진수로 변환하면 아래와 같습니다. (여기서 10진수를 2진수로 변환하는 방법을 모르신다면 이곳을 참고해주세요.)

6행에서 비트 AND 연산이 수행됩니다. 비트 AND 연산(&)은 위에서 두 개의 피연산자의 대응되는 비트가 모두 1이어야 1을 반환한다고 했습니다. 여기서 상위 3바이트는 연산 결과가 모두 0이기 때문에 생략하면 아래와 같습니다.

00000000 즉, 10진수로 0이 출력됩니다. 이어서 7행의 비트 OR 연산(|)을 살펴보도록 하겠습니다. 비트 OR 연산은 두 개의 피연산자의 대응되는 비트 중에서 하나라도 1이면 1을 반환한다고 했습니다. 여기서도 마찬가지로 상위 3바이트는 연산 결과가 모두 0이기 때문에 생략하면 아래와 같습니다.

8행에서는 비트 OR 연산으로 두개의 피연산자의 대응되는 비트를 비교해봤을때 하나라도 1일 경우엔 1을 반환한다고 했습니다. 이어서 8행의 비트 XOR 연산(^)을 살펴보도록 하겠습니다. 비트 XOR 연산은 두 개의 피연산자의 대응되는 비트가 서로 다르면 1을 반환한다고 했습니다. 여기서도 마찬가지로 상위 3바이트는 연산 결과가 모두 0이기 때문에 생략하면 아래와 같습니다.

01111111, 10진수로 127을 출력하게 됩니다. 9행에서는 비트 NOT 연산으로 피연산자의 모든 비트를 반전시킨다고 했습니다. 그러면 아래와 같은 결과가 나올 것입니다.

2진수 11111111 11111111 11111111 10010110이 왜 -106이 되는지는 컴퓨터가 음수를 표현하는 방법을 확인하시기 바랍니다. 내용이 길어지므로 여기서는 설명하지 않습니다.

비트 시프트 연산자

비트 시프트 연산자에는 >>, <<, >>>가 있습니다. 이 비트 시프트 연산자는 피연산자의 비트를 이동시키는 연산자를 말합니다.

다음은 비트 시프트 연산자를 사용하여 그 결과를 출력한 예제입니다.

public class OperatorExamples {
	public static void main(String[] args) {
		System.out.println(2 << 1); // 4
		System.out.println(2 << 2); // 8
		System.out.println(16 >> 1); // 8
		System.out.println(16 >> 2); // 4
		System.out.println(-32  >>> 1); // 2147483632
	}
}

코드를 같이 살펴보도록 하겠습니다. 3행에서 << 연산이 진행되며 피연산자의 비트열이 왼쪽으로 1만큼 이동됩니다. 아래와 같이 00000010이 한 칸 왼쪽으로 이동했으므로 00000100이 되어 10진수로 4가 됩니다. 왼쪽으로 비트열이 이동할 때는 뒤에 생겨나는 빈자리에는 0이 옵니다.

4행도 마찬가지로 00000010이 두 칸 왼쪽으로 이동했으므로, 00001000이 되어 10진수로 8이 됩니다. 이해가 되시나요?

5행은 >> 연산이 진행되고 피연산자의 비트열이 오른쪽으로 1만큼 이동됩니다. 00010000이 한칸 오른쪽으로 이동했으므로 00001000이 되어 10진수로 8이 됩니다.

6행도 마찬가지로 00010000이 두 칸 오른쪽으로 이동했으므로, 00000100이 되어 10진수로 4가 됩니다. 그리고 오른쪽으로 비트열이 이동할 때는 왼쪽에서 생겨난 비트가 부호 비트인 최상위 비트(MSB)와 같은 수로 채워집니다.

마지막으로 7행은 >>> 연산이 진행되고 피연산자의 비트열이 오른쪽으로 1만큼 이동합니다. -32를 2진수로 표현하면 11111111 11111111 11111111 11100000이 됩니다. 이것을 오른쪽으로 1만큼 이동시킨다고 생각합시다. 그러면 10진수로 2147483632이 될 것입니다.

여기서 주의하실 점은 오른쪽 시프트 연산자인 >>와는 다르게 왼쪽에서 생겨난 빈자리는 부호 비트를 따라가는 게 아닌 0이 온다는 사실을 기억해주세요.

삼항 연산자

마지막으로 삼항 연산자 ?:입니다. 말 그대로 항이 세 개가 있는데, 다음 편에서 볼 if~else문의 축약형이라고 생각해도 무방합니다. 조건식에는 참(true) 혹은 거짓(false)이 올 수 있습니다. 만약에 조건식의 결과가 참인 경우에는 식1의 결과를 돌려주고, 거짓인 경우에는 식2의 결과를 돌려줍니다.

아래의 예를 살펴봅시다. 5행에서 a > b는 참이므로 max에는 10이 들어갑니다. 9행의 c > 0은 c가 음수이므로 -c가 되어 d에 30이 들어가게 됩니다.

public class OperatorExamples {
    public static void main(String[] args) {
        int a = 10;
        int b = 3;
        int max = a > b ? a : b; // 참고로 int max = Math.max(a, b)로도 쓸 수 있음
        System.out.println("최댓값: " + max);

        int c = -30;
        int d = c > 0 ? c : -c;
        System.out.println("절댓값: " + d);
    }
}

아래와 같이 삼항 연산자를 중첩시킬 수도 있습니다. 하지만 조건식이 복잡해지고 삼항 연산자가 중첩될수록 가독성이 떨어지므로 주의해야 합니다.

int a = 10;
int b = 5;
int c = 31;
// 소괄호를 제외하면 int max = a > b ? a > c ? a : c : b > c ? b : c;
int max = a > b ? (a > c ? a : c) : (b > c ? b : c);

'프로그래밍 관련 > 자바' 카테고리의 다른 글

9편. 제어문 (2)  (20) 2012.07.30
8편. 제어문 (1)  (16) 2012.07.30
6편. 연산자 (1)  (19) 2012.07.25
5편. 주석  (14) 2012.07.22
4편. 변수와 타입  (24) 2012.07.22