출제기준 세세항목: 데이터 타입 · 변수 · 연산자
데이터 타입(자료형)은 값을 담는 그릇의 모양과 크기를 정해 줍니다. 국을 담는 그릇과 접시가 다르듯, 정수와 실수와 문자는 메모리에 저장되는 방식이 달라서 그릇도 다르게 써야 합니다. 자료형을 알면 '이 변수에 어떤 값이 들어가고, 계산하면 어떤 결과가 나오는지'를 정확히 예측할 수 있습니다.
C의 기본 자료형은 문자형 char(1바이트), 정수형 short(2바이트)·int(보통 4바이트)·long(4 또는 8바이트, 환경에 따라 다름)·long long(8바이트), 실수형 float(4바이트)·double(8바이트)입니다. C에는 원래 참·거짓 전용 자료형이 없어서 0을 거짓, 0이 아닌 값을 모두 참으로 봅니다. 그래서 if (5)도 참으로 실행됩니다.
Java는 기본 자료형 8가지가 크기까지 정해져 있습니다. byte 1, short 2, int 4, long 8, float 4, double 8, char 2, boolean(true·false)입니다. C와 달리 Java의 char는 2바이트이고, boolean은 숫자로 바꿀 수 없어 if (1)처럼 쓰면 컴파일 오류가 납니다. 기본 자료형이 아닌 String, 배열, 클래스로 만든 객체는 값 대신 객체의 위치를 담는 참조형입니다.
Python은 변수에 자료형을 적지 않고, 값이 들어갈 때 자료형이 정해지는 동적 타이핑 언어입니다. 정수 int(크기 제한 없음), 실수 float, 문자열 str, 논리 bool(True·False)에 더해 list, tuple, dict, set 같은 묶음 자료형을 기본으로 씁니다. 문자는 내부적으로 숫자 코드로 저장된다는 점도 기억해 두세요. ASCII 코드로 'A'는 65, 'a'는 97, 숫자 '0'은 48이며, 대문자와 소문자는 32만큼 차이가 납니다.
변수는 값을 담아 두는 이름 붙은 메모리 공간입니다. 이름은 영문자, 숫자, 밑줄(_)로 짓고(Java는 $도 허용), 첫 글자에 숫자를 쓸 수 없으며, 대문자와 소문자를 다른 글자로 봅니다. if, for, int, class 같은 예약어는 이미 언어가 쓰고 있으므로 이름으로 쓸 수 없습니다. 그래서 2nd나 int는 변수 이름이 될 수 없지만 _count나 score2는 됩니다.
여러 단어로 된 이름을 붙이는 관례도 있습니다. 첫 단어는 소문자로 쓰고 뒤 단어의 첫 글자만 대문자로 쓰는 카멜 표기법(totalScore), 모든 단어의 첫 글자를 대문자로 쓰는 파스칼 표기법(TotalScore), 단어 사이를 밑줄로 잇는 스네이크 표기법(total_score), 이름 앞에 자료형을 뜻하는 접두어를 붙이는 헝가리안 표기법(nScore, strName)이 있습니다. Java는 변수와 메서드에 카멜, 클래스에 파스칼을, Python은 변수와 함수에 스네이크를 주로 씁니다.
변수는 선언한 위치에 따라 쓸 수 있는 범위가 다릅니다. 함수나 중괄호 블록 안에서 선언한 지역 변수는 그 블록 안에서만 쓰이고, 블록이 끝나면 사라집니다. 함수 밖에서 선언한 전역 변수는 프로그램 어디서나 쓰입니다. 같은 이름이 있으면 안쪽(지역)이 바깥쪽(전역)을 가립니다. C에서 지역 변수 앞에 static을 붙이면 처음 한 번만 초기화되고, 함수가 끝나도 값이 남아 다음 호출 때 이어서 쓰입니다. 호출할 때마다 1씩 늘어나는 호출 횟수 세기에 자주 쓰입니다.
초기화도 언어마다 다릅니다. C의 지역 변수는 초기화하지 않으면 알 수 없는 쓰레기 값이 들어 있고, 전역·static 변수는 0으로 시작합니다. Java의 지역 변수는 값을 넣기 전에 쓰면 컴파일 오류가 나지만, 클래스의 필드는 숫자는 0, boolean은 false, 참조형은 null로 자동 초기화됩니다. 값을 바꿀 수 없는 상수는 C에서 const, Java에서 final을 붙여 만듭니다.
자료형이 다른 값끼리 계산하면 한쪽에 맞추는 형 변환이 일어납니다. 작은 그릇의 값을 큰 그릇으로 옮기는 것은 자동으로 일어나며(묵시적 형 변환), int와 double을 계산하면 int가 double로 바뀌어 결과도 double이 됩니다. 반대로 큰 그릇의 값을 작은 그릇에 넣으려면 (int)처럼 직접 적어야 하고(명시적 형 변환, 캐스팅), 이때 소수점 아래는 반올림이 아니라 버려집니다. (int)3.9는 3, (int)-3.9는 -3입니다.
정수끼리의 나눗셈은 결과도 정수라서 몫만 남습니다. 7/2는 3.5가 아니라 3입니다. 그래서 (double)7/2는 7을 먼저 실수로 바꾼 뒤 나누므로 3.5지만, (double)(7/2)는 괄호 안에서 이미 3이 된 뒤 바꾸므로 3.0입니다. 계산 순서도 중요해서 7/2*2.0은 7/2=3이 먼저 계산되어 6.0이 됩니다. 음수가 끼면 C와 Java는 0 쪽으로 버리므로 -7/2는 -3입니다.
나머지 연산자 %의 부호도 언어마다 다릅니다. C와 Java에서는 나머지의 부호가 왼쪽 값(나뉘는 수)을 따라 -7%3은 -1, 7%-3은 1입니다. Python은 몫을 음의 무한대 쪽으로 내리는 //를 쓰고, 나머지의 부호는 오른쪽 값(나누는 수)을 따라 -7//2는 -4, -7%3은 2입니다. 또 Python의 /는 정수끼리여도 항상 실수를 돌려줘서 6/2는 3.0이고, 거듭제곱은 **로 씁니다.
문자와 숫자가 섞일 때도 조심합니다. C와 Java에서 'A'+1은 문자 코드 65에 1을 더한 정수 66이고, 문자로 보려면 (char)('A'+1)처럼 바꿔야 B가 나옵니다. Java에서 문자열과 숫자를 +로 이으면 왼쪽부터 차례로 계산되어 "1"+2+3은 "123"이지만, 1+2+"3"은 앞의 1+2가 먼저 더해져 "33"입니다. 범위를 넘는 값도 주의해야 합니다. Java의 int 최댓값 2147483647에 1을 더하면 오류 없이 최솟값 -2147483648로 넘어갑니다(오버플로).
증감 연산자 ++와 --는 변수 값을 1씩 늘리거나 줄입니다. 위치가 중요합니다. a가 5일 때 b = a++는 지금 값 5를 b에 먼저 넣고 a를 6으로 바꿉니다(후위). b = ++a는 a를 먼저 6으로 바꾼 뒤 그 값을 b에 넣습니다(전위). 혼자 한 줄로 쓰는 a++;와 ++a;는 결과가 같고, 다른 식 안에 섞일 때만 차이가 납니다. Python에는 ++가 없어서 a += 1로 씁니다.
관계 연산자(< > <= >= == !=)는 비교 결과를 참·거짓으로 돌려줍니다. 같은지 비교하는 ==와 값을 넣는 =를 헷갈리지 않아야 합니다. C에서 if (a = 0)이라고 쓰면 a에 0을 넣고 그 값 0을 조건으로 쓰므로 항상 거짓이 됩니다. 대입 연산자는 a += 3(a = a + 3), a *= 2처럼 계산과 대입을 합친 복합 대입으로도 씁니다.
논리 연산자는 C·Java에서 &&(그리고), ||(또는), !(아니다), Python에서 and, or, not입니다. 여기서 꼭 알아야 할 것이 단락 평가(short-circuit)입니다. &&는 왼쪽이 거짓이면 오른쪽을 아예 계산하지 않고, ||는 왼쪽이 참이면 오른쪽을 건너뜁니다. 그래서 if (i != 0 && 10 / i > 1)은 i가 0이어도 나눗셈을 하지 않아 안전하고, 오른쪽에 i++ 같은 식이 있으면 경우에 따라 실행되지 않을 수도 있습니다. Python의 and·or는 참·거짓 대신 마지막으로 확인한 값을 돌려주어 0 or 5는 5, 3 and 0은 0입니다.
삼항 연산자는 조건에 따라 두 값 중 하나를 고르는 연산자로, C·Java에서는 조건 ? 참일 때 값 : 거짓일 때 값 모양입니다. max = a > b ? a : b는 a와 b 중 큰 값을 max에 넣습니다. Python은 순서가 달라서 값1 if 조건 else 값2로 씁니다.
비트 연산자는 값을 2진수로 놓고 자리마다 계산합니다. &(AND)는 둘 다 1일 때만 1, |(OR)는 하나라도 1이면 1, ^(XOR)는 서로 다를 때만 1, ~(NOT)는 0과 1을 뒤집습니다. 12는 1100, 10은 1010이므로 12 & 10은 1000(8), 12 | 10은 1110(14), 12 ^ 10은 0110(6)입니다. ~x는 음수를 2의 보수로 나타내는 방식 때문에 -(x+1)이 되어 ~12는 -13입니다.
시프트 연산자는 비트를 왼쪽이나 오른쪽으로 밉니다. x << n은 x에 2의 n제곱을 곱한 것과 같고, x >> n은 2의 n제곱으로 나눈 몫(내림)과 같습니다. 5 << 3은 40, 20 >> 2는 5입니다. Java에는 왼쪽 빈자리를 부호와 상관없이 0으로 채우는 >>> 연산자도 있습니다.
여러 연산자가 한 식에 섞이면 우선순위대로 계산합니다. C·Java 기준으로 높은 것부터 단항(++ -- ! ~ 형 변환), 곱셈류(* / %), 덧셈류(+ -), 시프트(<< >>), 관계(< <= > >=), 등가(== !=), 비트 AND(&), 비트 XOR(^), 비트 OR(|), 논리 AND(&&), 논리 OR(||), 삼항(?:), 대입(= += 등) 순입니다. 우선순위가 같으면 대부분 왼쪽부터 계산하지만, 단항·삼항·대입은 오른쪽부터 계산합니다. 그래서 10 - 4 - 3은 3이고, a = b = 5는 b에 5를 넣은 뒤 a에 넣습니다.
자주 틀리는 함정이 있습니다. C에서 x & 1 == 0은 ==가 &보다 먼저라서 x & (1 == 0), 즉 x & 0이 되어 x가 무엇이든 0(거짓)입니다. 반면 Python은 비트 연산자가 비교 연산자보다 먼저라서 같은 식이 (x & 1) == 0으로 계산됩니다. Python은 1 < x < 5처럼 비교를 이어 쓰면 '1 < x 그리고 x < 5'로 해석하지만, C에서 1 < 3 < 2는 앞의 1 < 3이 1(참)이 된 뒤 1 < 2를 계산해 참이 됩니다. 또 Python의 **는 단항 -보다 먼저라서 -2**2는 -4이고, 오른쪽부터 계산해 2**3**2는 2**9=512입니다. 헷갈리면 괄호를 쓰는 것이 가장 안전합니다.
if 문은 조건이 참일 때만 실행하고, else if(Python은 elif)로 여러 조건을 위에서부터 차례로 확인합니다. 먼저 참이 된 한 곳만 실행되고 나머지는 건너뛴다는 점이 중요합니다. 점수가 95일 때 if (s >= 60) ... else if (s >= 90) ...이라면 첫 조건에서 이미 걸리므로 두 번째는 확인하지도 않습니다.
switch 문은 값에 맞는 case로 바로 뛰어간 뒤, break를 만날 때까지 아래 case를 계속 실행합니다. 이것을 fall-through라고 하며, break를 빠뜨린 코드를 추적하는 문제가 자주 나옵니다. 맞는 case가 없으면 default가 실행됩니다. Java는 switch에 문자열도 쓸 수 있습니다. Python에는 오랫동안 switch가 없었고, 3.10부터 match 문이 생겼습니다.
for 문은 for (초기식; 조건식; 증감식)의 모양이며, 초기식은 처음 한 번, 그다음은 조건 확인 → 본문 실행 → 증감식이 반복됩니다. for (i = 0; i < 5; i++)가 끝나면 i는 5입니다. while은 조건을 먼저 보고 실행하므로 한 번도 안 돌 수 있지만, do-while은 본문을 먼저 실행한 뒤 조건을 보므로 최소 한 번은 실행됩니다. Python의 for는 range(시작, 끝, 간격)로 범위를 만들며 끝 값은 포함하지 않습니다. range(1, 10, 3)은 1, 4, 7이고, range(10, 0, -3)은 10, 7, 4, 1입니다.
break는 반복문을 즉시 빠져나가고, continue는 이번 반복의 남은 부분만 건너뛰고 다음 반복으로 갑니다. 반복문이 겹쳐 있을 때 break는 자기가 들어 있는 가장 안쪽 반복문 하나만 빠져나옵니다. 코드 추적 문제에서는 변수 값이 바뀔 때마다 표로 적어 가면 실수가 줄어듭니다.
배열은 같은 자료형의 값을 줄지어 담는 그릇이며, 칸 번호(인덱스)는 0부터 셉니다. 길이가 5인 배열은 a[0]부터 a[4]까지이고 a[5]는 없습니다. 범위를 벗어나면 Java는 ArrayIndexOutOfBoundsException, Python은 IndexError가 나지만, C는 검사하지 않아 엉뚱한 메모리를 읽거나 덮어쓸 수 있습니다. 길이는 C에서 sizeof(a) / sizeof(a[0]), Java 배열은 a.length(괄호 없음), Java 문자열은 s.length()(괄호 있음), Python은 len(a)로 구합니다.
2차원 배열 a[i][j]는 i가 행, j가 열입니다. int a[2][3] = {1, 2, 3, 4, 5, 6};이면 첫 행이 1, 2, 3이고 둘째 행이 4, 5, 6이라 a[1][0]은 4입니다. 메모리에는 행 단위로 이어서 저장됩니다. Java는 행마다 길이가 다른 2차원 배열도 만들 수 있습니다.
C의 문자열은 문자 배열 끝에 널 문자 '\0'을 붙여 끝을 표시합니다. 그래서 char s[] = "hello";는 다섯 글자에 널 문자까지 6칸을 차지하며, sizeof(s)는 6이지만 strlen(s)는 널 문자를 빼고 센 5입니다. Python은 음수 인덱스로 뒤에서부터 셀 수 있어서 s[-1]은 마지막 글자입니다.
함수는 입력(매개변수)을 받아 일을 하고 결과를 돌려주는(return) 코드 묶음입니다. 함수를 부를 때 넘기는 값을 인자, 함수가 받는 변수를 매개변수라고 합니다. 함수가 자기 자신을 다시 부르는 것을 재귀라고 하는데, 반드시 멈추는 조건(기저 조건)이 있어야 합니다. 예를 들어 f(n)이 n이 1 이하면 1을, 아니면 n * f(n - 1)을 돌려준다면 f(4)는 4 * 3 * 2 * 1 = 24입니다. 재귀 문제는 가장 깊은 호출부터 값이 정해져 거꾸로 올라온다는 점을 생각하며 풀면 됩니다.
학생 성적 평균을 int 합계 / int 인원으로 계산했더니 85.7점이어야 할 평균이 85점으로 나왔습니다. 정수 나눗셈 때문이었고, (double)sum / count로 한쪽을 먼저 실수로 바꿔 해결했습니다.
회원 등급별 할인율을 switch 문으로 정했는데, 실버 등급 case에 break가 빠져 있어 실버 회원에게도 골드 할인이 적용되었습니다. 코드 리뷰 체크리스트에 'switch의 각 case 끝에 break가 있는지'를 넣었습니다.
오늘로부터 며칠 전의 요일을 (오늘 요일 - 일수) % 7로 구했더니, Java에서는 결과가 음수가 되어 요일 배열 인덱스 오류가 났습니다. 같은 식이 Python에서는 문제없었던 것은 나머지 부호 규칙이 달랐기 때문이며, Java에서는 ((오늘 - 일수) % 7 + 7) % 7로 고쳤습니다.