관계 연산자와 논리 연산자
비교식이 만드는 bool 값과 논리 연산의 단락 평가를 이해하고 여러 조건을 정확한 판단식으로 조합합니다.
프로그램은 단순히 계산만 하는 것이 아니라 특정 조건에 따라 다른 동작을 수행해야 할 때가 많습니다.
예를 들어 점수가 60점 이상이면 합격이라는 조건을 판단해야 합니다.
이때 필요한 것이 바로 관계 연산자(Relational Operators)와 논리 연산자(Logical Operators)입니다.
이 장에서는 두 값의 크기를 비교하고, 여러 조건을 조합해 참(true)/거짓(false)을 판단하는 방법을 살펴봅니다.
이 연산자들은 앞으로 배우게 될 조건문(if), 반복문(for, while)의 핵심적인 구성 요소가 됩니다.
관계 연산자 (Relational Operators)
관계 연산자와 논리 연산자는 값의 형태, 식의 그룹핑, 평가 순서, 경계 입력을 구분해서 확인합니다.
기본 제공 관계 연산자와 동등 비교 연산자는 두 피연산자의 관계를 비교하고 bool 결과를 만듭니다. std::string 같은 타입이 제공하는 오버로드 연산자는 해당 타입의 별도 계약을 따르며, 모든 사용자 정의 비교 연산자가 반드시 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)은 언어 차원의 오류가 아니라 유효한 대입 표현식입니다.a에10을 저장한 뒤 그 결과를bool로 문맥 변환하므로 조건은 참이 됩니다. 많은 컴파일러가 경고하지만 경고에만 의존하지 말고 비교 의도를a == 10으로 명시해야 합니다. -
실수 비교는 도메인 계약부터 정하기: 정확한 동일성이 필요한 값에는
==가 맞을 수 있습니다. 반올림 오차를 허용해야 한다면 값의 스케일, 절대·상대 허용 오차, NaN과 무한대 정책을 먼저 정합니다. 고정 절대 허용 오차는 크기 범위를 아는 문제에만 적합한 한 가지 정책입니다.double a = 0.1 + 0.2; double b = 0.3; // 알려진 스케일에서 선택한 애플리케이션 정책의 예 const double tolerance = 1e-8; if (std::isfinite(a) && std::isfinite(b) && std::abs(a - b) <= tolerance) { std::cout << "정한 허용 오차 안에 있다\n"; }
#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로 문맥 변환되며, 연산 결과의 타입은 bool입니다.
관계 연산자의 결과(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) | 피연산자의 논리 값을 반전시킵니다. (true는 false로, false는 true로). | !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: 만약A가false라면,B의 값에 관계없이 전체 표현식은false가 됩니다. 따라서B는 평가되지 않습니다.A || B: 만약A가true라면,B의 값에 관계없이 전체 표현식은true가 됩니다. 따라서B는 평가되지 않습니다.
이러한 단락 평가는 특정 조건이 만족될 때만 안전하게 다음 코드를 실행해야 하는 경우에 유용합니다.
예를 들어 포인터가 nullptr인지 먼저 확인한 뒤, null이 아닐 때만 역참조하는 코드를 실행할 수 있습니다.
아래 다이어그램은 왼쪽 값에 따라 오른쪽 피연산자를 평가하거나 건너뛰는 두 갈래를 직접 비교합니다.
기본 제공 논리 연산자의 조건부 오른쪽 평가
내장 &&/||가 왼쪽 값에 따라 오른쪽 평가를 생략하는 실제 실행 규칙이다. 본문 nullptr 검사 사례의 조건 하나를 명시해 선택된 경로만 진행하고 전체 분기는 위 관계도와 표에 남긴다.
BUILT-IN SHORT-CIRCUIT CONTRACT
논리 연산은 왼쪽 결과에 따라 오른쪽 평가를 생략한다
왼쪽 A를 먼저 평가해 bool로 문맥 변환합니다. 그 값만으로 결과가 정해지지 않을 때에만 오른쪽 B를 평가합니다.
A && B
전체가 참이려면 두 피연산자가 모두 참이어야 합니다.
bool(A) == falseB미평가 · 결과는 즉시falsebool(A) == trueB평가 · 결과는bool(B)
A || B
전체가 거짓이려면 두 피연산자가 모두 거짓이어야 합니다.
bool(A) == trueB미평가 · 결과는 즉시truebool(A) == falseB평가 · 결과는bool(B)
이 계약은 기본 제공 &&와 ||에 적용됩니다. 논리 연산자를 오버로드한 함수 호출은 오른쪽 피연산자를 생략하지 않습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 기본 제공 AND는 A가 false면 B를 평가하지 않고 false로 끝내며, A가 true일 때 B를 평가한다. 기본 제공 OR는 A가 true면 B를 평가하지 않고 true로 끝내며, A가 false일 때 B를 평가한다. 두 연산은 독립 비교이며 사용자 정의 함수 호출에 적용하지 않는다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 왼쪽 검사 nullptr 사례의 내장 &&에서 먼저 포인터를 검사한다. |
|
|
| 오른쪽 생략 선택한 false 경로에서는 역참조식을 평가하지 않는다. |
|
|
기본 제공 논리 연산자의 조건부 오른쪽 평가
내장 &&/||가 왼쪽 값에 따라 오른쪽 평가를 생략하는 실제 실행 규칙이다. 본문 nullptr 검사 사례의 조건 하나를 명시해 선택된 경로만 진행하고 전체 분기는 위 관계도와 표에 남긴다.
BUILT-IN SHORT-CIRCUIT CONTRACT
논리 연산은 왼쪽 결과에 따라 오른쪽 평가를 생략한다
왼쪽 A를 먼저 평가해 bool로 문맥 변환합니다. 그 값만으로 결과가 정해지지 않을 때에만 오른쪽 B를 평가합니다.
A && B
전체가 참이려면 두 피연산자가 모두 참이어야 합니다.
bool(A) == falseB미평가 · 결과는 즉시falsebool(A) == trueB평가 · 결과는bool(B)
A || B
전체가 거짓이려면 두 피연산자가 모두 거짓이어야 합니다.
bool(A) == trueB미평가 · 결과는 즉시truebool(A) == falseB평가 · 결과는bool(B)
이 계약은 기본 제공 &&와 ||에 적용됩니다. 논리 연산자를 오버로드한 함수 호출은 오른쪽 피연산자를 생략하지 않습니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 기본 제공 AND는 A가 false면 B를 평가하지 않고 false로 끝내며, A가 true일 때 B를 평가한다. 기본 제공 OR는 A가 true면 B를 평가하지 않고 true로 끝내며, A가 false일 때 B를 평가한다. 두 연산은 독립 비교이며 사용자 정의 함수 호출에 적용하지 않는다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 왼쪽 검사 nullptr 사례의 내장 &&에서 먼저 포인터를 검사한다. |
|
|
| 오른쪽 생략 선택한 false 경로에서는 역참조식을 평가하지 않는다. |
|
|
// if (ptr != nullptr && ptr->member == value) { ... }
// 만약 ptr이 nullptr이면, ptr != nullptr이 false가 되어 ptr->member에 접근하지 않아 안전함.ptr != nullptr은 null인 경우만 제외합니다. null이 아닌 포인터도 이미 수명이 끝난 객체를 가리키는 dangling pointer일 수 있으므로, 이 검사만으로 포인터의 전체 유효성이 증명되지는 않습니다. 또한 사용자 정의 operator&&와 operator||는 함수 호출이므로 오른쪽 피연산자 평가를 생략하지 않습니다.
and, or, not, not_eq는 각각 &&, ||, !, !=와 동작이 같은 대체 표기입니다.
예시
포인터를 배우기 전에도 단락 평가를 확인할 수 있습니다. 아래는 물건 10개를 양수인 상자 수로 나눌 때, 한 상자에 2개 이상 배분되는지를 판단하는 완전한 프로그램입니다.
#include <iostream>
int main() {
int boxes{0};
bool enough = (boxes > 0) && (10 / boxes >= 2);
std::cout << std::boolalpha << enough << "\n";
}boxes > 0이 거짓이므로 오른쪽 나눗셈은 실행하지 않고 false를 출력합니다. 순서를 뒤집으면 검사 전에 0으로 나누게 됩니다. &로 바꾸어도 오른쪽 평가를 생략하지 않으므로 같은 보호가 되지 않습니다. 두 위험한 변경을 실행 실험으로 확인하지 않습니다.
조건을 바꾸어 판단하기
boxes가 2일 때와 6일 때의 출력과 실제 몫을 먼저 적습니다. x=-3에서 수학식처럼 쓴 0 <= x <= 10과 (0 <= x) && (x <= 10)도 비교해 보세요.
단락 평가와 범위 검사 풀이
상자 2개는 몫이 5이므로 true, 6개는 정수 몫이 1이므로 false입니다. 두 경우 모두 왼쪽 조건이 참이어서 오른쪽을 평가합니다. x=-3에서는 연쇄 비교가 (0 <= -3) <= 10, 즉 false <= 10으로 묶입니다. false가 정수 0으로 변환되어 전체는 참입니다. 두 비교를 &&로 연결한 식은 첫 조건에서 거짓이 되어 올바르게 범위 밖을 판단합니다.
#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;
}C++ 연산자 우선순위와 결합 규칙
복잡한 표현식에서는 산술 연산자, 관계 연산자, 논리 연산자가 함께 사용될 수 있습니다.
우선순위와 결합 규칙은 식이 어떻게 묶이는지를 정합니다. 일반적인 피연산자 평가 순서를 정하는 규칙과는 구분해야 합니다.
이 문서에 등장하는 연산자의 우선순위는 다음과 같습니다(높은 것에서 낮은 것 순).
후위 연산자 (expr++, expr--)
전위·단항 연산자 (!, ++expr, --expr, +, -)
산술 연산자 (*, /, % 다음 +, -)
관계 연산자 (<, >, <=, >=)
동등 비교 연산자 (==, !=)
논리 AND (&&)
논리 OR (||)
대입 연산자 (=, +=, -= 등)
int x = 10, y = 5, z = 15;
bool result = x + y > z && z - y < x;
// 그룹핑: ((x + y) > z) && ((z - y) < x)
// 왼쪽: 10 + 5 > 15 -> 15 > 15 -> false
// 기본 제공 &&는 여기서 단락 평가하므로 z - y < x는 평가하지 않음
// 결과: false
std::cout << "복합 조건 결과: " << std::boolalpha << result << std::endl; // 출력: false괄호는 의도한 그룹핑을 명확하게 하지만, 한 그룹 안에서 일반적인 피연산자 평가 순서를 지정하는 도구는 아닙니다.
앞 운전 예제의 나이·면허·점수 조건은 논리식 연습용 가정이며 실제 운전·대여 자격의 법적 기준이 아닙니다. needsTransport라는 이름도 이동수단이 필요한지를 충분히 모델링하지 못합니다. hasLicense || hasCar가 판정하는 것은 오직 두 보유 조건 중 하나 이상이 참인지입니다. 실제 업무에서는 요구 문장과 변수 이름부터 이 판정에 맞추어 정합니다.
수학식처럼 비교를 이어 쓰면 C++에서는 다른 식으로 묶입니다.
0 <= x <= 10; // (0 <= x) <= 10: 원하는 범위 검사가 아님
0 <= x && x <= 10; // 올바른 범위 검사