Language/Java

연산자의 종류

도시와 2023. 11. 8. 18:05

※ 만약 연산자에 대해 처음 들어본다면, 이전 포스트부터 보고 오는 것을 추천한다.

 

연산자의 기본 개념

연산자(operator)란? 연산자(operator): 연산을 수행하는 기호 (+, -, *, / 등) 피연산자(operand): 연산자의 작업 대상 (변수, 상수, 리터럴, 수식) 연산자는 '연산을 수행하는 기호'이다. 우리가 계산할 때

doshiwa-dev.tistory.com

 

증감 연산자

증감 연산자는 피연산자의 저장된 값을 1 증가 또는 1 감소시킨다.

그저 값을 1 증가시키거나 감소시킬 뿐이라면 증감 연산자가 왜 필요한 것인지 의문이 들 수도 있다.

 

증감 연산자가 일반 사칙 연산자와 다른 점은 대입 연산자를 쓰지 않고도 피연산자의 값을 변경할 수 있다는 것이다.

 

int i = 5;
System.out.println(i - 1); // 4
System.out.println(i); // 5

위의 코드처럼 일반 사칙 연산자는 일시적인 결과값을 얻을 뿐, 값을 실제로 변경하지는 않는다.

System.out.println(--i); // 4
System.out.println(i); // 4

하지만 위의 코드처럼 증감 연산자를 사용하면, 피연산자의 값을 실제로 변경시킬 수 있다.

 

증감 연산자는 '++'를 사용하면 값을 1 증가시키고, '- -'를 사용하면 값을 1 감소시키는 단순한 연산자이지만,

단항 연산자이기 때문에 사용하는 위치에 따라 연산의 결과가 달라질 수 있다.

 

만약 증감 연산자를 앞에 붙이면 '전위형'이라 부르며, 값이 참조되기 전에 증가(감소)시킨다.

반대로 증감 연산자를 뒤에 붙이면 '후위형'이라 부르며, 값이 참조된 후에 증가(감소)시킨다.

그리고 이러한 차이는 연산결과를 반환 받을 코드와 함께 사용해야 느낄 수 있고, 독립적인 문장으로 사용됐을 때는 전위형과 후위형이 별 다른 차이 없이 같은 연산을 실행한다.

 

아래의 예시를 보면 쉽게 이해할 수 있을 것이다.

j = ++i; // 전위형의 경우에는
i = i + 1; // 먼저 i를 1 증가시키는 연산을 실행한 후
j = i;  // j에 i를 대입하는 연산을 실행하는 것이고,

j = i++; // 후위형의 경우에는
j = i; // 먼저 j에 i를 대입하는 연산을 실행한 후
i = i + 1; // i를 1 증가시키는 연산을 실행하는 것이다.

// 그렇기 때문에 아래와 같은 코드가 있을 경우,
int n = 5;
System.out.println(++n); // n을 증가시킨 후 출력해서 6이 출력되고,
System.out.println(n++); // n을 출력한 후 증가시켜서 똑같이 6이 출력된다.

 

 

부호 연산자와 사칙 연산자

부호 연산자는 '+'와 '-'가 있는데, '+'는 아무 역할도 없고 사용도 안 하기 때문에 '-'가 부호 연산자라고 생각하면 된다.

부호 연산자의 역할은 간단하다. 만약 피연산자가 양수면 음수로, 음수면 양수로 변환해 준다.

우리가 상식적으로 사용하는 음수 부호가 부호 연산자의 역할이다.

 

다음으로 사칙 연산자도 기본적으로 우리가 흔히 알고 있는 사칙 연산과 동일하게 연산을 진행한다.

하지만 일반적인 사칙 연산과 다른 점이 있는데, 위의 산술 변환 파트에서도 언급했듯이 정수형 피연산자 간의 나누기 연산 '/'을 진행할 경우 실수형 결과값이 반환되는 것이 아니라 소수점이 버려진 정수형 결과값, 즉 이 반환된다.

 

추가로 '%' 연산자를 사용하면 나머지 값을 결과로 받을 수 있다.

int x = 10;
int y = 8;
System.out.println(x / y); // 1
System.out.println(x % y); // 2

 

 

비교 연산자

비교 연산자는 이름 그대로 두 피연산자의 값을 비교할 때 사용하는 연산자이다.

연산결과는 boolean 값, 즉 true 또는 false를 반환한다. 그래서 주로 조건식을 작성할 때 사용된다.

 

비교 연산자는 다시 2가지로 구분할 수 있는데, 값의 크기를 비교하는 대소비교 연산자 값이 같은지 또는 다른지를 비교하는 등가비교 연산자로 구분할 수 있다.

 

