자료형, 연산자, 흐름 제어
자료형, 연산자, 흐름 제어
학습 흐름
자료형과 리터럴 → 정수 나눗셈과 % → 형변환 → 정수 범위(오버플로, unsigned)
↓
연산자(증감, 비교, 논리, 삼항) → 우선순위와 결합성 → 단락 평가
↓
if / switch → while / do while / for → break / continue → 중첩 반복문1. 프로그램의 기본 형태
#include <iostream> // cin, cout을 쓰기 위한 헤더
using namespace std; // std:: 생략
int main() { // 프로그램의 시작점
int age;
cin >> age; // 입력
cout << "age: " << age << endl; // 출력 + 줄바꿈 + 버퍼 비우기
return 0; // 0 = 정상 종료
}| 요소 | 기억할 점 |
|---|---|
#include <iostream> | cin, cout 사용에 필요 |
using namespace std; | 없으면 std::cout, std::cin, std::string처럼 써야 함 |
main | 반드시 하나 있어야 함. 없으면 링크 오류 |
endl vs '\n' | 둘 다 줄바꿈. endl은 출력 버퍼도 비움 |
2. 자료형과 리터럴
| 자료형 | 저장 대상 | 리터럴 예 | 주의 |
|---|---|---|---|
char | 문자 1개 (실제로는 작은 정수) | 'A' | 작은따옴표 |
int | 정수 (보통 32비트) | 42, 0x2A | 약 ±21억 |
long long | 큰 정수 (64비트) | 8000000000LL | |
float | 단정밀도 실수 | 3.14f | f가 없으면 double |
double | 배정밀도 실수 | 3.14 | 실수 리터럴의 기본형 |
bool | 참/거짓 | true, false | 출력하면 1, 0 |
string | 문자열 (클래스) | "Hello" | #include <string> |
문자는 정수 코드다 (아스키 코드)
char는 문자 하나를 정수 코드로 저장합니다. 산술 연산을 하면 int로 바뀌어 계산됩니다.
| 문자 | 코드 | 문자 | 코드 |
|---|---|---|---|
'0' | 48 | 'A' | 65 |
'9' | 57 | 'Z' | 90 |
'\0' (널 문자) | 0 | 'a' | 97 |
' ' (공백) | 32 | 'z' | 122 |
char c = 'A';
cout << c; // A (char로 출력)
cout << c + 1; // 66 (int로 바뀜)
cout << char(c + 1); // B (다시 char로 변환)
cout << '7' - '0'; // 7 (숫자 문자 → 정수 값)
cout << 'c' - 'a'; // 2 (알파벳 순번)초기화하지 않은 지역 변수
함수 안에서 초기값 없이 선언한 기본 자료형 변수는 쓰레기 값을 가집니다. 읽으면 결과를 예측할 수 없습니다. 전역 변수와 static 변수는 자동으로 0이 됩니다.
int count; // 함수 안: 값이 정해지지 않음
int total = 0; // 항상 초기화하는 습관
int score{0}; // 중괄호 초기화도 가능다음 출력을 쓰시오.
char c = 'A';
cout << c + 1 << ' ' << char(c + 1) << ' ' << c << '\n';
cout << '7' - '0' << ' ' << ('a' < 'B') << '\n';정답 확인
66 B A
7 0c + 1은 int 66, char(c + 1)은 'B'입니다. c 자체는 바뀌지 않습니다. 'a'(97)는 'B'(66)보다 크므로 'a' < 'B'는 0입니다.
3. 정수 나눗셈과 나머지 %
두 피연산자가 모두 정수이면 결과도 정수이고, 소수 부분은 0 쪽으로 잘립니다(버림이 아니라 절단).
| 식 | 결과 | 이유 |
|---|---|---|
7 / 2 | 3 | 3.5 → 3 |
-7 / 2 | -3 | −3.5 → −3 (0 쪽으로) |
7 / 2.0 | 3.5 | 하나라도 실수면 실수 나눗셈 |
7 % 3 | 1 | 7 = 3 × 2 + 1 |
-7 % 3 | -1 | 나머지의 부호 = 왼쪽 피연산자의 부호 |
7 % -3 | 1 | 왼쪽이 양수 |
규칙: a == (a / b) * b + a % b가 항상 성립합니다.
다음 출력을 쓰시오.
cout << 7 / 2 << ' ' << -7 / 2 << ' ' << 7 % 3 << ' '
<< -7 % 3 << ' ' << 7 % -3 << '\n';정답 확인
3 -3 1 -1 1나눗셈은 0 쪽으로 절단되고, 나머지의 부호는 왼쪽 피연산자를 따릅니다.
4. 자료형 변환
산술 변환: 더 넓은 쪽으로
서로 다른 자료형을 계산하면 더 넓은 자료형으로 맞춘 뒤 계산합니다.
bool, char, short → int (정수 승격)
int op double → double
int op long long → long long축소 변환: 정보 손실
넓은 자료형을 좁은 자료형에 넣으면 값이 잘립니다. 실수 → 정수는 소수 부분을 버립니다(0 쪽으로).
int n = 3.99; // 3
int m = -3.99; // -3 (내림이 아니라 0 쪽으로)
int k{3.99}; // 컴파일 오류: 중괄호 초기화는 축소 변환을 막음명시적 변환
static_cast<double>(7) / 2 // 3.5 (C++ 방식)
(double)7 / 2 // 3.5 (C 방식)다음 출력을 쓰시오.
int a = 7, b = 2;
double x = a / b;
double y = (double)a / b;
double z = (double)(a / b);
int n = 3.99;
int m = -3.99;
cout << x << ' ' << y << ' ' << z << ' ' << n << ' ' << m << '\n';정답 확인
3 3.5 3 3 -3x와 z는 정수 나눗셈 3을 변환한 것이고, y만 나누기 전에 a를 double로 바꿨습니다. double 3.0은 cout에서 3으로 출력됩니다.
부동소수점 오차
float, double은 많은 십진 소수를 정확히 저장하지 못합니다. 그래서 0.1 + 0.2 == 0.3은 거짓입니다. 실수 비교는 오차 범위를 둡니다.
#include <cmath>
bool same = fabs((0.1 + 0.2) - 0.3) < 1e-9; // true5. 정수의 범위: 오버플로와 unsigned
signed 오버플로 = 정의되지 않은 동작
32비트 int의 범위는 −2,147,483,648 ~ 2,147,483,647입니다. 이를 넘는 부호 있는 정수의 결과는 UB입니다. "최댓값 + 1 = 최솟값"이라고 가정하면 안 됩니다.
int big = 2147483647;
big = big + 1; // UB
long long safe = 2147483647LL + 1; // 2147483648unsigned는 범위를 넘으면 돌아간다 (규칙이 정해져 있음)
unsigned int(32비트)는 0 ~ 4,294,967,295이고, 범위를 넘으면 2³²로 나눈 나머지로 돌아갑니다. 이 동작은 표준이 보장합니다.
0 - 1 → 4294967295 (0 아래로 가면 최댓값)
4294967295 + 2 → 1
unsigned int u = -1; → 4294967295 (−1을 넣어도 최댓값)다음 출력을 쓰시오.
unsigned int u = 0;
u = u - 1;
cout << u << '\n';
int i = -1;
unsigned int k = 1;
cout << (i < k) << ' ' << (i < (int)k) << '\n';
unsigned int big = 4294967295u;
big = big + 2;
cout << big << '\n';정답 확인
4294967295
0 1
1i < k에서는 i가 unsigned로 바뀌어 4294967295 < 1이 되어 거짓입니다. k를 int로 바꾸면 −1 < 1로 참입니다. 4294967295 + 2는 한 바퀴 돌아 1입니다.
6. 연산자
대입과 복합 대입
=는 오른쪽 값을 계산한 뒤 왼쪽 변수에 저장합니다. 대입식 자체도 값(대입된 값)을 가집니다.
x = y = 10; // 오른쪽부터: y = 10, 그 결과 10을 x에 대입
x += 3; // x = x + 3
x %= 4; // x = x % 4증감 연산자: 전위와 후위
| 형식 | 식의 값 | 변수 변화 |
|---|---|---|
++x | 증가한 뒤 값 | +1 |
x++ | 증가 전 값 | +1 |
--x | 감소한 뒤 값 | −1 |
x-- | 감소 전 값 | −1 |
다음 출력을 쓰시오.
int x = 5;
int a = x++;
int b = ++x;
int c = x-- + 10;
cout << a << ' ' << b << ' ' << c << ' ' << x << '\n';정답 확인
5 7 17 6| 문장 | 식의 값 | 실행 후 x |
|---|---|---|
a = x++ | 5 | 6 |
b = ++x | 7 | 7 |
c = x-- + 10 | 7 + 10 = 17 | 6 |
비교 연산자와 = / ==
비교 결과는 bool이고 출력하면 1 또는 0입니다.
논리 연산자와 0/1 변환
- 조건식에서 0은 거짓, 0이 아닌 모든 값(음수 포함)은 참입니다.
!는 결과를bool로 뒤집습니다.!5는0,!0은1,!(-3)은0.bool을 정수로 바꾸면true → 1,false → 0.bool b = -5;는true.
단락 평가 (Short-circuit)
| 식 | 왼쪽이 이것이면 | 오른쪽은 |
|---|---|---|
A && B | 거짓 | 실행하지 않음 (결과 거짓) |
A || B | 참 | 실행하지 않음 (결과 참) |
오른쪽에 ++, 함수 호출, 나눗셈이 있으면 실행 여부에 따라 결과가 달라집니다. 0으로 나누는 것을 막는 가드로도 씁니다: d != 0 && 10 / d > 2.
다음 출력을 쓰시오.
int x = 0, y = 0;
bool r1 = (x++ > 0) && (y++ > 0);
bool r2 = (x++ > 0) || (y++ > 0);
bool r3 = (x > 5) || (++y);
cout << r1 << r2 << r3 << ' ' << x << ' ' << y << '\n';정답 확인
011 2 1r1:x++ > 0은 0 > 0으로 거짓(x는 1이 됨) →y++는 실행 안 됨.r2:x++ > 0은 1 > 0으로 참(x는 2) →y++는 실행 안 됨.r3:x > 5가 거짓이므로++y를 실행 → y = 1, 0이 아니므로 참.
삼항(조건) 연산자 ?:
조건 ? 참일 때 값 : 거짓일 때 값
int max = (a > b) ? a : b;
cout << (n % 2 == 0 ? "even" : "odd");cout과 함께 쓸 때는 괄호로 감싸야 합니다. <<가 ?:보다 우선순위가 높기 때문입니다.
7. 연산자 우선순위와 결합성
우선순위 표 (위가 먼저 묶임)
| 순위 | 연산자 | 결합 방향 |
|---|---|---|
| 1 | ( ), a[i], f(), 후위 x++ x-- | → |
| 2 | 전위 ++x --x, 단항 ! - +, (type), sizeof | ← |
| 3 | * / % | → |
| 4 | + - | → |
| 5 | << >> (cout 출력도 이것) | → |
| 6 | < <= > >= | → |
| 7 | == != | → |
| 8 | && | → |
| 9 | || | → |
| 10 | ? : | ← |
| 11 | = += -= *= /= %= | ← |
외우는 순서: 단항 → 산술(곱·나눗셈·나머지 → 덧셈·뺄셈) → 비교(크기) → 같음 → AND → OR → 삼항 → 대입
계산하는 방법
- 단항 연산자(
!,-,++)를 먼저 피연산자에 붙입니다. - 우선순위가 높은 연산자부터 괄호로 묶습니다.
- 같은 순위가 이어지면 왼쪽부터 묶습니다. (단항, 삼항, 대입만 오른쪽부터)
- 묶인 괄호를 안쪽부터 계산합니다. 단,
&&,||는 단락 평가로 오른쪽을 건너뛸 수 있습니다.
1 + 2 < 4 && 0 || 3 > 2
① 산술: (1 + 2) < 4 && 0 || 3 > 2
② 비교: ((1 + 2) < 4) && 0 || (3 > 2)
③ AND: (((1 + 2) < 4) && 0) || (3 > 2)
④ OR: 전체
계산: (1 && 0) || 1 → 0 || 1 → 1!x == 0 → (!x) == 0 ! 가 == 보다 먼저
-x % 3 → (-x) % 3 단항 - 가 % 보다 먼저
a % b * 2 → (a % b) * 2 같은 순위는 왼쪽부터
x < y < z → (x < y) < z 0 또는 1과 z를 비교 (수학의 범위 비교가 아님!)
x = (7 * 3 / 4 - 2) * 5 == y → x = ((((7*3)/4)-2)*5 == y) == 가 = 보다 먼저int a = 10, b = 3;일 때 각 식의 값을 쓰시오.
| 식 | |
|---|---|
| (1) | 2 + 3 * 4 % 5 |
| (2) | 10 - 4 - 3 |
| (3) | !0 + 1 |
| (4) | 5 > 3 == 1 |
| (5) | 1 + 2 < 4 && 0 || 3 > 2 |
| (6) | a % b * 2 + a / b |
| (7) | !a == 0 |
| (8) | -2 * -3 |
정답 확인
| 묶는 순서 | 값 | |
|---|---|---|
| (1) | 2 + ((3 * 4) % 5) = 2 + (12 % 5) | 4 |
| (2) | (10 - 4) - 3 | 3 |
| (3) | (!0) + 1 = 1 + 1 | 2 |
| (4) | (5 > 3) == 1 = 1 == 1 | 1 |
| (5) | ((3 < 4) && 0) || (3 > 2) = 0 || 1 | 1 |
| (6) | ((10 % 3) * 2) + (10 / 3) = 2 + 3 | 5 |
| (7) | (!10) == 0 = 0 == 0 | 1 |
| (8) | (-2) * (-3) | 6 |
8. 조건문 if
if (score >= 90) grade = 'A';
else if (score >= 80) grade = 'B'; // 위 조건이 거짓일 때만 검사
else grade = 'F';else if사슬은 처음 참이 된 분기 하나만 실행합니다. 그래서 범위가 좁은(높은) 조건을 먼저 씁니다.if를 여러 개 따로 쓰면 각각 독립적으로 검사되어 여러 개가 실행될 수 있습니다.- 중괄호가 없으면
if는 바로 다음 한 문장만 지배합니다.
각 코드의 출력을 쓰시오.
// (1)
int x = 7;
if (x = 0) cout << "A"; else cout << "B";
cout << x << '\n';
// (2) a = 5일 때와 a = 1일 때
int b = 0;
if (a > 3)
if (b > 3)
cout << "X";
else
cout << "Y";
cout << "Z\n";
// (3)
int s = 95;
if (s >= 90) cout << "A";
if (s >= 80) cout << "B";
else cout << "C";
cout << '\n';정답 확인
(1) B0. x = 0은 대입이므로 x는 0이 되고 조건은 거짓입니다.
(2) a = 5 → YZ, a = 1 → Z. else는 안쪽 if (b > 3)의 짝입니다. a = 1이면 바깥 if가 거짓이라 안쪽 if-else 전체를 건너뜁니다.
(3) AB. 두 if는 독립적입니다. 두 번째 if의 else는 실행되지 않습니다.
9. switch
switch (정수식) { // int, char, bool, enum 가능 (double, string 불가)
case 상수1:
문장;
break; // 없으면 아래 case로 계속 진행 (fall-through)
case 상수2:
case 상수3: // 여러 값을 같은 처리로 묶기
문장;
break;
default: // 일치하는 case가 없을 때
문장;
}아래 sw(n)에 대해 sw(2), sw(4), sw(7)의 출력을 쓰시오. 그리고 sw2(5), sw2(2), sw2(1)의 출력을 쓰시오.
void sw(int n) {
switch (n) {
case 1: cout << "one ";
case 2: cout << "two ";
case 3: cout << "three "; break;
case 4: cout << "four ";
default: cout << "other ";
}
}
void sw2(int n) {
switch (n) {
default: cout << "D";
case 1: cout << "1"; break;
case 2: cout << "2";
}
}정답 확인
| 호출 | 출력 | 이유 |
|---|---|---|
sw(2) | two three | case 2부터 실행, case 3의 break에서 멈춤 |
sw(4) | four other | break가 없어 default까지 실행 |
sw(7) | other | 일치하는 case가 없어 default로 |
sw2(5) | D1 | default로 점프한 뒤 아래 case 1까지 진행 |
sw2(2) | 2 | case 2가 마지막이므로 여기서 끝 |
sw2(1) | 1 | case 1 실행 후 break |
10. 반복문
세 반복문 비교
| 반복문 | 조건 검사 시점 | 최소 실행 횟수 | 주로 쓰는 경우 |
|---|---|---|---|
while (조건) { } | 본문 전 | 0 | 횟수를 모르고 조건으로 반복 |
do { } while (조건); | 본문 후 | 1 | 입력을 먼저 받고 검사 |
for (초기식; 조건; 증감식) { } | 본문 전 | 0 | 횟수가 정해진 반복 |
for의 실행 순서: 초기식(한 번) → 조건 → 본문 → 증감식 → 조건 → 본문 → …
while ↔ for 변환
초기식; for (초기식; 조건; 증감식) {
while (조건) { ↔ 본문;
본문; }
증감식;
}break와 continue
| 키워드 | 동작 |
|---|---|
break | 가장 안쪽 반복문(또는 switch) 하나를 즉시 빠져나감 |
continue | 이번 회차의 나머지를 건너뛰고 다음 회차로 |
다음 출력을 쓰시오.
// (1)
int y = 1;
for (int i = 5; i > 0; i--) {
if (i % 2 == 1) y = y + i;
else y++;
cout << y << ' ';
}
// (2)
int i = 10;
do { cout << i; i++; } while (i < 5);
cout << " i=" << i;
// (3)
int k;
for (k = 0; k < 10; k += 3);
cout << k;
// (4)
int n = 1, total = 0;
while (n < 20) {
if (n % 3 == 0) { n++; continue; }
if (n > 10) break;
total += n;
n++;
}
cout << total << ' ' << n;정답 확인
(1) 6 7 10 11 12 — i = 5, 3, 1(홀수)에서는 i를 더하고, 4, 2에서는 1을 더합니다.
(2) 10 i=11 — do while은 조건(10 < 5)이 거짓이어도 한 번 실행합니다.
(3) 12 — for 뒤의 ; 때문에 본문이 비어 있습니다. k = 0, 3, 6, 9, 12에서 조건이 거짓이 되어 멈춥니다.
(4) 37 11 — 3의 배수(3, 6, 9)는 건너뛰고, 1 + 2 + 4 + 5 + 7 + 8 + 10 = 37. n = 11에서 break.
다음 while문을 같은 동작의 for문으로 바꾸고, 반복이 끝난 뒤 sum과 j의 값을 쓰시오. 두 변수는 반복문 밖에서도 사용해야 한다.
// (1)
int sum = 0;
int j = 0;
while (j <= 10) {
sum = sum + j;
j++;
}
// (2)
int sum = 0;
int j = 5;
while (j > 0)
sum = sum + j--;정답 확인
// (1)
int sum = 0;
int j = 0;
for (; j <= 10; j++)
sum = sum + j;
// (2)
int sum = 0;
int j = 5;
for (; j > 0; j--)
sum = sum + j;(1) sum = 55, j = 11. (2) sum = 15, j = 0. (2)의 j--는 후위이므로 현재 값을 더한 뒤 감소합니다. for로 옮길 때는 감소를 증감식 자리로 빼야 합니다. 초기식을 for 안에서 int j = 0으로 선언하면 반복 후 j를 쓸 수 없습니다.
11. 중첩 반복문
바깥 반복이 한 번 돌 때마다 안쪽 반복이 처음부터 끝까지 돕니다. 안쪽 반복의 범위가 바깥 변수에 따라 바뀌면 표를 그려 횟수를 셉니다.
for (int i = 1; i <= 4; i++) { // 줄
for (int j = 1; j <= i; j++) // i번 반복
cout << '*';
cout << '\n';
}*
**
***
****다음 출력을 쓰시오.
// (1)
int cnt = 0;
for (int i = 0; i < 4; i++)
for (int j = i; j < 4; j++)
cnt++;
cout << cnt << '\n';
// (2)
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 3; j++) {
if (j == 2) break;
cout << i << j << ' ';
}정답 확인
(1) 10 — i = 0, 1, 2, 3일 때 안쪽이 4, 3, 2, 1번 돌아 4 + 3 + 2 + 1 = 10.
(2) 11 21 31 — break는 안쪽 반복만 끝냅니다. 바깥 반복은 계속되므로 i마다 j = 1만 출력됩니다.
12. 범위 기반 for
vector<int> v = {1, 2, 3};
for (int x : v) cout << x; // 복사본으로 읽기
for (int& x : v) x *= 2; // 참조로 원본 수정
for (const int& x : v) cout << x; // 복사 없이 읽기만13. 컴파일 오류와 정의되지 않은 동작 구분
시험에서는 "컴파일 오류 / 실행 시 문제(UB) / 정상"을 구분하라고 자주 묻습니다.
| 코드 | 판정 | 이유 |
|---|---|---|
char g = "A"; | 컴파일 오류 | 문자열을 char에 저장할 수 없음 |
int k{3.5}; | 컴파일 오류 | 중괄호 초기화의 축소 변환 금지 |
do { … } while (x) (세미콜론 없음) | 컴파일 오류 | 문법 오류 |
5.5 % 2 | 컴파일 오류 | %는 정수 전용 |
int x; cout << x; | UB | 초기화하지 않은 값 읽기 |
int r = 10 / 0; | UB | 정수를 0으로 나눔 |
int big = 2147483647; big++; | UB | signed 오버플로 |
i++ + ++i | UB | 한 식에서 같은 변수를 두 번 변경 |
unsigned int u = -1; | 정상 | 4294967295로 정해져 있음 |
if (x = 5) | 정상(논리 오류) | 대입식이 항상 참 |
int i = 0; while (i < 5) { } | 정상(무한 반복) | i가 바뀌지 않음 |
핵심 정리
char는 정수 코드다.'A'=65,'a'=97,'0'=48. 산술하면int가 된다.- 정수끼리 나누면 0 쪽으로 절단되고,
%의 부호는 왼쪽 피연산자를 따른다. - 형변환은 나누기 전에 해야 실수 결과가 나온다. 실수 → 정수는 0 쪽으로 버린다.
- signed 오버플로는 UB, unsigned는 돌아간다(0 − 1 = 4294967295). signed/unsigned 비교는 위험하다.
- 우선순위: 단항 → 산술 → 비교 → 같음 →
&&→||→?:→ 대입. 같은 순위는 왼쪽부터(단항·삼항·대입은 오른쪽부터). &&는 왼쪽이 거짓,||는 왼쪽이 참이면 오른쪽을 실행하지 않는다.else는 가장 가까운 짝 없는if와 짝이다.if (x = 0)은 대입이다.switch는break가 없으면 아래case로 계속 실행된다.do while은 최소 한 번 실행된다.continue는for에서는 증감식으로,while에서는 조건으로 간다.break는 가장 안쪽 반복문 하나만 빠져나간다.
내용을 이해했다면 학습 완료를 체크해 주세요.