icon
3장 : 연산자와 표현식

관계 연산자와 논리 연산자

프로그램은 단순히 계산만 하는 것이 아니라 특정 조건에 따라 다른 동작을 수행해야 할 때가 많습니다.

예를 들어 "만약 점수가 60점 이상이면 합격"과 같은 조건을 판단해야합니다.

이때 필요한 것이 바로 관계 연산자(Relational Operators)논리 연산자(Logical Operators) 입니다.

이 장에서는 두 값의 크기를 비교하거나, 여러 조건을 조합하여 참(true) 또는 거짓(false)을 판단하는 방법에 대해 자세히 알아보겠습니다.

이 연산자들은 앞으로 배우게 될 조건문(if), 반복문(for, while)의 핵심적인 구성 요소가 됩니다.


관계 연산자 (Relational Operators)

관계 연산자는 두 피연산자 간의 크기 또는 동등성 관계를 비교하여 그 결과로 true (참) 또는 false (거짓)의 bool 타입 값을 반환합니다.

연산자이름설명예시 (결과)
==같다 (Equal to)두 피연산자의 값이 같으면 true, 아니면 false.5 == 5 (true)
!=같지 않다 (Not equal to)두 피연산자의 값이 다르면 true, 아니면 false.5 != 3 (true)
>보다 크다 (Greater than)왼쪽 피연산자가 오른쪽보다 크면 true.5 > 3 (true)
<보다 작다 (Less than)왼쪽 피연산자가 오른쪽보다 작으면 true.5 < 3 (false)
>=보다 크거나 같다 (Greater than or equal to)왼쪽 피연산자가 오른쪽보다 크거나 같으면 true.5 >= 5 (true)
<=보다 작거나 같다 (Less than or equal to)왼쪽 피연산자가 오른쪽보다 작거나 같으면 true.5 <= 3 (false)

주의사항

  • 같다(==)와 대입(=)의 혼동 금지: ==는 비교 연산자이고, =는 대입 연산자입니다. 초보자들이 가장 많이 하는 실수 중 하나이므로 항상 주의해야 합니다. if (a = 10)와 같이 실수로 대입 연산자를 사용하면 컴파일러가 경고를 주거나 (C++17부터는 특정 상황에서 오류로 처리될 수 있음), 의도치 않게 a에 10이 할당되고 조건문이 항상 참이 되는 심각한 버그를 유발할 수 있습니다.
  • 실수 비교의 주의: 부동 소수점 수(float, double)는 컴퓨터 내부 표현 방식 때문에 미세한 오차가 발생할 수 있습니다. 따라서 두 실수가 정확히 같은지 ==로 비교하는 것은 위험합니다. 대신, 두 실수의 차이가 아주 작은 값(epsilon)보다 작으면 같다고 간주하는 방식을 사용합니다.
    double a = 0.1 + 0.2; // a는 정확히 0.3이 아닐 수 있음 (예: 0.30000000000000004)
    double b = 0.3;
    if (a == b) { // 대부분 false로 나옴
        std::cout << "a와 b는 같다 (직접 비교)" << std::endl;
    } else {
        std::cout << "a와 b는 다르다 (직접 비교)" << std::endl; // 이 메시지가 출력될 확률이 높음
    }
    const double EPSILON = 0.00000001; // 아주 작은 값 정의
    if (std::abs(a - b) < EPSILON) { // std::abs는 <cmath>에 있음
        std::cout << "a와 b는 거의 같다 (엡실론 비교)" << std::endl; // 이 메시지가 출력
    }
예시
#include <iostream> // std::cout, std::endl 사용
#include <string>   // std::string 사용
#include <cmath>    // std::abs 사용 (실수 비교를 위해)

int main() {
    int score = 75;
    int passingScore = 60;
    std::string name1 = "Alice";
    std::string name2 = "Bob";
    std::string name3 = "Alice";

    // 점수 비교
    bool isPassed = (score >= passingScore);
    std::cout << "합격 여부 (score >= passingScore): " << std::boolalpha << isPassed << std::endl; // true

    bool isPerfect = (score == 100);
    std::cout << "만점 여부 (score == 100): " << std::boolalpha << isPerfect << std::endl;   // false

    // 문자열 비교 (문자열 비교는 기본적으로 사전 순 비교)
    bool namesEqual = (name1 == name2);
    std::cout << "이름 같음 (name1 == name2): " << std::boolalpha << namesEqual << std::endl; // false

    bool namesNotEqual = (name1 != name3);
    std::cout << "이름 같지 않음 (name1 != name3): " << std::boolalpha << namesNotEqual << std::endl; // false

    bool nameOrder = (name1 < name2); // 'A'가 'B'보다 앞서므로 true
    std::cout << "사전순 비교 (name1 < name2): " << std::boolalpha << nameOrder << std::endl; // true

    return 0;
}

논리 연산자 (Logical Operators)

논리 연산자는 bool 타입 피연산자를 사용하여 논리적인 연산(AND, OR, NOT)을 수행하고 그 결과로 true 또는 falsebool 타입 값을 반환합니다.

