정보처리기사 필기 공부 기록 · 3일차 · p.4-38~p.4-55
정처기 3일차 수제비: 명령문·함수·구조체·클래스 총정리
3일차 범위는 명령문, 사용자 정의 함수, 구조체, 클래스입니다. 조건과 반복으로 실행 흐름을 만들고, 반복되는 로직은 함수로 묶으며, 관련 데이터는 구조체로, 상태와 동작은 클래스로 설계하는 순서로 이어집니다.
시험에서는 반복문의 실행 결과, break와 continue, 함수의 값 전달, s1.age와 p->age, 생성자 문법이 자주 헷갈립니다. 이 글에서는 코드가 실행되는 순서를 초보자 눈높이에서 하나씩 따라가 보겠습니다.
.은 변수, ->는 포인터
오늘의 한 줄 지도: 명령문은 흐름, 함수는 재사용, 구조체는 데이터 묶음, 클래스는 상태와 동작의 설계도다.
3일차 학습 순서
- 조건문·반복문과 흐름 제어
- 사용자 정의 함수·값 전달·재귀 함수
- 배열과 구조체의 차이
- 구조체 선언과 구조체 포인터
- 클래스·객체·인스턴스의 관계
- Java와 Python의 클래스 및 생성자
this와self비교- 절차지향과 객체지향 비교
1. 명령문: 프로그램의 실행 흐름 만들기
명령문은 컴퓨터에 “무엇을 실행하라”고 지시하는 문장입니다. 프로그램은 기본적으로 위에서 아래로 실행되지만, 조건문을 만나면 실행할 길을 선택하고 반복문을 만나면 같은 작업을 여러 번 수행합니다.
| 종류 | 대표 문법 | 역할 |
|---|---|---|
| 순차문 | 대입문·함수 호출문 | 작성된 순서대로 실행 |
| 선택문 | if, if-else, switch |
조건에 따라 실행할 문장을 선택 |
| 반복문 | for, while, do-while |
조건을 만족하는 동안 같은 작업을 반복 |
| 흐름 제어문 | break, continue, return |
반복·선택·함수의 흐름을 중간에 변경 |
조건문: 조건이 참인지 판단하기
int score = 85;
if (score >= 90) {
printf("A");
} else if (score >= 80) {
printf("B");
} else {
printf("C");
}
// 출력: B
if의 조건이 거짓이면 다음 else if를 검사합니다. 여러 조건이 참이더라도 위에서 처음 만난 참인 블록 하나만 실행합니다.
switch와 break 함정
int n = 2;
switch (n) {
case 1: printf("A"); break;
case 2: printf("B");
case 3: printf("C"); break;
default: printf("D");
}
// 출력: BC
case 2 뒤에 break가 없기 때문에 다음 case 3의 문장까지 이어서 실행합니다. 이를 폴스루(fall-through)라고 합니다. 시험에서는 각 case의 끝에 break가 있는지 먼저 표시해 두면 실수를 줄일 수 있습니다.
반복문 세 가지 비교
| 반복문 | 조건 검사 | 핵심 특징 |
|---|---|---|
for |
실행 전 | 반복 횟수가 비교적 분명할 때 편리 |
while |
실행 전 | 처음부터 조건이 거짓이면 한 번도 실행하지 않음 |
do-while |
실행 후 | 조건이 거짓이어도 본문을 최소 한 번 실행 |
int sum = 0;
for (int i = 1; i <= 5; i++) {
if (i == 3) continue;
if (i == 5) break;
sum += i;
}
printf("%d", sum); // 1 + 2 + 4 = 7
| 문장 | 무엇을 끝내거나 건너뛰나? |
|---|---|
break |
가장 가까운 반복문 또는 switch를 즉시 종료 |
continue |
현재 반복의 남은 문장만 건너뛰고 다음 반복으로 이동 |
return |
현재 함수 자체를 끝내고 호출한 곳으로 복귀 |
range()의 끝값은 포함하지 않는다
range(1, 5)는 1부터 4까지 만듭니다. 시험에서 반복 횟수를 셀 때 마지막 값 5를 포함하지 않는다는 점을 기억하세요.
for i in range(1, 5):
print(i, end=" ")
# 출력: 1 2 3 4
C에는 지정한 레이블로 바로 이동하는 goto문도 있습니다. 문법 문제에서는 알아두되, 실행 흐름을 따라가기 어려워질 수 있으므로 일반적인 코드에서는 신중하게 사용합니다.
2. 사용자 정의 함수: 반복 작업에 이름 붙이기
함수는 특정 작업을 수행하는 코드 묶음입니다. printf()처럼 언어와 라이브러리가 제공하는 함수도 있지만, 필요한 기능을 직접 만든 것을 사용자 정의 함수라고 합니다. 함수를 사용하면 같은 코드를 반복해서 쓰지 않아도 되고, 프로그램을 작은 역할 단위로 나눌 수 있습니다.
| 구성 요소 | 예시 | 의미 |
|---|---|---|
| 반환형 | int |
함수가 돌려주는 값의 자료형 |
| 함수명 | add |
함수를 호출할 때 사용하는 이름 |
| 매개변수 | int a, int b |
호출할 때 전달된 값을 받는 변수 |
| 함수 본문 | { return a + b; } |
실제로 수행할 문장과 반환값 |
함수 원형 선언 → 호출 → 정의
#include <stdio.h>
int add(int a, int b); // 1. 함수 원형 선언
int main(void) {
int result = add(3, 5); // 2. 함수 호출
printf("%d\n", result); // 출력: 8
return 0;
}
int add(int a, int b) { // 3. 함수 정의
return a + b;
}
함수 원형은 컴파일러에게 “이런 이름과 형태의 함수가 뒤에 나온다”고 미리 알려 줍니다. 함수를 main()보다 앞에서 정의했다면 별도의 원형 선언을 생략할 수 있습니다.
add(3, 5)의 3과 5는 호출하는 쪽에서 보내는 인수이고, int add(int a, int b)의 a와 b는 함수가 값을 받는 매개변수입니다.
매개변수와 반환값의 네 가지 형태
| 매개변수 | 반환값 | C 함수 예시 |
|---|---|---|
| 없음 | 없음 | void show(void) |
| 있음 | 없음 | void print_sum(int a, int b) |
| 없음 | 있음 | int get_number(void) |
| 있음 | 있음 | int add(int a, int b) |
C의 값 전달과 포인터 전달
#include <stdio.h>
void change_value(int n) {
n = 99;
}
void change_address(int *n) {
*n = 99;
}
int main(void) {
int a = 10;
int b = 10;
change_value(a);
change_address(&b);
printf("%d %d\n", a, b); // 10 99
return 0;
}
change_value(a)는 a의 값 10을 복사해서 보내므로 함수 안에서 n을 바꿔도 원본 a는 그대로입니다. 반면 change_address(&b)는 b의 주소를 보내고 *n으로 그 주소의 값을 바꾸므로 원본 b가 99가 됩니다.
포인터를 매개변수로 넘길 때도 함수가 받는 것은 주소값의 복사본입니다. 다만 그 주소를 역참조하면 호출한 쪽의 실제 데이터에 접근할 수 있어, 교재에서는 이를 주소에 의한 호출 또는 참조 효과라고 설명하기도 합니다.
재귀 함수는 종료 조건부터 확인하기
재귀 함수는 자기 자신을 다시 호출하는 함수입니다. 반복문처럼 문제를 되풀이해 해결하지만, 호출을 멈추는 종료 조건이 반드시 필요합니다.
int factorial(int n) {
if (n <= 1) return 1; // 종료 조건
return n * factorial(n - 1); // 자기 자신 호출
}
// factorial(4)
// = 4 * 3 * 2 * 1
// = 24
종료 조건이 없거나 종료 조건에 도달하지 못하면 호출이 계속 쌓여 스택 공간을 소진할 수 있습니다. 재귀 코드 문제에서는 종료 조건 → 호출 시 전달값 → 되돌아오며 계산되는 값 순서로 추적하세요.
Python의 사용자 정의 함수
def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
Python은 def로 함수를 정의합니다. C처럼 반환형을 함수 이름 앞에 적지 않으며, 들여쓰기로 함수 본문의 범위를 구분합니다.
3. 구조체란 무엇일까?
구조체(struct)는 서로 관련된 여러 데이터를 하나의 단위로 묶는 사용자 정의 자료형입니다. 학생 한 명을 저장한다고 생각해 보겠습니다. 학생에게는 이름, 나이, 시력처럼 자료형이 서로 다른 정보가 필요합니다. 이 값들을 따로 관리하는 대신 Student라는 하나의 구조로 묶는 것입니다.
| 구분 | 배열 | 구조체 |
|---|---|---|
| 담을 수 있는 자료형 | 같은 자료형의 원소 | 서로 다른 자료형의 멤버 |
| 접근 방법 | arr[0] |
student.age |
| 예시 | 시험 점수 5개 | 학생의 이름·나이·시력 |
구조체 설계도 만들기
struct Student {
char name[20];
int age;
double vision;
};
이 코드는 아직 학생 한 명을 만든 것이 아닙니다. Student라는 구조체가 이름, 나이, 시력을 어떤 형태로 저장할지 자료형의 설계도를 만든 것입니다. 실제 값을 저장하려면 구조체 변수를 따로 선언해야 합니다.
struct Student s1;
s1.age = 25;
s1.vision = 1.2;
struct Student s1;이라고 쓸까?
위 예제는 typedef를 사용하지 않았기 때문에 C에서는 구조체 태그 앞에 struct를 붙여야 합니다. typedef로 별칭을 만들면 더 짧게 쓸 수 있지만, 처음에는 기본 형태부터 익히는 편이 좋습니다.
점 연산자로 멤버에 접근하기
구조체 변수 안의 멤버를 읽거나 바꿀 때는 점(.) 연산자를 사용합니다. s1.age는 “s1이라는 학생 구조체 안의 age 멤버”라는 뜻입니다.
#include <stdio.h>
#include <string.h>
struct Student {
char name[20];
int age;
double vision;
};
int main(void) {
struct Student s1;
strcpy(s1.name, "민수");
s1.age = 25;
s1.vision = 1.2;
printf("%s, %d세, 시력 %.1f\n", s1.name, s1.age, s1.vision);
return 0;
}
=로 다시 대입할 수 없다
s1.name은 문자 배열이므로 선언이 끝난 뒤 s1.name = "민수";처럼 대입하면 안 됩니다. 위 예제처럼 strcpy()를 사용하거나, 구조체 변수를 만들 때 처음부터 초기화해야 합니다.
4. 구조체와 포인터가 만나면?
구조체 변수도 메모리에 저장되므로 주소를 가지고 있습니다. 이 주소를 포인터에 저장하면 포인터를 통해 구조체의 멤버를 읽고 바꿀 수 있습니다.
struct Student s1;
struct Student *p = &s1;
s1.age = 20;
p->age = 25;
p에는 s1의 주소가 들어 있습니다. 따라서 p->age = 25;는 “p가 가리키는 구조체로 찾아가서 age를 25로 바꿔라”라는 뜻입니다. 실제로 바뀌는 것은 s1.age입니다.
| 대상 | 표현 | 읽는 방법 |
|---|---|---|
| 일반 구조체 변수 | s1.age |
s1의 age |
| 구조체 포인터 | p->age |
p가 가리키는 구조체의 age |
| 역참조 후 점 사용 | (*p).age |
p->age와 같은 뜻 |
시험 핵심 등식: p->age == (*p).age
왜 (*p).age에는 괄호가 필요할까?
점(.) 연산자는 역참조(*)보다 우선순위가 높습니다. 괄호 없이 *p.age라고 쓰면 컴퓨터는 이를 *(p.age)처럼 해석하려고 합니다. 하지만 p는 구조체 값이 아니라 구조체를 가리키는 포인터이므로 p.age부터 성립하지 않습니다.
*p.age // 잘못된 표현: *(p.age)처럼 해석됨
(*p).age // 먼저 *p로 구조체를 얻고 age에 접근
p->age // 위 표현을 간단하게 쓴 화살표 연산자
실행 결과까지 확인하기
#include <stdio.h>
struct Student {
int age;
};
int main(void) {
struct Student s1 = {20};
struct Student *p = &s1;
(*p).age = 25;
printf("%d\n", s1.age); // 25
p->age = 30;
printf("%d\n", s1.age); // 30
return 0;
}
s1.age, (*p).age, p->age는 표현만 다를 뿐 같은 메모리 공간을 가리킵니다.
5. 클래스·객체·인스턴스는 어떻게 다를까?
클래스는 객체지향 프로그래밍에서 객체를 만들기 위한 설계도입니다. 흔히 클래스는 붕어빵 틀, 객체는 그 틀로 만든 붕어빵에 비유합니다.
| 용어 | 의미 | 비유 |
|---|---|---|
| 클래스 | 객체의 속성과 동작을 정의한 설계도 | 붕어빵 틀 |
| 객체 | 상태와 동작을 가지는 실제 소프트웨어 실체 | 만들어진 붕어빵 |
| 인스턴스 | 특정 클래스로부터 생성된 객체임을 강조하는 표현 | Taiyaki 틀로 만든 팥 붕어빵 |
실무에서는 객체와 인스턴스를 비슷한 뜻으로 섞어 쓰기도 합니다. 시험에서는 “클래스는 설계도, 객체는 실체, 인스턴스는 특정 클래스에서 생성된 객체”라고 구분하면 안전합니다. 클래스를 바탕으로 실제 객체를 만드는 과정을 인스턴스화라고 합니다.
속성과 메서드
- 속성·필드: 객체가 가진 상태나 특징. 예: 맛, 가격, 색상
- 메서드: 객체가 수행하는 동작. 예: 굽기, 가격 출력하기
| 구분 | C 구조체 | Java·Python 클래스 |
|---|---|---|
| 주요 목적 | 관련된 데이터를 한 단위로 묶기 | 상태와 동작을 객체 단위로 설계하기 |
| 구성 요소 | 필드 중심 | 필드·메서드·생성자 등 |
| 접근 통제 | 언어 차원의 클래스 접근 제어 없음 | 언어에 따라 접근 제어·캡슐화 지원 |
“C 구조체는 변수만 담고 클래스는 함수도 담는다”라고만 외우면 지나치게 단순합니다. C 구조체에도 함수 포인터를 넣을 수 있고, C++의 struct는 메서드를 가질 수 있습니다. 여기서는 정보처리기사에서 자주 비교하는 C 구조체와 클래스 기반 객체지향의 기본 차이에 초점을 맞췄습니다.
6. Java와 Python으로 클래스 만들기
Java 클래스
class Taiyaki {
String flavor;
int price;
Taiyaki(String flavor, int price) {
this.flavor = flavor;
this.price = price;
}
void bake() {
System.out.println(flavor + " 붕어빵을 굽습니다.");
}
public static void main(String[] args) {
Taiyaki redBean = new Taiyaki("팥", 1500);
redBean.bake();
}
}
new Taiyaki("팥", 1500)은 Taiyaki 클래스를 인스턴스화해 객체를 만들고, 생성자를 호출해 초기값을 넣습니다.
Python 클래스
class Taiyaki:
def __init__(self, flavor, price):
self.flavor = flavor
self.price = price
def bake(self):
print(f"{self.flavor} 붕어빵을 굽습니다.")
red_bean = Taiyaki("팥", 1500)
red_bean.bake()
Python에서는 클래스를 함수처럼 호출해 인스턴스를 만듭니다. __init__()은 새 인스턴스가 준비된 뒤 초기 상태를 설정합니다.
7. 생성자와 소멸자는 정확히 이해하기
Java 생성자
- 클래스 이름과 같은 이름을 사용합니다.
- 반환형을 적지 않습니다.
void도 적지 않습니다. new로 객체를 만드는 과정에서 호출되어 필드를 초기화합니다.- 매개변수의 개수나 자료형을 다르게 하여 여러 생성자를 만들 수 있습니다.
void Taiyaki() { } // 생성자가 아니라 반환형이 void인 일반 메서드
Taiyaki() { } // 올바른 Java 생성자
Python의 __init__()
입문서나 시험에서는 __init__()을 흔히 생성자라고 부릅니다. 엄밀히 말하면 __new__()가 인스턴스를 만들고, __init__()은 이미 만들어진 인스턴스를 초기화합니다. 초보 단계에서는 “객체를 만들 때 자동으로 호출되어 초기값을 설정하는 메서드”라고 이해하면 충분합니다.
소멸·정리 관련 주의사항
finalize()를 소멸자로 외우지 말자
Java에는 C++처럼 개발자가 직접 정의하는 소멸자가 없습니다. 사용하지 않는 객체의 메모리는 가비지 컬렉터가 회수합니다. finalize()는 실행 시점과 순서를 보장할 수 없고 현재 Java에서 폐기 예정으로 표시되므로 자원 정리 방법으로 사용하면 안 됩니다.
__del__()도 호출 시점이 보장되지 않는다
시험에서 Python의 소멸 관련 특수 메서드를 묻는다면 __del__()을 떠올릴 수 있습니다. 다만 실제 프로그램에서는 언제 호출될지 확실하지 않으므로 파일이나 네트워크 연결을 정리하는 수단으로 의존하지 않습니다. 이런 자원은 with문이나 명시적인 close()로 정리하는 편이 안전합니다.
8. 자기 자신을 가리키는 this와 self
Java의 this
this는 현재 객체를 가리키는 Java 키워드입니다. 생성자의 매개변수와 객체의 필드 이름이 같을 때 둘을 구분하는 용도로 자주 사용합니다.
class Taiyaki {
String flavor;
Taiyaki(String flavor) {
this.flavor = flavor;
}
}
this.flavor: 현재 객체가 가진 필드flavor: 생성자로 들어온 매개변수
Python의 self
Python의 인스턴스 메서드에서는 첫 번째 매개변수로 현재 인스턴스가 전달됩니다. 관례상 이 매개변수의 이름을 self라고 씁니다.
class Taiyaki:
def __init__(self, flavor):
self.flavor = flavor
self는 Python 키워드가 아니다
다른 이름을 써도 문법상 가능하지만 모든 Python 개발자가 알아보기 쉽도록 self를 사용하는 것이 표준적인 관례입니다. 또한 self는 인스턴스 메서드에 해당하는 설명입니다. 클래스 메서드는 보통 cls를 받고, 정적 메서드는 인스턴스를 첫 매개변수로 받지 않습니다.
| 구분 | Java this |
Python self |
|---|---|---|
| 정체 | 언어의 키워드 | 관례적인 매개변수명 |
| 의미 | 현재 객체 참조 | 현재 인스턴스를 전달받은 참조 |
| 대표 표현 | this.flavor |
self.flavor |
9. 절차지향과 객체지향의 차이
C를 공부하다가 Java나 Python의 클래스를 만나면 “명령형과 객체지향은 완전히 반대인가?”라는 의문이 생깁니다. 정확히는 C에서 흔히 사용하는 절차지향적 접근과 클래스 기반 객체지향적 접근이 프로그램을 나누고 조직하는 기준에서 차이를 보이는 것입니다.
| 구분 | 절차지향 접근 | 객체지향 접근 |
|---|---|---|
| 중심 질문 | 어떤 순서와 함수로 처리할까? | 어떤 객체가 어떤 책임을 맡고 협력할까? |
| 주요 분해 단위 | 함수·절차 | 클래스·객체 |
| 데이터와 동작 | 데이터를 함수에 전달해 처리하는 형태가 흔함 | 관련 상태와 동작을 객체 안에 함께 설계 |
| 강점 | 작업 흐름이 선명한 문제를 간결하게 표현 | 책임 분리, 재사용, 변경 범위 관리에 유리 |
객체지향에서도 실행 순서는 중요하고, 절차지향 프로그램도 함수와 모듈을 잘 나누면 충분히 유지보수하기 좋습니다. Python과 C++은 여러 프로그래밍 방식을 함께 지원하기도 합니다. 따라서 “C는 무조건 빠르고 객체지향은 무조건 느리다”처럼 언어와 패러다임을 단순하게 나누어 외우지 않는 편이 좋습니다.
캡슐화는 단순히 데이터와 함수를 한 상자에 넣는 것에서 끝나지 않습니다. 객체의 내부 상태를 함부로 바꾸지 못하게 숨기고, 필요한 기능만 공개된 인터페이스로 제공해 변경의 영향을 줄이는 설계 원리입니다.
10. 시험 직전 암기 카드
- 조건문:
if는 조건식을,switch는 일치하는case를 확인 switch함정:break가 없으면 다음case까지 이어서 실행- 반복문:
while은 0회 가능,do-while은 최소 1회 실행 - 흐름 제어:
break는 종료,continue는 현재 반복 건너뛰기,return은 함수 종료 - 함수 원형: 반환형·함수명·매개변수 형태를 호출 전에 컴파일러에 알림
- C의 인수 전달: 항상 값을 복사하며, 주소를 전달하고 역참조하면 원본 변경 가능
- 재귀 함수: 자기 자신을 호출하므로 반드시 종료 조건이 필요
- 구조체: 서로 관련된 서로 다른 자료형을 하나로 묶은 사용자 정의 자료형
- 일반 구조체 변수:
s1.age처럼 점(.) 사용 - 구조체 포인터:
p->age처럼 화살표(->) 사용 - 동치식:
p->age == (*p).age - 클래스: 객체의 상태와 동작을 정의한 설계도
- 객체·인스턴스: 클래스로부터 만들어진 실체
- Java 생성자: 클래스와 같은 이름, 반환형 없음
- Python 초기화:
__init__(self, ...) - 현재 객체: Java는
this, Python 인스턴스 메서드는 관례상self - 절차지향: 함수와 처리 흐름 중심, 객체지향: 객체의 책임과 협력 중심
11. 미니 퀴즈로 마무리
- 위의
switch예제에서n이 2일 때 출력되는 문자열은? do-while문의 본문은 최소 몇 번 실행될까?break와continue의 차이는?- C에서
change_value(a)를 호출한 뒤 원본a가 그대로인 이유는? - 재귀 함수가 끝나기 위해 반드시 필요한 것은?
- 구조체 변수
s1의age멤버에 접근하는 표현은? - 구조체 포인터
p가 가리키는 구조체의age에 접근하는 표현 두 가지는? - Java 생성자에 반환형
void를 적어도 될까? - Python에서
self는 언어가 예약한 키워드일까? - 특정 클래스를 바탕으로 객체를 만드는 과정을 무엇이라고 할까?
정답 확인하기
BC.case 2뒤에break가 없어case 3까지 실행된다.- 최소 1번. 본문을 먼저 실행한 뒤 조건을 검사한다.
break는 반복문을 끝내고,continue는 현재 반복의 나머지만 건너뛴다.- 함수에 원본이 아니라 값의 복사본이 전달되기 때문이다.
- 재귀 호출을 멈추는 종료 조건
s1.agep->age,(*p).age- 안 된다. 반환형을 적으면 생성자가 아니라 메서드가 된다.
- 아니다. 현재 인스턴스를 받는 첫 매개변수에 사용하는 관례적인 이름이다.
- 인스턴스화
12. 공식 문서 참고 자료
- GNU C Language Manual: Statements
- GNU C Language Manual: Functions
- GNU C Language Manual: Structures
- Python 공식 문서: Control Flow와 함수
- Oracle Java Tutorials: Constructors
- Oracle Java Tutorials: this Keyword
- Java SE 26 Deprecated API List
- Python 공식 문서: Classes
- Python Data Model: __del__()
마무리
3일차의 네 단원은 따로 떨어져 있지 않습니다. 명령문으로 실행 흐름을 만들고, 반복되는 작업은 사용자 정의 함수로 분리합니다. 여러 데이터를 구조체로 묶는 방법을 익힌 뒤에는 상태와 동작을 함께 설계하는 클래스로 확장됩니다.
시험 직전에는 switch의 break, 반복 횟수, 함수의 값 전달, 재귀 종료 조건, 구조체의 점·화살표 연산자를 우선 확인하세요. 클래스 부분은 “설계도에서 객체를 만들고, 생성자가 초기값을 넣으며, this와 self가 현재 객체를 구분한다”는 흐름으로 기억하면 좋습니다.
이 글은 정보처리기사 학습 내용을 바탕으로 개념을 다시 구성한 개인 공부 기록입니다. 교재 문장을 그대로 옮긴 해설이 아니며, 세부 출제 범위와 표현은 최신 교재와 출제 기준을 함께 확인해 주세요.
'AI > 정보처리기사_스터디' 카테고리의 다른 글
| 정보처리기사_수제비_2일차 (0) | 2026.07.14 |
|---|