논리 연산자
논리 연산자 (Logical operators)
논리 연산자는 불리언 연산의 결과를 돌려줘요. 부정(!), AND(&&), OR(||) 세 가지를 묶어서 다루는 주제예요. 특히 &&와 ||의 단락 평가(short-circuit) 동작이 핵심이에요.
본문
| 연산자 이름 | 문법 | 오버로드 가능 | 클래스 안 프로토타입 예 (T) | 클래스 밖 프로토타입 예 (T) |
|---|---|---|---|---|
| 부정 (negation) | !a (또는 not a) |
예 | bool T::operator!() const; |
bool operator!(const T& a); |
| AND | a && b (또는 a and b) |
예 | bool T::operator&&(const T2& b) const; |
bool operator&&(const T& a, const T2& b); |
| 포함 OR (inclusive OR) | a || b (또는 a or b) |
예 | bool T::operator||(const T2& b) const; |
bool operator||(const T& a, const T2& b); |
참고:
- 키워드 형태(
and,or,not)와 기호 형태(&&,||,!)는 서로 바꿔 쓸 수 있어요 (대체 표현 참고). - 모든 내장 연산자는
bool을 돌려주고, 대부분의 사용자 정의 오버로드도 내장처럼 쓰일 수 있도록bool을 돌려줘요. 다만 사용자 정의 오버로드에서는 반환 타입으로 어떤 타입이든(void포함) 쓸 수 있어요. - 내장
&&와||는 단락 평가를 수행해요 (첫 번째 피연산자를 평가한 뒤 결과가 확정되면 두 번째 피연산자를 평가하지 않아요). 반면 오버로드된 연산자는 일반 함수 호출처럼 동작해서 항상 두 피연산자를 모두 평가해요.
설명 (Explanation)
논리 연산자 표현식은 다음 형태를 가져요:
| 형태 | 설명 |
|---|---|
!rhs |
(1) 논리 NOT |
lhs && rhs |
(2) 논리 AND |
lhs || rhs |
(3) 논리 포함 OR |
피연산자가 bool이 아니면 bool로의 문맥 변환(contextual conversion to bool)을 써서 bool로 변환돼요. 어떤 발명된 임시 변수 t에 대해 선언 bool t(arg)이 well-formed일 때만 well-formed예요.
결과는 bool prvalue예요.
내장 논리 NOT 연산자에서, 피연산자가 false면 결과는 true, 그 외에는 false예요.
내장 논리 AND 연산자에서, 두 피연산자 모두 true면 결과는 true, 그 외에는 false예요. 이 연산자는 단락 평가를 해요: 첫 번째 피연산자가 false면 두 번째 피연산자는 평가되지 않아요.
내장 논리 OR 연산자에서, 첫 번째나 두 번째 피연산자(또는 둘 다)가 true면 결과는 true예요. 이 연산자는 단락 평가를 해요: 첫 번째 피연산자가 true면 두 번째 피연산자는 평가되지 않아요.
비트 논리 연산자(&, |)는 단락 평가를 하지 않는다는 점을 기억해 둘게요.
결과 (Results)
a |
true |
false |
|---|---|---|
!a |
false |
true |
and:
a \ b |
true |
false |
|---|---|---|
true |
true |
false |
false |
false |
false |
or:
a \ b |
true |
false |
|---|---|---|
true |
true |
true |
false |
true |
false |
사용자 정의 연산자와의 오버로드 결정에서 다음 내장 함수 시그니처가 참여해요:
bool operator!(bool)
bool operator&&(bool, bool)
bool operator||(bool, bool)
예제
&&와 ||가 단락 평가를 하면서도 어떻게 조합되는지 볼게요.
#include <iostream>
#include <sstream>
#include <string>
int main()
{
int n = 2;
int* p = &n;
// pointers are convertible to bool
if ( p && *p == 2 // "*p" is safe to use after "p &&"
|| !p && n != 2) // || has lower precedence than &&
std::cout << "true\n";
// streams are also convertible to bool
std::stringstream cin;
cin << "3...\n" << "2...\n" << "1...\n" << "quit";
std::cout << "Enter 'quit' to quit.\n";
for (std::string line; std::cout << "> "
&& std::getline(cin, line)
&& line != "quit";)
std::cout << line << '\n';
}
출력:
true
Enter 'quit' to quit.
> 3...
> 2...
> 1...
>
첫 번째 if에서 p && *p == 2는 p가 널이 아닐 때만 *p를 안전하게 평가할 수 있다는 점을 살리는 전형적인 패턴이에요. &&는 ||보다 우선순위가 높아서 (p && *p == 2) || (!p && n != 2)로 파싱돼요. for 루프에서도 std::cout << "> "를 먼저 단락 평가로 확인하고, getline으로 읽고, "quit"가 아닌 동안 반복하는 방식이 돼요.
표준 라이브러리
&&와 ||의 단락 평가 특성이 오버로드에는 적용되지 않고, 불리언 의미를 가진 타입도 드물기 때문에, 이 연산자를 오버로드하는 표준 라이브러리 클래스는 두 개뿐이에요:
std::valarray—operator!(각 요소에 단항 산술 연산자 적용),operator&&/operator||(두 valarray 또는 valarray와 값에 이항 연산자 적용)std::basic_ios—operator!(오류가 발생했는지 확인,fail()의 동의어)
더 알아보기 (Learn more)
- 연산자 우선순위 — 각 연산자의 우선순위와 결합성을 정리한 표예요.
- 연산자 오버로딩 — 사용자 정의 타입에서 연산자를 재정의하는 방법이에요.
std::logical_and,std::logical_or,std::logical_not— 각각x&&y,x\|\|y,!x를 구현하는 함수 객체예요.