논리 연산자

논리 연산자 (Logical operators)

논리 연산자는 불리언 연산의 결과를 돌려줘요. 부정(!), AND(&&), OR(||) 세 가지를 묶어서 다루는 주제예요. 특히 &&||단락 평가(short-circuit) 동작이 핵심이에요.

출처: cppreference - Logical operators

본문

연산자 이름 문법 오버로드 가능 클래스 안 프로토타입 예 (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 == 2p가 널이 아닐 때만 *p를 안전하게 평가할 수 있다는 점을 살리는 전형적인 패턴이에요. &&||보다 우선순위가 높아서 (p && *p == 2) || (!p && n != 2)로 파싱돼요. for 루프에서도 std::cout << "> "를 먼저 단락 평가로 확인하고, getline으로 읽고, "quit"가 아닌 동안 반복하는 방식이 돼요.

표준 라이브러리

&&||의 단락 평가 특성이 오버로드에는 적용되지 않고, 불리언 의미를 가진 타입도 드물기 때문에, 이 연산자를 오버로드하는 표준 라이브러리 클래스는 두 개뿐이에요:

  • std::valarrayoperator!(각 요소에 단항 산술 연산자 적용), operator&&/operator||(두 valarray 또는 valarray와 값에 이항 연산자 적용)
  • std::basic_iosoperator!(오류가 발생했는지 확인, fail()의 동의어)

더 알아보기 (Learn more)

  • 연산자 우선순위 — 각 연산자의 우선순위와 결합성을 정리한 표예요.
  • 연산자 오버로딩 — 사용자 정의 타입에서 연산자를 재정의하는 방법이에요.
  • std::logical_and, std::logical_or, std::logical_not — 각각 x&&y, x\|\|y, !x를 구현하는 함수 객체예요.