대소비교 연산자

대소비교 연산자도 마찬가지로 수학의 기호와 똑같기 때문에 어렵지 않다.

단지 '≤'와 '≥'는 키보드 자판만으로는 표현하기 힘들기 때문에 '<='와 '>='처럼 풀어서 사용할 뿐이다.

단, 주의할 점은 반드시 등호를 뒤에 작성해야 한다. '=<' 또는 '=>'와 같이 사용하면 에러가 발생한다.

 

등가비교 연산자

등가비교 연산자는 수학의 기호와 동일하지는 않지만 유사한 의미를 가지고 있다.

원래 수학에서 '같다'를 의미하는 '='는 프로그래밍에서는 대입 연산자로 사용하고 있으므로 '=='를 사용하고,

'다르다'를 의미하는 '≠'는 부정을 의미하는 논리 연산자 '!'와 결합하여 '!='를 사용한다.

참고: 논리 연산자 '!'는 바로 뒤에서 설명할 예정이다.

 

등가비교 연산자를 사용할 때는 주의할 점이 하나 있는데, 참조형 변수 간의 비교 연산을 할 때는 값이 아니라 주소를 비교한다는 사실을 기억해야 한다. 이는 특히 String 타입의 변수를 사용할 때 실수하기 쉽다.

 

String은 일반적인 참조형 타입과 달리 대입 연산자로 값을 초기화할 수도 있고, 변수를 출력할 때도 주소가 아닌 값을 출력해 주기 때문에 기본형 타입으로 착각하기 쉽다. 하지만 String도 엄연히 참조형 타입이기 때문에 '=='을 사용하여 비교 연산을 진행하면 객체의 주소를 비교하기 때문에 의도와 다른 결과가 나올 수 있다.

 

그래서 참조형 타입의 값을 서로 비교할 때는 equals()라는 메서드를 사용해야 한다. 지금은 String의 값을 비교할 때는 equals()를 사용해야 한다는 사실만 알아두면 된다.

 

 

논리 연산자

논리 연산자는 두 개 이상의 조건을 결합해야 할 때 사용하는 연산자이다.

|| (OR 결합): 피연산자 중 어느 한 쪽이라도 true이면 true, 아니면 false를 반환한다.
&& (AND 결합): 피연산자 양쪽 모두 true일 때만 true, 아니면 false를 반환한다. 

 

아래의 두 가지 예제를 통해 논리 연산자의 사용 방법과 주의할 사항을 확인할 수 있다.

 

1. "x는 10보다 크고, 20보다 작다."

x > 10 && x < 20 
// 주어진 조건을 그대로 코드로 옮기면 위와 같이 표현할 수 있다.
10 < x && x < 20 
// 이때 x의 위치를 조금 조정해주면 범위를 명확히 표현하여 가독성을 높일 수 있다.
// 그렇다고 해서 10 < x < 20과 같이 논리 연산자를 생략하는 것은 불가능하다.

 

2. "x는 2의 배수 또는 3의 배수이지만, 6의 배수는 아니다."

// 어떤 수 x를 n으로 나머지 연산했을 때 결과가 0이라면, x는 n의 배수이다.
x % 2 == 0 || x % 3 == 0 && x % 6 != 0 
// 주어진 조건을 그대로 코드로 옮기면 위와 같이 표현할 수 있다.
// 단, 주어진 조건에서는 2의 배수 또는 3의 배수인지를 먼저 확인해야 하는데, 
// &&의 우선순위가 높기 때문에 x % 3 == 0 && x % 6 != 0을 먼저 확인한다.
// 이처럼 ||와 &&를 같이 사용할 때는 괄호를 통해 우선순위를 명확히 해야 한다.
(x % 2 == 0 || x % 3 == 0) && x % 6 != 0

 

논리 부정 연산자

논리 부정 연산자 '!'는 주로 조건식에 사용되며, 조건식의 결과를 반대로 뒤집는 역할을 한다.

 

만약 조건식 x의 결과가 true라면 !x는 false가 되고, 반대로 x의 결과가 false라면 !x는 true가 된다.

 

효율적인 논리 연산

추가로 논리 연산자는 같은 조건식을 작성하더라도 작성하는 순서에 따라 연산 속도가 달라질 수 있다.

 

예를 들어 'true || false'라는 조건식이 있다고 했을 때, || 연산자는 좌변이 true인 것을 확인하는 순간, true를 반환하고 종료된다. || 연산은 둘 중 하나만 true이면 되기 때문에, 우변의 값과 관계없이 무조건 true가 되기 때문이다.

 

