Kong Eunho

기말고사 예상문제

2025년 12월 10일 16시
카테고리 - LECTURE, 객체지향프로그래밍II


객체지향프로그래밍II(김정준) 기말고사 예상문제

1. 가상 함수와 오버라이딩에 대하여 자세히 서술하시오.

class Base {
public:
    virtual void f() { ... };
};

class Derived : public Base {
public:
    virtual void f() { ... }; // virtual 생략 가능
};

가상 함수란 파생 클래스에서 재정의될 때 동적 바인딩을 수행하도록 하기 위해 사용하는 함수 선언 방식이다. 가상 함수를 파생 클래스에서 재정의하는 것을 함수 오버라이딩이라 한다.
함수 선언 앞에 virtual 키워드를 붙여서 가상 함수를 선언할 수 있다. 일반 함수를 재정의할 경우에는 컴파일 타임에 호출 바인딩이 일어나는 정적 바인딩 방식이 사용되는데 비해, 오버라이딩의 경우 기본 클래스의 포인터나 참조를 통해 호출될 때 컴파일러에게 호출 바인딩을 런타임으로 미루라고 지시하는 동적 바인딩 방식으로 기능한다.
오버라이딩을 한 경우에 기본 클래스의 가상 함수는 존재감을 잃고, 호출 시 항상 파생 클래스의 함수가 호출된다.
가상 함수를 가진 기본 클래스를 오버라이딩하여 파생 클래스에서 구현할 함수의 인터페이스로 사용할 수 있다. 이를 통해 파생 클래스의 다형성이 보장된다.
오버라이딩은 각 함수의 함수명, 매개변수 타입과 개수, 리턴 타입모두 일치해야 성공한다.
오버라이딩을 한 경우에도 범위 지정 연산자(::)를 사용하여 정적 바인딩을 지시할 수 있다.
소멸자에 virtual 키워드를 붙여 가상 소멸자를 선언할 수 있다. 이 경우 기본 클래스와 파생 클래스의 소멸자가 모두 실행되어 파생 클래스에서의 동적 할당 메모리 누수를 방지할 수 있다.

2. 순수 가상 함수와 추상 클래스에 대하여 자세히 서술하시오.

class Shape {
protected:
	virtual void draw() = 0; // 순수 가상 함수
};

class Circle : public Shape {
protected:
	virtual void draw() { ... };
};

class Rect : public Shape {
protected:
	virtual void draw() { ... };
};

추상 클래스최소 하나 이상순수 가상 함수를 멤버로 가진 클래스이다. 순수 가상 함수란 코드가 없이 선언만 있는 가상 멤버 함수를 말한다. 기본 클래스를 인터페이스 용도로만 사용할 경우, 가상 함수를 구현할 필요가 없어지기 때문에 추상 클래스로 이용할 수 있다.
추상 클래스는 온전한 클래스가 아니므로 객체로 생성할 수 없지만, 포인터는 선언 가능하다.
추상 클래스를 단순 상속하면 파생 클래스도 자동으로 추상 클래스가 된다. 추상 클래스를 상속받은 뒤 추상 클래스의 모든 순수 가상 함수를 오버라이딩해야 객체 생성이 가능한 일반 클래스가 된다.


3. 템플릿에 대하여 자세히 서술하시오.

template <class T> // 템플릿 선언
T add(T data [], int n) {
    T sum = 0;
    for(int i=0; i<n; i++) {
        sum += data[i];
    }
    return sum;
}

template <typename T> // class T와 동일
class Stack { // 제네릭 클래스 선언
    T data[100]; // T 타입 배열
public:
    void push(T element);
};

template <typename T>
void Stack<T>::push(T element) { ... } // 제네릭 클래스 구현

Stack <int> iStack; // int형으로 구체화

template <class T1, class T2> // 여러 제네릭 타입을 가진 템플릿

템플릿이란 함수나 클래스를 일반화하는 C++의 도구이다. template 키워드로 템플릿을 선언할 수 있다. 일반화를 위해 제네릭 타입을 사용하여 템플릿을 선언할 수 있다.
class T 혹은 typename T를 이용하여 제네릭 타입의 템플릿을 선언할 수 있으며, 이 경우 매개 변수나 리턴 타입만 다른 중복 함수들을 일반화할 수 있다.
템플릿의 제네릭 타입에 구체적인 타입을 지정하는 것구체화라고 한다. 하나의 제네릭 타입에 서로 다른 타입의 인자를 전달할 수 없다.
템플릿을 사용할 경우 함수 코드를 재사용하여 생산성을 늘릴 수 있지만, 포팅에 취약하며, 컴파일 오류 메시지가 빈약하다는 단점이 있다.
템플릿 함수의 중복 함수가 존재한다면, 중복 함수가 우선으로 바인딩된다.
템플릿을 이용하여 제네릭 클래스를 만들 수 있다.

4. 람다 함수에 대하여 자세히 서술하시오.

// 선언과 동시에 호출되는 람다 함수
[](int x, int y) { cout << "합은 " << x + y; } (2, 3); // 5 출력

// auto 키워드로 변수에 저장되는 람다 함수
double pi = 3.14; // 지역 변수
auto calc = [pi](int r)-> double { return pi*r*r; };
cout << calc(3); // 28.26 출력

C++에서 람다 함수익명의 함수를 만드는 기능이다. 람다 함수는 4부분으로 구성된다.
① 캡쳐 리스트
람다 함수에서 사용하고자 하는 함수 바깥의 변수 목록.
[=] (모두 값으로 복사), [&] (모두 참조로 캡쳐) 방식으로도 사용이 가능하다.
② 매개변수 리스트
③ 리턴 타입
앞에 ->를 붙이며, 생략이 가능하다.
④ 함수 코드
[①](②)③{④};
람다 함수는 컴파일러에 의해 고유한 클래스로 변환되는데, 그 클래스는 컴파일러만 알고 있기 때문에, 컴파일러가 타입을 자동으로 추론하게 하는 auto 키워드를 사용해야만 변수에 저장할 수 있다.

