[C/C++] 7. if / else 조건문과 비교 연산자
앞서 논리 연산자와 참/거짓의 기준에 대해 알아보았다. 이번 글에서는 프로그램의 흐름을 제어하는 대표적인 구문인 , , 조건문과 이를 구성하는 비교 연산자, 그리고 실무에서 자주 하는 조건문 구성 시의 주의사항을 정리한다. 문은 말 그대로 "만약 ~라면"을 의미한다. 소괄호 안의 조건식이 참(이 아닌 수)이면 중괄호 내부의 코드를 실행하고, 거짓()이면 실행하지 않고 건너뛴다. 문의 조건식에는 두 값을 비교하여 참() 또는 거짓()을 반환하는 비교 연산자가 주로 사용된다. |연산자|기호|의미|예시 (a = 10, b = 20)|결과| |---|---|---|---|---| |같다||두 값이 같은가?||거짓 ()| |다르다||두 값이 다른가?||참 ()| |크다 / 작다|, |초과 / 미만인가?||참 ()| |크거나 같다 / 작거나 같다|, |이상 / 이하인가?||거짓 ()| > ⚠️ 초보자가 자주 하는 실수: 대입 연산자()와 비교 연산자()를 헷갈려 으로 작성하면, 에 이 대입된 후 은 이 아니므로 무조건 참(True) 으로 처리되는 버그가 발생한다. 하나의 조건뿐만 아니라 여러 상황에 따라 다른 코드를 실행해야 할 때 구문을 확장하여 사용한다. - *: 앞선 조건이 거짓일 때, 다음 조건을 순차적으로 검사한다. (원하는 만큼 추가 가능) - *: 연결된 모든 조건이 거짓일 때 최종적으로 실행되는 블록이다. (생략 가능) - 특징: 연결된 구문 중 하나의 조건이라도 참이 되어 실행되면, 나머지 아래의 조건검사는 완전히 건너뛴다. 조건문 작성 시 가장 중요한 설계 판단 중 하나는 "조건들을 하나로 연결할 것인가, 독립적으로 나눌 것인가?"이다. 키와 키가 로 연결되어 있으면, 키를 누르고 있는 동안에는 키 입력 조건을 검사조차 하지 않는다. 따라서 대각선 이동(W+A 동시 입력)이 불가능해진다. 동시 처리가 필요한 독립적인 사건들은 별개의 문으로 작성해야 정상적으로 동시 입력을 처리할 수 있다. - 문은 조건식이 이 아니면 참, 이면 거짓으로 판단하여 실행을 결정한다. - 로 연결된 구조는 단 하나만 실행되고 나머지는 스킵된다. - 동시 처리가 필요한 개별 조건(예: 키 입력)은 로 묶지 말고 독립된 문으로 작성해야 한다.
[C/C++] 6. 논리 연산자
앞서 증감 연산자를 알아보았다. 이번 글에서는 조건식에서 참(True)과 거짓(False)을 판단하는 논리 연산자, C++에서 추가된 * 자료형, 그리고 컴퓨터가 참과 거짓을 바라보는 기준을 정리한다. 컴퓨터 프로그래밍에서 조건 판단의 기준은 매우 명확하다. - *거짓 (False): 오직 만을 의미한다. - **참 (True): 이 아닌 모든 수를 의미한다. (1, 100, -5 등 모두 참으로 간주) 논리 연산의 결과는 참일 경우 **, 거짓일 경우 이라는 대표 정수값으로 반환된다. 초보자가 "오직 1만 참이다"라고 오해하기 쉽지만, **0이 아닌 모든 값은 조건식에서 참으로 평가된다는 점을 명심해야 한다. C 언어 초창기에는 참과 거짓을 나타내기 위해 일반 형 변수(0 또는 1)를 사용했지만, C++에 이르러 참/거짓 전용 자료형인 과 키워드(, )가 추가되었다. - 크기: 1 바이트 (8 비트) - 특징: 변수에 이나 같은 숫자를 대입해도, 내부적으로는 0이 아니므로 (1)로 자동 변환하여 저장한다. 논리 연산자는 주어진 조건들의 참/거짓 조합을 계산할 때 사용한다. - 원리: 참을 거짓으로, 거짓을 참으로 뒤집는다. - $\rightarrow$ (0) - $\rightarrow$ (1) - 일반 정수에도 적용 가능: 은 , 은 이 된다. - 원리: 양쪽 피연산자가 모두 참(0이 아닌 값)일 때만 참(1)을 반환한다. (교집합) - 하나라도 거짓()이면 결과는 거짓()이 된다. - 원리: 양쪽 피연산자 중 하나라도 참이면 참(1)을 반환한다. (합집합) - 둘 다 거짓()일 때만 결과가 거짓()이 된다. |연산자|기호|설명|조건 (참이 되는 경우)| |---|---|---|---| |NOT||논리 반전|거짓(0)을 뒤집을 때| |AND||논리곱|모든 피연산자가 참일 때| |OR||논리합|피연산자 중 하나 이상이 참일 때| - 컴퓨터는 만 거짓, 이 아닌 모든 숫자는 참으로 인식한다. - C++의 자료형은 1바이트 크기이며, 어떤 숫자를 대입하든 또는 으로 변환하여 관리한다. - 논리 연산자(, , )는 향후 배우게 될 조건문(, )과 삼항 연산자의 핵심 기초가 되므로 확실히 정리해 두자.
[C/C++] 5. 증감 연산자
앞서 산술 연산자와 우선순위에 대해 알아보았다. 이번 글에서는 변수의 값을 간편하게 증가시키거나 감소시키는 증감 연산자(, ), 그리고 많은 초보자가 헷갈려 하는 전위(Prefix)와 후위(Postfix) 방식의 차이점을 정리한다. 증감 연산자는 변수의 값을 한 단계 증가시키거나 감소시키는 단항 연산자다. - * (증가 연산자): 변수의 값을 1 증가시킨다. - * (감소 연산자): 변수의 값을 1 감소시킨다. > **💡 올바른 개념 잡기: 단순히 "1이 더해진다"보다는 "다음 단계로 이동한다"로 이해하는 것이 좋다. 나중에 학습할 포인터(Pointer) 연산에서는 피연산자의 자료형 크기에 따라 '한 단계'의 이동 단위가 4바이트, 8바이트 등으로 달라지기 때문이다. 증감 연산자는 변수의 앞이나 뒤 중 어디에 붙이느냐에 따라 연산의 우선순위가 완전히 달라진다. - 원리: "선 증가, 후 연산" - 변수의 값을 먼저 증가시킨 후, 해당 라인의 다른 연산(대입 등)을 수행한다. - 원리: "선 연산, 후 증가" - 해당 라인의 모든 연산을 먼저 처리한 후, 가장 마지막에 변수의 값을 증가시킨다. (대입 연산자보다도 우선순위가 낮다) 단독으로 또는 를 사용할 때는 결과에 차이가 없다. 하지만 실무나 객체지향 프로그래밍(C++)에서는 전위 방식을 기본적으로 사용하는 습관을 들이는 것이 권장된다. 1. 복합 연산 시의 실수를 방지: 후위 연산자는 연산 순서가 직관적이지 않아 버그를 유발하기 쉽다. 2. 성능상의 이점 (클래스 및 객체): - C++에서 사용자 정의 객체(클래스/구조체)에 증감 연산자를 오버로딩할 때, 후위 연산자는 값이 변경되기 전의 상태를 저장해 둘 임시 객체를 생성해야 한다. - 따라서 불필요한 복사 비용이 발생할 수 있으므로, 특별한 사유가 없다면 전위 연산자()를 사용하는 것이 성능상 유리하다. - 증감 연산자는 단순 1 증가가 아닌 "다음 단계로의 이동"으로 이해하는 것이 포인터 개념 확장에 도움이 된다. - 전위()는 증가 후 대입, 후위()는 대입 후 증가한다. - 코드의 직관성과 가독성을 위해 특별한 이유가 없다면 전위 방식을 우선 사용하자.
[C/C++] 4. 산술 연산자
앞서 정수와 실수의 메모리 표현 방식을 알아보았다. 이번 글에서는 데이터를 계산할 때 사용하는 가장 기본적인 산술 연산자, 연산 시 발생하는 임시 메모리 저장, 그리고 나누기(/)와 나머지(%) 연산자의 특수성을 정리한다. C/C++에서 식을 구성할 때 사용하는 연산자는 우측의 피연산자를 기반으로 연산을 수행한다. - 덧셈(), 뺄셈(), 곱셈(): 일반적인 사칙연산과 동일하게 동작한다. - 대입 연산자( harness): 우항의 값을 좌항의 변수에 저장(메모리 이동)한다. 컴퓨터는 지시된 명령어에 따라 엄격한 우선순위를 적용하여 계산한다. CPU가 연산을 수행할 때, 연산 과정의 중간 결과는 CPU 내부의 레지스터(Register) 등 임시 메모리 공간에 잠시 머무른다. 만약 처럼 연산만 하고 대입 연산자()를 사용하지 않으면, 연산 결과는 변수에 반영되지 않고 그대로 휘발되어 사라진다. 자기 자신의 값에 특정 연산을 수행한 후 다시 자신에게 저장하는 구문은 복합 대입 연산자를 사용해 간결하게 축약할 수 있다. 나누기 연산은 피연산자의 자료형(정수 vs 실수)에 따라 동작 방식이 완전히 달라진다. - 정수 나눗셈(): 소수점을 표기할 수 없으므로, 몫만 남고 소수점 아래는 버려진다. - 나머지 연산자(, 모듈러스): 나눗셈 후 남은 나머지를 구한다. 오직 정수형 피연산자끼리만 사용 가능하다. 실수 나눗셈에서는 소수점 이하까지 연산이 수행되므로 나머지라는 개념 자체가 존재하지 않는다. 따라서 실수에 연산자를 사용하면 컴파일 에러가 발생한다. 코드에 직접 작성하는 숫자를 상수(Literal)라고 부른다. 코드에 적힌 상수는 그 자체로 특정 자료형과 크기를 갖는다. 실수 연산 결과(예: )를 정수형() 변수에 대입하려 하면 소수점이 잘려 나가며 데이터 손실 경고(Warning)가 발생한다. 이 경우 명시적 형변환을 통해 컴파일러에게 의도를 전달해야 한다. - 연산만 수행하고 변수에 대입하지 않으면 중간 결과는 휘발된다. - (나머지) 연산자는 정수끼리만 가능하며 실수에는 사용할 수 없다. - 코드에 직접 적는 실수 상수는 기본적으로 타입이므로, 변수에 담을 때는 * 접미사*를 붙여주도록 하자.
[C/C++] 3. 실수형 자료형과 부동 소수점의 원리
앞서 정수형 자료형의 저장 구조와 2의 보수 표현 방식을 알아보았다. 이번 글에서는 소수점을 표현하는 실수형 자료형(float, double)의 내부 동작 원리인 부동 소수점(Floating Point) 체계와, 실수 연산 시 주의해야 할 정밀도 문제를 정리한다. 컴퓨터 메모리 관점에서 정수와 실수는 데이터를 저장하고 처리하는 방식이 완전히 다르다. - 정수형: 한정된 비트 공간 내에서 정확한 2진수 값으로 직관적으로 표현한다. - 실수형: $0$과 $1$ 사이에도 무한한 실수가 존재하기 때문에, 한정된 메모리 크기로 정확한 값을 담는 것은 불가능하다. 따라서 실수는 '정확한 값'이 아닌 '근사값'을 저장한다. 정수 와 실수 은 인간에게는 같은 수처럼 보이지만, 메모리에 저장되는 비트 패턴은 완전히 다르다. 컴퓨터는 IEEE 754 표준에 따라 실수를 지수부(Exponent)와 가수부(Mantissa)로 나누어 저장하는 부동 소수점 방식을 사용한다. $$\text{실수} = \text{가수부} \times 2^{\text{지수부}}$$ 1. 2진수 변환: $21.8125_{(10)} \rightarrow 10101.1101_{(2)}$ 2. 정규화(Normalization): 소수점을 이동시켜 맨 앞을 $1.\text{xxx}$ 형태로 맞춘다. - $1.01011101_{(2)} \times 2^4$ 3. 비트 분배: - 부호 비트 (Sign): 양수/음수 판별 - 지수부 (Exponent): 소수점의 위치 ($2^4$의 $4$) - 가수부 (Mantissa): 소수점 뒤의 유효 숫자 영역 () 소수점 아래 자릿수가 길어질수록 가수부 비트 공간이 부족해진다. 따라서 더 큰 메모리를 사용하는 자료형일수록 더 높은 를 가진다. |자료형|메모리 크기|유효 자릿수 (정밀도)|비트 구성 (부호 / 지수 / 가수)| |---|---|---|---| |float|4 바이트 (32 비트)|소수점 이하 약 7자리|$1 \text{ bit} / 8 \text{ bits} / 23 \text{ bits}$| |double|8 바이트 (64 비트)|소수점 이하 약 15~16자리|$1 \text{ bit} / 11 \text{ bits} / 52 \text{ bits}$| > C/C++에서는 기본 실수 리터럴(예: )을 형으로 처리한다. 형으로 명시하려면 숫자 뒤에 리터럴 접미사를 붙여야 한다 (). 부동 소수점 방식의 핵심은 "모든 실수를 정확하게 표현할 수 없다"는 점이다. 실수 연산 시 미세한 오차가 발생하므로, 조건문에서 연산자로 실수를 직접 비교하면 예상치 못한 버그가 발생한다. 정수와 실수가 혼합된 연산을 수행하면 컴퓨터는 둘 중 하나의 체계로 변환하는 암시적 형변환(Implicit Casting)을 거치게 되며, 이 과정에서 성능 하락이나 값 유실이 발생할 수 있다. 따라서 의도하지 않은 연산 오류를 방지하기 위해 가능하면 같은 자료형끼리 연산하고, 정수와 실수를 함께 사용할 때에는 명시적 형변환을 통해 개발자의 의도를 컴파일러에게 확실히 전달해야 한다. - 정수는 '정확한 값', 실수는 '근사값'으로 처리된다. - 실수 비교 시 연산 대신 오차 범위()를 고려하여 비교해야 한다. - 혼합 연산 시 소수점 절삭 등의 오차를 막기 위해 명시적 형변환을 적극 활용하자.