티스토리 뷰
C언어 기초 다지기, 2019년 중간고사 문제 해설
안녕하세요, 코딩 꿈나무 여러분! 오늘은 C언어 기초를 탄탄하게 다질 수 있는 시간을 가져보려고 해요. 2019학년도 2학기 기초 프로그래밍2 중간고사에 나왔던 문제들을 함께 풀어보면서 중요한 개념들을 복습해 봅시다. 코딩이 처음이라도 괜찮아요! 차근차근 설명해 드릴 테니 잘 따라오세요!
목차
- 코드의 결과는? - 반복문과 조건문 (문제 1-가)
- 함수와 변수의 세계 - 변수의 범위 (문제 1-나)
- 포인터가 뭐길래? - 주소와 값 (문제 1-다)
- 소문자를 대문자로! - 함수 만들기 (문제 2)
- 값이 변하지 않는 이유? - 값에 의한 호출 (문제 3)
- 핵심 개념 정리표
코드의 결과는? - 반복문과 조건문 (문제 1-가)
먼저 첫 번째 코드의 출력 결과를 맞춰볼까요?
for (i = 2; i < 12; i = i + 2) {
if (i % 4 == 0) continue;
printf("%d, ", i);
if (i % 10 == 0) break;
}
이 코드는 `for` 반복문을 사용해서 변수 `i`를 2부터 시작해서 2씩 증가시키면서 12보다 작을 때까지 반복해요. 즉, `i`는 2, 4, 6, 8, 10 순서로 변하게 되죠.
- `i`가 2일 때: `i % 4 == 0` (2를 4로 나눈 나머지)는 거짓이죠. 그래서 `continue`를 건너뛰고 `printf("%d, ", i);`를 실행해서 "2, "가 출력돼요. `i % 10 == 0`도 거짓이니 `break`도 건너뜁니다.
- `i`가 4일 때: `i % 4 == 0` (4를 4로 나눈 나머지)는 참이에요! `continue`를 만나면 이번 반복은 여기서 멈추고 바로 다음 반복으로 넘어가요. 그래서 `printf`는 실행되지 않아요.
- `i`가 6일 때: `i % 4 == 0`은 거짓. `printf`를 실행해서 "6, "이 출력돼요. `i % 10 == 0`은 거짓.
- `i`가 8일 때: `i % 4 == 0`은 참. `continue`를 만나 `printf`는 실행되지 않아요.
- `i`가 10일 때: `i % 4 == 0`은 거짓. `printf`를 실행해서 "10, "이 출력돼요. 그런데 이번에는 `i % 10 == 0` (10을 10으로 나눈 나머지)가 참이네요! `break`를 만나면 반복문을 완전히 빠져나와요.
따라서 최종 출력 결과는 2, 6, 10, 이 됩니다!
여기서 `continue`는 현재 반복을 중단하고 다음 반복으로 넘어가는 것, `break`는 반복문 자체를 완전히 끝내는 것이라는 차이점을 기억하는 게 중요해요!
함수와 변수의 세계 - 변수의 범위 (문제 1-나)
다음 코드의 결과를 예측해 봅시다.
int num = 10;
void fun1() { int num = 20; printf("%d\n", num); }
void fun2() { printf("%d\n", num); num = 40; }
int main() {
int num = 30;
fun1();
printf("%d\n", num);
fun2();
fun2();
}
이 코드는 변수의 '범위(scope)'에 대한 문제예요. 같은 이름의 변수 `num`이 여러 곳에 선언되어 있죠?
- 전역 변수 `num` (값 10): 함수 바깥 맨 위에 선언된 `num`이에요. 프로그램 전체에서 사용할 수 있지만, 같은 이름의 지역 변수가 있으면 그 지역 변수에 가려져요.
- `fun1` 함수의 지역 변수 `num` (값 20): `fun1` 함수 안에서만 사용되는 `num`이에요.
- `main` 함수의 지역 변수 `num` (값 30): `main` 함수 안에서만 사용되는 `num`이에요.
이제 `main` 함수를 따라가 봅시다.
- `fun1();` 호출: `fun1` 함수 안으로 들어가요. `fun1` 안에는 지역 변수 `num` (값 20)이 있죠? `printf("%d\n", num);`는 이 지역 변수 `num`의 값인 20을 출력해요. `fun1` 함수가 끝나면 이 지역 변수 `num`은 사라져요.
- `printf("%d\n", num);` 실행: 다시 `main` 함수로 돌아왔어요. 여기서 `num`은 `main` 함수의 지역 변수 `num` (값 30)을 가리켜요. 따라서 30이 출력돼요.
- `fun2();` 첫 번째 호출: `fun2` 함수 안으로 가요. `fun2` 안에는 `num`이라는 이름의 지역 변수가 없죠? 이럴 때는 가장 가까운 바깥 범위의 변수, 즉 전역 변수 `num` (값 10)을 사용해요. `printf("%d\n", num);`는 전역 변수 `num`의 값인 10을 출력해요. 그리고 `num = 40;`을 실행해서 전역 변수 `num`의 값을 40으로 바꿔요!
- `fun2();` 두 번째 호출: 다시 `fun2` 함수로 가요. 이번에도 전역 변수 `num`을 사용하는데, 이전 호출에서 값이 40으로 바뀌었죠? 그래서 `printf("%d\n", num);`는 40을 출력해요. 그리고 `num = 40;`을 또 실행하지만, 이미 40이니까 값은 그대로 40이에요.
따라서 최종 출력 결과는 줄바꿈으로 구분되어 20 30 10 40 이 됩니다!
지역 변수는 그 함수 안에서만 유효하고, 지역 변수가 없을 때는 전역 변수를 사용한다는 점을 꼭 기억하세요!
포인터가 뭐길래? - 주소와 값 (문제 1-다)
포인터가 등장했네요! 다음 코드의 출력은 무엇일까요?
int num = 1;
int *p = #
printf("%d, %p, %p, %d\n", num, &num, p, *p);
포인터는 메모리의 '주소'를 저장하는 특별한 변수예요.
- `int num = 1;`: 정수형 변수 `num`을 만들고 값 1을 저장했어요.
- `int *p = #`: 정수형 포인터 변수 `p`를 만들고, `num` 변수의 메모리 주소(`&num`)를 저장했어요. `&`는 변수의 주소를 알아내는 연산자예요.
- `printf(...)`: 이제 출력할 값들을 하나씩 봅시다.
- `num`: 변수 `num`의 값인 1이 출력돼요.
- `&num`: 변수 `num`의 메모리 주소가 출력돼요. 이건 컴퓨터마다 다르지만, 어떤 메모리 주소 값(예: 0x7ffeeabc1234)이 출력될 거예요. `%p`는 주소를 출력하는 서식 지정자예요.
- `p`: 포인터 변수 `p`에 저장된 값, 즉 `num`의 주소가 출력돼요. 따라서 바로 앞의 `&num`과 같은 메모리 주소 값이 출력돼요.
- `*p`: 포인터 변수 `p`가 가리키는 주소에 저장된 '값'을 가져오는 연산자(`*`)예요. `p`는 `num`의 주소를 가리키고 있으니, `*p`는 `num`의 값인 1이 출력돼요.
따라서 출력 결과는 1, [어떤 주소], [같은 주소], 1 형태가 될 거예요. (실제 주소 값은 실행 환경마다 다릅니다.)
포인터는 변수의 주소를 저장하고(`p = &num`), 그 주소를 통해 원래 변수의 값에 접근(`*p`)할 수 있게 해준다는 점이 핵심이에요!
소문자를 대문자로! - 함수 만들기 (문제 2)
키보드로 영문 소문자 하나를 입력받아서 대문자로 바꿔 출력하는 프로그램을 만드는 문제예요. `upper`라는 함수를 사용하라고 되어 있네요.
// 주어진 upper 함수
char upper(char ch) {
return ch - ('a' - 'A');
}
// main 함수 완성하기
int main() {
char ch;
printf("문자: ");
scanf("%c", &ch); // 키보드로부터 문자 하나를 입력받아 ch 변수에 저장
// 입력받은 문자가 소문자인지 확인
if (ch >= 'a' && ch <= 'z') {
// 소문자라면 upper 함수를 호출하여 대문자로 변환하고 출력
printf("%c ==> %c\n", ch, upper(ch));
} else {
// 소문자가 아니라면 변환할 수 없다고 알려주기 (선택 사항)
printf("입력한 문자는 소문자가 아닙니다.\n");
}
return 0;
}
먼저 `scanf("%c", &ch);`를 사용해서 사용자로부터 문자 하나를 입력받아요. `%c`는 문자를 입력받는 서식 지정자고, 변수 이름 앞에 `&`를 붙여서 입력받은 값을 `ch` 변수의 메모리 주소에 저장하라는 뜻이에요.
그 다음 `if (ch >= 'a' && ch <= 'z')` 조건문으로 입력받은 문자가 소문자인지 확인해요. C언어에서 문자는 내부적으로 숫자로 처리되기 때문에 이렇게 비교할 수 있어요.
만약 소문자가 맞다면, `upper(ch)` 함수를 호출해요. `upper` 함수는 소문자(`ch`)와 대문자의 아스키 코드 값 차이('a' - 'A')를 이용해서 대문자로 변환한 값을 돌려줘요. 예를 들어 'a'가 들어오면 'a' - ('a' - 'A') = 'A'가 되는 원리죠. 그리고 `printf`를 이용해 원래 소문자와 변환된 대문자를 함께 출력해요.
만약 소문자가 아니라면, 변환하지 않고 그냥 넘어갈 수도 있고, 위 코드처럼 안내 메시지를 출력해 줄 수도 있어요.
값이 변하지 않는 이유? - 값에 의한 호출 (문제 3)
마지막 문제는 `increment` 함수를 호출했는데 왜 `main` 함수의 `num` 값이 변하지 않았는지 설명하는 거예요.
int increment(int num) {
return ++num; // 함수 안의 num 값을 1 증가시키고 그 값을 반환
}
int main() {
int num = 10;
increment(num); // 함수 호출
printf("%d\n", num); // main 함수의 num 값 출력
return 0;
}
C언어에서 함수를 호출할 때 인자를 전달하는 방식은 기본적으로 '값에 의한 호출 (call by value)'이에요. 이게 무슨 말이냐면, `increment(num);` 이렇게 함수를 호출할 때 `main` 함수의 `num` 값인 10 '자체'가 넘어가는 게 아니라, 10이라는 '값'이 복사되어서 `increment` 함수의 매개변수 `num`에 전달된다는 뜻이에요.
그래서 `increment` 함수 안에서 `++num`을 실행하면, 복사된 값 10이 11로 바뀌는 것이지, `main` 함수에 있는 원래 `num` 변수의 값이 바뀌는 게 아니에요. `increment` 함수가 끝나면 복사되었던 값은 사라지고, `main` 함수의 `num`은 여전히 원래 값인 10을 가지고 있는 거죠. 그래서 `printf`로 출력하면 10이 나오는 거예요.
만약 `main` 함수의 `num` 값을 바꾸고 싶다면, `increment` 함수가 반환하는 값(증가된 값)을 다시 `main` 함수의 `num`에 저장해 줘야 해요. 이렇게요:
num = increment(num); // increment 함수가 반환한 값을 num에 저장
printf("%d\n", num); // 이제 11이 출력될 거예요!
또는 포인터를 사용해서 함수가 직접 `main` 함수의 변수 주소에 접근하게 하는 방법도 있답니다. (이건 나중에 더 자세히 배울 거예요!)
오늘 C언어 중간고사 문제를 풀어보면서 반복문, 조건문, 변수의 범위, 포인터, 함수 호출 방식 등 중요한 개념들을 복습해 봤어요. 꾸준히 연습하면 코딩 실력이 쑥쑥 늘 거예요! 화이팅!
개념 | 설명 | 관련 코드/키워드 |
---|---|---|
반복문 제어 | `continue`: 현재 반복 중단 후 다음 반복 진행. `break`: 반복문 전체를 즉시 종료. |
`for`, `while`, `continue`, `break` |
변수의 범위 (Scope) | 변수가 유효한 영역. 지역 변수는 함수 내에서만, 전역 변수는 프로그램 전체에서 (단, 지역 변수에 가려질 수 있음). | 지역 변수 (함수 내 선언), 전역 변수 (함수 밖 선언) |
포인터 | 메모리 주소를 저장하는 변수. 주소를 통해 값에 접근 가능. | `*` (포인터 선언, 값 접근), `&` (주소 연산자), `%p` (주소 출력) |
함수 인자 전달 | 값에 의한 호출 (Call by Value): 인자의 '값'이 복사되어 전달됨. 함수 내에서 매개변수 값을 변경해도 원본 변수에는 영향 없음. | 함수 호출 시 인자 전달 방식 |
문자 처리 | 문자는 내부적으로 숫(아스키 코드)로 처리되어 비교 및 연산 가능. | `char`, `'a'`, `'A'`, `scanf("%c", ...)` |