4단원 — 연산자
값을 더하고, 비교하고, 논리적으로 결합하고, 형식을 바꾸는 것은 모두 **연산자**가 합니다. 이번 단원에서 다루는 연산자는 이후 모든 단원의 기초가 됩니다.
이 강의에서 배우는 것
- 1산술/관계/논리/대입/증감/비트 연산자를 구분한다.
- 2연산자 우선순위를 의식한다.
- 3정수 나눗셈과 형 변환(타입 캐스팅)의 함정을 피한다.
왜 연산자에 별도 단원이 필요할까?
겉보기엔 익숙한 사칙연산처럼 보이지만, C에는 함정이 많습니다.
- `7 / 2` 의 결과는 `3.5`가 아니라 `3` (정수 나눗셈).
- `a == b` 와 `a = b` 는 완전히 다릅니다(비교 vs 대입).
- `a++ + ++a` 같은 식은 결과가 컴파일러마다 다를 수 있습니다 (정의되지 않은 동작).
이 단원에서 "왜 결과가 이렇게 나오는지"를 미리 손에 익혀 둡니다.
핵심 개념
1) 산술 연산자
| 연산자 | 의미 | 예 |
|---|---|---|
| `+` `-` `*` `/` | 사칙연산 | `7 / 2` → 3 (정수) |
| `%` | 나머지 (정수형 전용) | `7 % 2` → 1 |
**정수 / 정수 = 정수**, **실수 / 정수 = 실수**, **정수 / 실수 = 실수**.
2) 증감 연산자 (전위 / 후위)
int a = 5;
int b = a++; // 후위: b는 5, a는 6
int c = ++a; // 전위: a를 7로 만든 뒤 c에도 7타이밍이 다르므로 한 줄에 같은 변수에 ++/-- 를 두 번 쓰면 위험합니다.
3) 관계 연산자
`==`, `!=`, `<`, `>`, `<=`, `>=`. 결과는 **0 또는 1**.
4) 논리 연산자와 단축 평가
| 연산자 | 의미 | ||
|---|---|---|---|
| `&&` | AND. 좌측이 false면 우측은 평가하지 않음 | ||
| `\ | \ | ` | OR. 좌측이 true면 우측은 평가하지 않음 |
| `!` | NOT |
if (p != NULL && *p > 0) { /* p가 NULL이면 *p는 평가되지 않아 안전 */ }5) 대입 연산자 가족
`=`, `+=`, `-=`, `*=`, `/=`, `%=`. `a += 3`은 `a = a + 3`과 같습니다.
6) 비트 연산자 (참고)
`&`, `|`, `^`, `~`, `<<`, `>>`. 임베디드/비트 플래그에서 자주 쓰입니다.
7) 형 변환(캐스팅)
int total = 95, count = 4;
double avg = (double)total / count; // 23.75`(double)`이 한쪽을 실수로 만들면 나머지도 실수로 자동 승격됩니다.
예제로 보기
예제 1 — `ex01_arith.c` : 사칙연산과 나머지
int a = 17, b = 5;**실행 결과**
a = 17, b = 5
a + b = 22
a - b = 12
a * b = 85
a / b = 3 (정수 나눗셈)
a % b = 2 (나머지)
a / (double)b = 3.400핵심: 같은 17/5인데 자료형 따라 결과(3 vs 3.4)가 달라집니다.
예제 2 — `ex02_increment.c` : 전위와 후위 차이
a = 5; b = a++; // 후위
a = 5; b = ++a; // 전위
a += 10; a *= 2;**실행 결과**
후위: a=6, b=5
전위: a=6, b=6
a += 10 -> 16
a *= 2 -> 32핵심: **후위는 "사용 후 증가"**, **전위는 "증가 후 사용"**. 결과적으로 a는 둘 다 6.
예제 3 — `ex03_logical.c` : 관계와 논리
int age = 25, has_license = 1;**실행 결과**
a == b : 0
a != b : 1
a < b : 1
a >= b : 0
성인이고 면허 있음: 1
미성년 또는 면허 없음: 0핵심: 논리 연산 결과는 0/1이며, **0이 아닌 모든 값이 true** 로 취급됩니다.
예제 4 — `ex04_cast.c` : 캐스팅의 위력
int total = 95, count = 4;
double avg_wrong = total / count; // 23
double avg_right = (double)total / count; // 23.75**실행 결과**
잘못된 평균(정수 나눗셈): 23.000
올바른 평균(캐스팅): 23.750
(int)3.99 = 3 (소수점 절단)핵심: `int`로 캐스팅은 **반올림이 아니라 절단**(truncation).
다른 시각으로 보기 — 우선순위 한 줄 요약
높음 → 낮음
( ) [ ] -> . (괄호/접근)
! ++ -- (cast) sizeof &(주소) (단항)
* / % (곱셈류)
+ - (덧셈류)
< <= > >= (관계)
== != (등가)
&& || (논리)
?: (삼항)
= += -= *= /= (대입)
, (콤마)기억하기 어려우면 **항상 괄호**를 씁시다. `a * b + c`는 `(a * b) + c`로 적어 두면 명확합니다.
자주 하는 실수
- **정수 나눗셈 함정**: 평균 계산에서 `sum / count`로 적었다가 소수점이 사라짐.
- **`==`와 `=` 혼동**: `if (a = 5)` 는 항상 참 + 부작용.
- **단축 평가 미인지**: `if (count != 0 && total / count > 10)` 처럼 가드 조건을 앞에 둬야 안전.
- **부호 있는/없는 비교**: `int -1`과 `unsigned 1`을 비교하면 -1이 아주 큰 양수로 해석됩니다.
- **`++`의 다중 사용**: `a = a++ + ++a;` 는 정의되지 않은 동작.
정리
- 산술 연산은 자료형이 섞이면 결과 자료형이 결정된다(보통 더 큰 쪽으로 승격).
- 정수/정수 나눗셈은 소수부가 잘린다. 평균 등에서는 캐스팅 필수.
- 관계/논리 연산자의 결과는 0 또는 1이며, 0이 아닌 값은 true.
- 단축 평가로 NULL 체크 같은 안전 가드를 만들 수 있다.
- 모호하면 **괄호로 우선순위를 명시**.
직접 해 보기
cd src
gcc -std=c11 -Wall -o ex01 ex01_arith.c && ./ex01
gcc -std=c11 -Wall -o ex02 ex02_increment.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_logical.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_cast.c && ./ex04응용:
- `ex01`의 a, b 값을 음수로 바꾸고 `%`의 결과 부호를 관찰하세요.
- `ex04`에서 `(int)(-3.99)`의 결과는 어떻게 될지 예측 후 확인.
💻 예제 (examples)
실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.
#include <stdio.h>
int main(void) {
int a = 17, b = 5;
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", a + b);
printf("a - b = %d\n", a - b);
printf("a * b = %d\n", a * b);
printf("a / b = %d (정수 나눗셈)\n", a / b);
printf("a %% b = %d (나머지)\n", a % b);
printf("a / (double)b = %.3f\n", a / (double)b);
return 0;
}
a = 17, b = 5
a + b = 22
a - b = 12
a * b = 85
a / b = 3 (정수 나눗셈)
a % b = 2 (나머지)
a / (double)b = 3.400#include <stdio.h>
int main(void) {
int a = 5;
int b;
b = a++; // 후위: a 사용 후 증가
printf("후위: a=%d, b=%d\n", a, b); // a=6, b=5
a = 5;
b = ++a; // 전위: a 먼저 증가
printf("전위: a=%d, b=%d\n", a, b); // a=6, b=6
a += 10; // a = a + 10
printf("a += 10 -> %d\n", a);
a *= 2;
printf("a *= 2 -> %d\n", a);
return 0;
}
후위: a=6, b=5
전위: a=6, b=6
a += 10 -> 16
a *= 2 -> 32#include <stdio.h>
int main(void) {
int a = 10, b = 20;
printf("a == b : %d\n", a == b);
printf("a != b : %d\n", a != b);
printf("a < b : %d\n", a < b);
printf("a >= b : %d\n", a >= b);
int age = 25;
int has_license = 1;
printf("성인이고 면허 있음: %d\n", (age >= 18) && has_license);
printf("미성년 또는 면허 없음: %d\n", (age < 18) || !has_license);
return 0;
}
a == b : 0
a != b : 1
a < b : 1
a >= b : 0
성인이고 면허 있음: 1
미성년 또는 면허 없음: 0#include <stdio.h>
int main(void) {
int total = 95, count = 4;
double avg_wrong = total / count;
double avg_right = (double)total / count;
printf("잘못된 평균(정수 나눗셈): %.3f\n", avg_wrong); // 23.000
printf("올바른 평균(캐스팅): %.3f\n", avg_right); // 23.750
double pi = 3.99;
int trunc = (int)pi;
printf("(int)3.99 = %d (소수점 절단)\n", trunc);
return 0;
}
잘못된 평균(정수 나눗셈): 23.000
올바른 평균(캐스팅): 23.750
(int)3.99 = 3 (소수점 절단)📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
문제 1 (hw01.c)
목표: 두 정수 `a`, `b`를 입력받아 다음을 모두 출력하세요. - 합/차/곱/몫/나머지 - a를 b로 나눈 실수 결과 (소수점 둘째 자리)
- 파일명: hw01.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int a, b;
printf("두 정수 입력: ");
scanf("%d %d", &a, &b);
printf("합: %d\n", a + b);
printf("차: %d\n", a - b);
printf("곱: %d\n", a * b);
if (b != 0) {
printf("몫: %d\n", a / b);
printf("나머지: %d\n", a % b);
printf("실수 나눗셈: %.2f\n", (double)a / b);
} else {
printf("0으로 나눌 수 없습니다.\n");
}
return 0;
}
문제 2 (hw02.c)
목표: 정수 하나를 입력받아 **짝수/홀수 여부**와 **양수/음수/0** 여부를 함께 출력하세요. 조건문이 아닌 **연산자 결과**(0/1)만 사용해 보세요.
- 파일명: hw02.c
입력: -4
짝수=1, 양수=0, 음수=1▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
int n;
printf("정수 입력: ");
scanf("%d", &n);
int even = (n % 2 == 0);
int positive = (n > 0);
int negative = (n < 0);
printf("짝수=%d, 양수=%d, 음수=%d\n", even, positive, negative);
return 0;
}
입력: -4
짝수=1, 양수=0, 음수=1문제 3 (hw03.c)
목표: 화씨 온도(`F`)를 입력받아 섭씨로 변환해 출력하세요. 공식: `C = (F - 32) * 5 / 9`. **정수 나눗셈에 주의**.
- 파일명: hw03.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
int main(void) {
double f;
printf("화씨 온도 입력: ");
scanf("%lf", &f);
double c = (f - 32) * 5.0 / 9.0;
printf("섭씨: %.2f\n", c);
return 0;
}