자료형과 연산자, 형변환
C·Java·Python의 자료형, 연산자 우선순위, 형변환과 오버플로를 코드 판독 중심으로 학습한다.
1. 자료형이 필요한 이유
자료형은 값의 표현 범위, 저장 방식, 허용 연산, 메모리 사용량을 결정한다. 같은 비트열도 자료형에 따라 정수, 실수, 문자, 주소 등으로 다르게 해석된다.
소스의 리터럴·변수
│
▼
자료형 결정
│
├─ 저장 크기와 표현 범위
├─ 가능한 연산
├─ 형변환 규칙
└─ 결과 자료형
다음 경계를 구분한다.
- 정수와 실수 연산 결과
- 정수 나눗셈과 나머지
- 암시적·명시적 형변환
- 부호 있는 정수와 부호 없는 정수
- 연산자 우선순위와 단락 평가
- 오버플로·정밀도 손실
2. C·Java·Python의 자료형 비교
| 구분 | C | Java | Python |
|---|---|---|---|
| 정수 | char, short, int, long 등 | byte, short, int, long | int |
| 실수 | float, double 등 | float, double | float |
| 문자 | 정수형 문자 코드로 처리 | char는 UTF-16 코드 단위 | 길이 1인 str |
| 논리 | C99 이후 _Bool, 일반식에서는 0/비0 | boolean | bool |
| 문자열 | 문자 배열 또는 포인터 | String 객체 | str 객체 |
| 타입 결정 | 정적 타입 | 정적 타입 | 동적 타입 |
C의 정수 자료형 크기는 표준이 최소 범위와 순서 관계를 정하고, 실제 크기는 구현에 따라 달라질 수 있다. Java의 기본 정수형 크기는 언어 사양에서 고정되어 있다. Python의 정수는 일반적인 기계어 정수보다 큰 값까지 표현할 수 있지만 메모리 한계는 존재한다.
3. 정수 표현과 오버플로
n비트 부호 없는 정수의 범위는 0 ~ 2^n - 1이다. 일반적인 2의 보수 기반 n비트 부호 있는 정수의 범위는 -2^(n-1) ~ 2^(n-1)-1이다.
8비트 unsigned: 0 ~ 255
8비트 signed : -128 ~ 127
Java의 정수 연산은 정해진 비트 폭을 넘어가면 하위 비트만 남는 방식으로 순환한다. 반면 C에서 부호 있는 정수 오버플로는 정의되지 않은 동작이므로 특정 결과가 반드시 나온다고 가정하면 안 된다. 부호 없는 정수는 모듈러 연산처럼 순환한다.
int x = Integer.MAX_VALUE;
System.out.println(x + 1); // -2147483648
Python의 int는 고정 폭이 아니므로 같은 방식의 정수 오버플로는 일반적으로 발생하지 않는다.
4. 실수 표현과 정밀도
부동소수점은 제한된 비트로 실수를 근사한다. 따라서 10진 소수 중 일부는 2진 부동소수점으로 정확히 표현할 수 없다.
print(0.1 + 0.2 == 0.3) # False가 될 수 있다.
실수 비교에서는 문제 목적에 따라 허용 오차를 사용한다.
a = 0.1 + 0.2
print(abs(a - 0.3) < 1e-9) # True
5. 연산자 종류와 우선순위
| 종류 | 대표 연산자 | 핵심 |
|---|---|---|
| 산술 | + - * / % | 정수·실수 자료형에 따라 결과가 달라진다. |
| 비교 | < <= > >= == != | 결과는 참·거짓이다. |
| 논리 | `&& | |
| 비트 | `& | ^ ~ << >>` |
| 대입 | = += -= *= | 오른쪽 계산 후 왼쪽에 저장한다. |
| 조건 | 조건 ? 참값 : 거짓값 | C·Java의 삼항 연산자이다. |
일반적인 우선순위의 큰 흐름은 다음과 같다.
괄호·호출
↓
단항 연산
↓
곱셈·나눗셈·나머지
↓
덧셈·뺄셈
↓
이동 연산
↓
비교·동등
↓
비트 연산
↓
논리 AND·OR
↓
조건·대입
괄호로 의도를 명확히 표현하고, 괄호가 없는 식은 단계별로 분해해 평가 순서를 확인한다.
6. 정수 나눗셈과 나머지
int a = 7 / 2; // 3
double b = 7 / 2; // 3.0: 나눗셈이 먼저 정수로 수행됨
double c = 7 / 2.0; // 3.5
int x = 7 / 2; // 3
double y = (double) 7 / 2; // 3.5
print(7 / 2) # 3.5: 일반 나눗셈
print(7 // 2) # 3: 몫 연산
print(7 % 2) # 1
음수의 몫과 나머지는 언어 규칙이 다를 수 있으므로 언어를 확인해야 한다. Python의 //는 음의 무한대 방향으로 내림한다.
7. 형변환
형변환은 값의 자료형을 다른 자료형으로 바꾸는 과정이다.
피연산자 자료형 확인
│
▼
연산을 위한 승격
│
▼
연산 결과 생성
│
▼
대입 대상형으로 변환
- 확대 변환: 표현 범위가 넓은 형으로 이동한다. 일반적으로 정보 손실 가능성이 작다.
- 축소 변환: 표현 범위가 좁은 형으로 이동한다. 값 손실이나 잘림 가능성이 있다.
- 명시적 변환: 프로그래머가 캐스트를 작성한다.
- 암시적 변환: 언어 규칙이 자동으로 변환한다.
int i = 10;
double d = i; // 확대 변환
int n = (int) 3.9; // 3, 소수부 절삭
double d = 3.9;
int n = (int)d; // 일반적으로 3
Python은 변수 자체의 고정 자료형을 바꾸는 것이 아니라 변환 함수가 새 객체를 만든다.
n = int("42")
s = str(42)
f = float("3.5")
8. 단락 평가
논리식은 결과가 이미 확정되면 뒤의 피연산자를 평가하지 않을 수 있다.
if (p != NULL && p->value > 0) {
/* p가 NULL이면 오른쪽을 평가하지 않는다. */
}
items = []
if items and items[0] > 0:
print("positive")
단락 평가는 안전한 조건 검사에 활용되지만, 오른쪽 식의 부수효과에 의존하면 코드를 이해하기 어려워진다.
9. 코드 판독 절차
① 변수의 선언 자료형 표시
② 리터럴 자료형 확인
③ 괄호와 우선순위대로 부분식 계산
④ 암시적 승격·형변환 적용
⑤ 정수 나눗셈·오버플로·정밀도 확인
⑥ 대입 후 최종 자료형과 값 기록
10. 정수 승격과 혼합 연산
작은 정수형은 연산 전에 더 큰 정수형으로 승격될 수 있다. Java에서 byte, short, char의 산술 결과는 일반적으로 int가 된다.
byte a = 10, b = 20;
// byte c = a + b; // 컴파일 오류: a+b의 형은 int
int c = a + b; // 30
복합 대입은 연산 뒤 대상형으로의 암시적 축소 변환을 포함한다.
byte x = 120;
x += 10; // (byte)(x + 10)과 유사, 결과 -126
C에서도 char와 short는 정수 승격을 거친다. 부호 있는 형과 부호 없는 형을 섞으면 예상과 다른 비교 결과가 나올 수 있으므로, 문제에서 비트 폭과 형을 먼저 표시해야 한다.
11. 비트 이동과 마스크
비트 k 설정: x | (1 << k)
비트 k 해제: x & ~(1 << k)
비트 k 검사: (x & (1 << k)) != 0
비트 k 반전: x ^ (1 << k)
Java의 >>는 부호를 유지하는 산술 이동이고, >>>는 왼쪽을 0으로 채우는 논리적 오른쪽 이동이다. C에서는 음수인 부호 있는 정수의 이동과 표현 범위 밖 왼쪽 이동이 이식성 문제나 정의되지 않은 동작을 만들 수 있으므로 비트 연산에는 부호 없는 형을 쓰는 것이 안전하다.
System.out.println(-8 >> 1); // -4
System.out.println(-8 >>> 1); // 2147483644
12. 평가 순서와 부수효과
같은 식 안에서 한 변수를 여러 번 변경하면 언어별 규칙이 크게 다르다.
int i = 1;
int y = i++ + ++i; // Java는 피연산자를 왼쪽부터 평가: y=4, i=3
C에서 i++ + ++i처럼 순서가 정해지지 않은 변경을 겹치면 정의되지 않은 동작이므로 특정 출력값을 계산하지 않는다. 코드를 해석할 때는 언어가 무엇인지, 평가 순서가 규정되어 있는지, 한 변수의 변경과 읽기가 충돌하는지를 먼저 확인한다.
13. 형변환 경계 판독
| 표현 | 결과 원리 |
|---|---|
Java (int)-3.9 | 0 방향으로 소수부를 버려 -3 |
Python int(-3.9) | 0 방향으로 변환해 -3 |
Python math.floor(-3.9) | 음의 무한대 방향으로 내려 -4 |
Java byte b=(byte)130 | 하위 8비트만 남아 -126 |
실수에서 정수로 바꾸는 규칙과 정수 나눗셈의 규칙은 별개다. 또한 C에서 표현할 수 없는 실수를 정수형으로 변환하는 상황은 이식 가능한 단일 결과로 단정하지 않는다.
확인 문제
- Java에서
byte a=10, b=20;일 때a+b의 결과 자료형은 무엇인가? - Java에서
int x=-8;일 때x >>> 1의 값은 무엇인가? - 정수 x의 k번째 비트를 0으로 만드는 일반적인 비트식은 무엇인가?
- Java에서
(int)-3.9와 Python에서math.floor(-3.9)의 결과는 각각 무엇인가? - C에서
i++ + ++i의 결과를 특정 값으로 계산하면 안 되는 이유는 무엇인가?