마찬가지로 'false && true'라는 조건식이 있다고 했을 때, && 연산자는 좌변이 false인 것을 확인하는 순간, false를 반환하고 종료된다. && 연산은 둘 다 true여야 되기 때문에, 우변의 값과 관계없이 무조건 false가 되기 때문이다.

 

따라서 || 연산을 할 때는 true일 확률이 높은 조건을 좌측에, && 연산을 할 때는 false일 확률이 높은 조건을 좌측에 배치하면 더 빠른 연산결과를 얻을 수 있다.

 

 

비트 연산자

| (OR 연산자): 피연산자 중 한쪽이라도 값이 1이면 1, 아니면 0을 반환한다.
&(AND 연산자): 피연산자 양쪽 모두 1이어야만 1, 아니면 0을 반환한다.
^(XOR 연산자): 피연산자의 값이 서로 다를 때만 1, 아니면 0을 반환한다.
~(비트 전환 연산자): 피연산자의 값이 0이면 1로, 1이면 0으로 변환한다. 

비트 연산자는 정수형 피연산자를 대상(문자형 포함)으로, 비트 단위 논리 연산을 하는 연산자이다.

연산의 대상이 비트라는 것을 제외하면, 기본적인 연산은 논리 연산자와 동일하다.

비트 연산 예시

 

쉬프트 연산자

쉬프트 연산자는 피연산자의 각 자리를 왼쪽(<<) 또는 오른쪽(>>)으로 이동(shift)시키는 연산자이다.

왼쪽 피연산자의 2진수 값을 오른쪽 피연산자의 값만큼 이동시키는 연산을 수행한다.

 

기본적으로 저장 범위에서 벗어난 값은 버리고, 빈자리를 0으로 채우는 연산을 진행하는데,

오른쪽 쉬프트 연산자 '>>'는 부호를 유지하기 위해 왼쪽 피연산자의 값이 음수일 때는 1을 채운다.

쉬프트 연산 예시

 

x << n은 x * 2ⁿ과 동일한 결과를 반환하고, x >> n은 x / 2ⁿ과 동일한 결과를 반환한다.

사실 쉬프트 연산은 왼쪽 쉬프트 연산일 때는 '<<'는 2ⁿ을 곱하는 연산과 동일하고, 오른쪽 쉬프트 연산 '>>'일 때는 2ⁿ을 나누는 연산과 동일하다. 이해하기 어렵다면 10진수로 생각해 보면 된다.

 

예를 들어 '1234 << 2'를 한다고 가정하면, 왼쪽으로 2칸을 밀고 빈자리를 0으로 채우니 '123400'이 될 것이다.

잘 생각해 보면 '123400'은 '1234 * 10²'과 같다는 사실을 확인할 수 있다.

 

그렇다면 사칙 연산자를 사용하면 되는데 왜 굳이 쉬프트 연산자가 존재하는 것일까?

왜냐하면 컴퓨터의 입장에서는 사칙 연산보다 비트 연산이 훨씬 계산하기 쉽기 때문이다.

그래서 쉬프트 연산자를 사용하면 프로그램의 실행 속도가 더 빨라진다. 하지만 개발자의 입장에서 쉬프트 연산자는 가독성이 좋은 코드가 아니기 때문에, 실행 속도가 중요하지 않다면 사칙 연산자를 사용하는 것이 더 낫다.

 

 

삼항 연산자

삼항 연산자3개의 피연산자를 사용하는 연산자로, 조건에 따라 다른 결과를 반환하기 때문에 조건 연산자라고도 부른다. 단항 연산자나 이항 연산자와 달리, 삼항 연산자는 조건 연산자 하나뿐이므로 보통 삼항 연산자라고 부른다.

 

조건식 ? 참일 때 반환할 값 : 거짓일 때 반환할 값; 

위와 같은 형태로 사용하며, 조건식의 결과에 따라 각각 해당하는 값을 반환한다.

 

삼항 연산자는 조건문인 if문과 같은 역할을 수행하며, if문과 마찬가지로 중첩해서 사용할 수 있다.

result = x > 0 ? 1 : (x == 0 ? 0 : -1);

 

삼항 연산자를 사용하면 if문을 사용하는 것보다 간결하게 조건식을 표현할 수 있다는 장점이 있지만, 값을 무조건 반환해야 하기 때문에 언제나 사용할 수 있는 것은 아니다.

 

 

복합 대입 연산자

복합 대입 연산자는 대입 연산자에 다른 연산자를 결합하여, 연산과 대입을 동시에 할 수 있게 해 준다.

대입 연산자와 복합 대입 연산자 비교