5. 스트림과 버퍼에 대하여 자세히 서술하시오.

스트림이란 데이터의 흐름, 혹은 데이터를 전송하는 소프트웨어 모듈을 의미한다. 스트림은 프로그램과 장치/파일을 연결하는 역할을 한다. C++에는 입력 스트림출력 스트림이 있다.
C++ 입출력 스트림은 버퍼를 가진다. 키 입력 스트림의 버퍼입력된 데이터를 임시 저장하는 역할이며, 스크린 출력 스트림의 버퍼출력될 데이터를 임시 저장하는 역할을 한다. 버퍼는 꽉 차거나, 개행 문자를 만나거나, flush 명령이 있거나, 프로그램이 종료될 때 비워진다(출력된다).
입출력 클래스에는 입출력 스트림 클래스들의 기본 클래스ios, 문자 단위 입출력 클래스istream, ostream, iostream, 파일 입출력 클래스ifstream, ofstream, fstream이 있다.
표준 입출력 스트림 객체는 C++ 프로그램이 실행될 때 자동으로 생성된다. istreamcin, ostreamcout 등의 객체를 자동으로 생성한다.
ostream의 멤버 함수로는 put, write, flush 등이 있고, istream의 멤버 함수로는 get, getline, ignore 등이 있다.

6. 포맷 입출력에 대하여 자세히 서술하시오.

// 포맷 플래그
cout.unsetf(ios::dec); // 10진수 해제
cout.setf(ios::hex); // 16진수 설정
cout << 30 << endl; // 1e 출력

// 포맷 함수
cout.fill('^'); // 빈칸을 '^'로 채움
cout.width(10); // 최소 너비를 10칸으로 지정
cout << "Hello" << endl; // ^^^^^Hello 출력

// 조작자
cout << hex << setw(5) << setfill('^') << 30 << endl;
// ^^^1e 출력

C++에서는 3가지 방법으로 포맷 입출력을 수행할 수 있다.
① 포맷 플래그
입출력 스트림에서 입출력 형식을 지정하기 위해 사용하는 플래그이다. ios 클래스에 정의된 기본 포맷 플래그로는 dec, oct, hex 등이 있다. 포맷 플래그는 setf(), unsetf()로 설정/해제할 수 있다.
② 포맷 함수
width(), fill() 등의 포맷 함수로도 포맷을 지정할 수 있다.
③ 조작자
<< 혹은 >> 연산자와 함께 사용되는 함수를 의미한다. 매개변수가 있는 조작자없는 조작자로 구분할 수 있다. C++ 표준 라이브러리에는 입출력 포맷 지정을 위한 여러 조작자가 구현되어 있다. 매개변수가 없는 조작자로는 dec, oct, hex, endl 등이 있으며, 매개변수가 있는 조작자로는 setw(), setfill() 등이 있다. 매개변수가 있는 조작자는 iomanip 헤더 파일을 포함해야 사용할 수 있다. 사용자가 목적에 맞게 조작자를 정의하여 사용할 수도 있다.

7. C++ 예외 처리에 대하여 자세히 서술하시오.

int n = 0;
double pi = 3.14;

try { // 예외가 발생할 가능성이 있는 블록
    if(n == 0) {
        throw n; // 예외 발생을 알림, int 예외값
    }
    if(pi != 3.14) {
        throw pi; // 예외 발생을 알림, double 예외값
    }
}
catch(int x) { ... } // int 예외값에 대한 예외 처리 구문
catch(double y) { ... } // double 예외값에 대한 예외 처리 구문

예외란 실행 중에 발생하는 예상치 못한 상황이다. 예외는 운영체제, 응용프로그램 수준에서 처리되는데, C++의 예외 처리는 응용프로그램 수준의 예외 처리를 말한다.
C++ 예외 처리는 기본적으로 try-throw-catch를 이용한다. try { } 블록으로 예외가 발생할 가능성이 있는 코드를 묶으며, try 블록 내부에서 throw문으로 발견된 예외를 알린다. throw에 의해 예외가 발생한다면 catch() { } 블록으로 건너뛰어 처리한다. 이 경우 적절한 catch 블록을 찾을 때까지 호출된 함수들을 종료하며 스택을 정리하는 ‘스택 풀기’가 수행된다.
하나의 try 블록에 다수의 throw를 수행하거나 다수의 catch 블록을 연결할 수 있으며, try 블록 내에서 호출된 함수에도 throw를 포함할 수 있다. 또한 try 블록 내에 try 블록을 중첩할 수 있으며, catch 블록 내에도 try와 catch 블록을 선언할 수 있다.
가독성을 위하여 함수 원형 뒤에 throw(예외 타입)을 지정하기도 하였지만, 이 경우 컴파일러가 함수 실행 시마다 타입을 검사하기 때문에, 성능 저하의 우려가 있다. 따라서 예외 타입은 주석으로 지정하는 것이 좋다.
throw 문은 항상 try 블록 내에서 실행되어야 하며, 예외를 처리할 catch가 없다면 프로그램이 강제로 종료된다.
필요에 의해 특정 예외 정보를 포함하는 예외 클래스를 작성할 수 있으며, 이 경우 throw객체를 복사하여 던질 수 있다.

◀ 이전 글 LECTURE, 객체지향프로그래밍II
예외 처리와 링크 지정
2025-12-04
목록으로 다음 글 ▶ LECTURE, 데이터베이스실무
기말고사 대비 개념정리
2025-12-11