관계 연산자의 결과(true/false)나 다른 bool 변수를 조합하여 더 복잡한 조건을 만들 때 사용됩니다.

연산자이름설명예시 (결과)
&&논리 AND (Logical AND)두 피연산자 모두 true일 때만 true, 아니면 false.(true && false) (false)
||논리 OR (Logical OR)두 피연산자 중 하나라도 true이면 true, 모두 false일 때만 false.(true || false) (true)
!논리 NOT (Logical NOT)피연산자의 논리 값을 반전시킵니다. (truefalse로, falsetrue로).!true (false)

논리 AND(&&): 두 조건이 모두 참일 때만 전체 결과가 참이 됩니다. A && B

  • A가 true이고 B가 true일 때 -> true
  • A가 true이고 B가 false일 때 -> false
  • A가 false이고 B가 true일 때 -> false
  • A가 false이고 B가 false일 때 -> false

논리 OR(||): 두 조건 중 하나라도 참일 때 전체 결과가 참이 됩니다. A || B

  • A가 true이고 B가 true일 때 -> true
  • A가 true이고 B가 false일 때 -> true
  • A가 false이고 B가 true일 때 -> true
  • A가 false이고 B가 false일 때 -> false

논리 NOT(!): 단항 연산자로, 피연산자의 논리 값을 반전시킵니다. !A

  • A가 true일 때 -> false
  • A가 false일 때 -> true

단락 평가 (Short-Circuit Evaluation): &&|| 연산자는 단락 평가라는 특징을 가집니다. 이는 연산의 결과를 결정하기 위해 모든 피연산자를 평가할 필요가 없을 때, 더 이상 평가를 진행하지 않는다는 의미입니다.

  • A && B: 만약 Afalse라면, B의 값에 관계없이 전체 표현식은 false가 됩니다. 따라서 B는 평가되지 않습니다.
  • A || B: 만약 Atrue라면, B의 값에 관계없이 전체 표현식은 true가 됩니다. 따라서 B는 평가되지 않습니다.

이러한 단락 평가는 특정 조건이 만족될 때만 안전하게 다음 코드를 실행해야 하는 경우에 유용합니다.

예를 들어 포인터가 유효한지 먼저 확인한 후, 그 포인터를 사용하는 코드를 실행할 때 활용됩니다.

단락 평가 예시
// if (ptr != nullptr && ptr->member == value) { ... }
// 만약 ptr이 nullptr이면, ptr != nullptr이 false가 되어 ptr->member에 접근하지 않아 안전함.
논리 연산자 예시
#include <iostream>

int main() {
    int age = 20;
    bool hasLicense = true;
    bool hasCar = false;
    int points = 85;

    // 성인이고 면허가 있는지 (AND)
    bool canDrive = (age >= 18) && hasLicense;
    std::cout << "운전 가능 (성인 && 면허): " << std::boolalpha << canDrive << std::endl; // true

    // 면허가 있거나 차가 있는지 (OR)
    bool needsTransport = hasLicense || hasCar;
    std::cout << "교통수단 필요 (면허 || 차): " << std::boolalpha << needsTransport << std::endl; // true (면허가 있으므로)

    // 시험에 불합격했는지 (NOT)
    bool isFailed = !(points >= 70); // 70점 이상이면 합격이므로, 그 반대는 불합격
    std::cout << "시험 불합격 (만점 여부): " << std::boolalpha << isFailed << std::endl; // false (85점은 합격)

    // 복합 조건
    bool canRentCar = (age >= 21) && hasLicense && (points >= 80);
    std::cout << "렌터카 가능 (복합 조건): " << std::boolalpha << canRentCar << std::endl; // false (age >= 21이 false)

    return 0;
}

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

복잡한 표현식에서는 산술 연산자, 관계 연산자, 논리 연산자가 함께 사용될 수 있습니다.

이때 어떤 연산이 먼저 수행될지 이해하는 것이 중요합니다.

일반적인 우선순위는 다음과 같습니다 (높은 것에서 낮은 것 순)

  1. 단항 연산자 (!, ++, --, +, -)
  2. 산술 연산자 (*, /, % 다음 +, -)
  3. 관계 연산자 (<, >, <=, >=)
  4. 관계 연산자 (==, !=)
  5. 논리 AND (&&)
  6. 논리 OR (||)
  7. 대입 연산자 (=, +=, -= 등)
예시
int x = 10, y = 5, z = 15;
bool result = x + y > z && z - y < x;
// 1. 산술 연산 먼저:
//    x + y  -> 10 + 5 = 15
//    z - y  -> 15 - 5 = 10
// 2. 관계 연산 다음:
//    15 > z -> 15 > 15 -> false
//    10 < x -> 10 < 10 -> false
// 3. 논리 AND 연산 마지막:
//    false && false -> false
std::cout << "복합 조건 결과: " << std::boolalpha << result << std::endl; // 출력: false

이처럼 괄호를 적절히 사용하여 연산 순서를 명확히 하는 것이 코드의 가독성을 높이는 데 매우 중요합니다.