#include <iostream>
using namespace std;
class Base {
public:
~Base() {
cout << "Base destructor " << endl;
}
};
class Derived : public Base {
public:
~Derived() {
cout << "Derived destructor " << endl;
}
};
int main() {
Derived d;
return 0;
}
1-1번 문제
#include <iostream>
#include <vector>
using namespace std;
class Base {
public:
Base() {
cout << "Base constr called" << endl;
}
};
class Derived : public Base {
public:
Derived() {
cout << "Derived constr called" << endl;
}
};
int main() {
Derived d;
return 0;
}
기본 클래스의 생성자 호출 : Base 생성자가 먼저 호출됩니다. 이는 상속 계층 구조의 기본 클래스가 먼저 초기화되어야 하기 때문입니다.
이후 파생클래스의 생성자가 호출됩니다.
1-2번 문제
#include <iostream>
#include <vector>
using namespace std;
class Base {
int arr[10];
};
class Derived : public Base {
int d;
};
int main() {
cout << sizeof(Derived);
return 0;
}
BASE 클래스에서 arr[10]을 만들어 준 다음에 derived 클래스에서 int d;를 해줍니다.
main함수에서는 derived의 크기를 묻고 있는데요.
sizeof 연산자는 컴파일 타임에 피연산자의 크기를 바이트 단위로 반환합니다. derived 클래스의 크기는 base 클래스의 크기와 derived 클래스의 추가 멤버 변수 d의 크기를 합한 값이 됩니다.
즉 Base 클래스의 크기는 int arr[10]은 10개의 int를 가지고 있으므로 10 * sizeof(int) 가 됩니다. = 40
Derived 클래스의 크기는 Base클래스의 크기와 추가 멤버 변수 d를 더한 값입니다. d는 int 타입이므로 sizeof(int)를 추가합니다 . 즉 40 + 4 = 44 바이트가 됩니다.
1-3번 문제
#include <iostream>
#include <vector>
using namespace std;
class P {
public:
void print() {
cout << "Inside P";
}
};
class Q : public P {
public:
void print() {
cout << "Inside Q";
}
};
class R : public Q { };
int main() {
R r;
r.print();
return 0;
}
출력 : Inside Q
클래스 상속과 멤버 함수 재정의를 설명하는 예제입니다. p클래스는 PUBLIC 접근 지정자로 선언된 멤버 함수를 가지고 있습니다. 이 함수는 Inside P라는 메세지를 출력합니다.
Q클래스는 p클래스는 PUBLIC 상속받습니다. q 클래스는 p 클래스의 print 함수를 재정의합니다. q 클래스의 print 함수는 inside q를 출력합니다.
R 클래스는 q 클래스를 public 상속받습니다.
R클래스는 별도의 멤버 함수를 정의하고 있지 않기 때문에 q클래스의 멤버 함수를 그대로 상속받습니다.
R클래스 객체 'r'을 생성합니다.
r.print()를 호출합니다. 이때 R 클래스에는 print 함수가 정의되어 있지 않지만, Q클래스를 상속받았기 때문에 Q클래스의 print함수가 호출됩니다.
설명
R 클래스는 Q 클래스를 상속받았으므로 Q 클래스의 print 함수를 사용할 수 있습니다.
Q 클래스의 print 함수는 Inside Q를 출력합니다.
따라서 r.print()를 호출하면 Inside Q를 출력합니다.
INSIDE P가 출력되지 않는 이유는 함수 재정의(오버라이딩) 때문입니다. Q 클래스는 P 클래스의 print 함수를 재정의하여 자신의 버전의 print 함수를 제공하고 있습니다. 상속 계층에서 하위 클래스(Q 및 R)의 재정의된 함수를 호출할 때, 상위 클래스(P)의 함수는 호출되지 않습니다.
1-4 번
#include <iostream>
#include <vector>
using namespace std;
class Base {
private:
int x = 1, y = 2;
};
class Derived : public Base {
public:
void show() {
cout << x << " " << y << endl;
}
};
int main(void) {
Derived d;
d.show();
return 0;
}
private로 설정 되어 있기 때문에 public으로 받아와도 실행이 안되는 것을 볼 수 있습니다.
1-5번
class Base {
protected:
int x, y;
public:
Base(int a = 1, int b = 2) : x(a), y(b) {}
};
class Derived : public Base {
public:
void show() {
cout << x << " " << y;
}
};
int main(void)
{
Derived d;
d.show();
return 0;
}
1-4번문제의 연장입니다. protected로 바꿔주면 정상적으로 1 2 가 실행되는 것을 볼 수 있습니다.
1-6번
#include <iostream>
using namespace std;
class Base {};
class Derived : public Base {};
int main() {
Base* bp = new Derived;
Derived* dp = new Base;
}
Base라는 기본 클래스와 이를 상속받는 Derived 파생 클래스가 정의되어 있습니다.
main 함수에서는 두 개의 포인터가 생성됩니다.
첫 번째 할당인 Base* bp new Derived; 는 올바른 코드입니다. Derived 클래스에 객체를 생성하여 Base 클래스 포인터에 할당하고 있습니다. 이는 상속 관계에서 허용됩니다. 즉 파생 클래스 객체를 기본 클래스 포인터로 가리키는 것은 허용됩니다.
두 번째 할당 Derived * dp = new Base; 이 줄은 컴파일 오류를 발생시킵니다.
이유
Base 클래스 객체 Derived 클래스 포인터에 할당할 수 없습니다. 상위 클래스의 객체는 하위 클래스의 포인터로 변환할 수 없기 때문입니다. 이는 상속 관계에서 허용되지 않는 반대 방향의 형 변환입니다.
상속 관계에서는 항상 상위 클래스의 포인터나 참조가 하위 클래스의 객체를 가리킬 수 있지만 그 반대는 불가능합니다. 이는 논리적으로도 맞지 않습니다. 기본 클래스 객체에는 파생 클래스의 추가 멤버나 함수가 없기 때문에 안전하지 않습니다.
int main() {
Base* bp = new Derived; // 올바른 코드
// Derived* dp = new Base; // 오류 발생, 주석 처리 또는 삭제
Base* bp2 = new Base; // 대체 가능 코드
}
이렇게 바꿔주면 됩니다.
1-7번
#include <iostream>
using namespace std;
class Base {
public:
void show() {
cout << "In Base";
}
};
class Derived : public Base {
public:
int x;
void show() {
cout << "In Derived";
}
Derived() {
x = 10;
}
};
int main() {
Base* bp, b;
Derived d;
bp = &d;
bp->show();
cout << bp->x;
return 0;
}
cout << bp->x;
여기에서 오류를 발생시킵니다. 왜냐하면 bp는 Base 클래스 포인터로 선언되었기 때문에, Derived 클래스에만 있는 멤버 변수 x에 접근할 수 없습니다. Base 클래스에는 x가 정의되어 있지 않으므로 컴파일러는 이 코드를 컴파일러 할 수 없습니다.
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // virtual 키워드 추가
cout << "In Base";
}
};
class Derived : public Base {
public:
int x;
void show() override { // override 키워드 추가 (선택 사항)
cout << "In Derived";
}
Derived() {
x = 10;
}
};
int main() {
Base* bp;
Derived d;
bp = &d;
bp->show(); // 이제 Derived의 show 함수가 호출됩니다.
cout << static_cast<Derived*>(bp)->x; // 형 변환을 통해 x에 접근
return 0;
}
virtual을 통해서 x에 접근할 때 올바른 형 변환을 할 수 있도록 만들어주면 됩니다.
상위 클래스의 포인터를 사용하여 하위 클래스 멤버 변수에 직접 접근할 수 없느 것은 C++의 타입 시스템 때문입니다. 상위 클래스의 포인터가 하위 클래스의 객체를 가리키고 있을 때, 이 포인터는 기본적으로 상위 클래스의 멤버와 함수에만 접근할 수 있습니다.
상위 클래스에는 하위 클래스의 멤버 변수와 함수가 정의되어 있지 않기 때문에, 상위 클래스의 포인터로는 하위 클래스의 멤버 변수와 함수에 접근할 수 없습니다. 이를 해결하기 위해서는 형변환이 필요합니다.
Base 클래스의 show 함수를 virtual로 선언합니다. 이렇게 하면 Base 클래스 포인터를 통해 호출하더라도 실제 객체 타입에 따라 Derived 클래스의 show 함수가 호출됩니다.
형변환
static_cast<Derived*>(bp)->x는 Base 클래스 포인터 bp를 Derived 클래스 포인터로 변환합니다. 이렇게 변환한 후 에 x에 접근할 수 있습니다.
static_cast는 컴파일 타입에 형 변환을 확인하므로 타입이 올바르지 않으면 컴파일러가 오류를 발생시킵니다.
1-8번
#include <iostream>
using namespace std;
class Base {
public:
int fun() {
cout << "Base::fun() called";
}
int fun(int i) {
cout << "Base::fun(int i) called";
}
};
class Derived : public Base {
public:
int fun() {
cout << "Derived::funb() called";
}
};
int main() {
Derived d;
d.fun(5);
return 0;
}
Base 클래스
Base 클래스는 두 개의 fun 함수를 오버로딩합니다:
- int fun(): 인자가 없는 함수.
- int fun(int i): 정수형 인자를 받는 함수.
Derived 클래스
-Derived 클래스는 Base 클래스를 상속받습니다.
-Derived 클래스는 Base 클래스의 fun() 함수를 오버라이딩합니다.
int main() {
Derived d;
d.fun(5);
return 0;
}
Derived d;
Derived 클래스의 객체 d를 생성합니다.
d.fun(5);
d는 Derived 클래스의 객체입니다. fun(int i) 함수를 호출하려고 합니다.
발생하는 문제
Derived 클래스는 Base 클래스의 fun() 함수를 오버라이딩했지만, Base 클래스의 fun(int i) 함수를 가리게 됩니다. 즉 Derived 클래스의 fun() 함수 정의로 인해 Base 클래스의 fun(int i) 함수는 Derived 클래스 객체에서 접근할 수 없게 됩니다. 이를 숨김이라고 합니다.
해결방법
Derived 클래스에서 Base 클래스의 모든 fun 함수를 사용할 수 있도록 하려면 Base 클래스의 함수들을 using 키워드로 가져와서 사용해야 합니다.
class Derived: public Base {
public:
using Base:fun;
int fun() {
cout << "Derived::fun() called";
}
};
#include <iostream>
using namespace std;
class Base {
public:
int fun() {
cout << "Base::fun() called";
return 0; // 값을 반환
}
int fun(int i) {
cout << "Base::fun(int i) called";
return 0; // 값을 반환
}
};
class Derived : public Base {
public:
using Base::fun; // Base 클래스의 fun 함수들을 가져옴
int fun() {
cout << "Derived::fun() called";
return 0; // 값을 반환
}
};
int main() {
Derived d;
d.fun(5);
return 0;
}
1-9번
#include <iostream>
using namespace std;
class Base {
public:
void fun() {
cout << "Base::fun() called";
}
void fun(int i) {
cout << "Base::fun(int i)called";
}
};
class Derived : public Base {
public:
void fun() {
cout << "Derived::fun() called";
}
};
int main() {
Derived d;
d.Base::fun(5);
return 0;
}
출력 : Base::fun(int i)called
1-10번
#include <iostream>
using namespace std;
class Base {
public:
virtual string print() const {
return "This is Base class";
}
};
class Derived : public Base {
public:
virtual string print() const {
return "This is Derived class";
}
};
void describe(Base p) {
cout << p.print() << endl;
}
int main() {
Base b;
Derived d;
describe(b);
describe(d);
return 0;
}
Base 클래스는 virtual 키워드를 사용하여 print 함수를 정의합니다. 이 함수는 문자열을 반환합니다. virtual 키워드를 사용하여 선언된 함수는 런타임 다형성을 가능하게 합니다.
Derived 클래스는 Base 클래스를 상속받습니다. Derived 클래스는 Base 클래스의 print 함수를 오버라이딩합니다.
describe 함수는 Base 클래스 객체를 매개변수로 받아, 해당 객체의 print 함수를 호출하여 결과를 출력합니다. 이 함수는 객체를 값으로 전달합니다. 즉 함수 호출 시 객체의 복사본이 만들어집니다.
int main() {
Base b;
Derived d;
describe(b);
describe(d);
return 0;
}
main 함수에서 Base 클래스 객체 b와 Derived 클래스 객체 d를 생성합니다. describe 함수에 b와 d를 전달합니다.
describe 함수가 객체를 값으로 전달하고 있기 때문에 다형성이 제대로 작동하지 않습니다. 이는 복사 시 객체 슬라이딩이 발생하여 Derived 클래스 객체가 Base 클래스 객체로 슬라이스 됩니다. 즉 Derived 클래스의 부분만 Base 클래스 객체로 복사되기 때문에 Base 클래스의 print 함수만 호출됩니다.
#include <iostream>
using namespace std;
class Base {
public:
virtual string print() const {
return "This is Base class";
}
};
class Derived : public Base {
public:
virtual string print() const {
return "This is Derived class";
}
};
void describe(const Base& p) {
cout << p.print() << endl;
}
int main() {
Base b;
Derived d;
describe(b);
describe(d);
return 0;
}
describe 함수의 매개변수 타입을 const Base&로 변경했습니다. 이렇게 하면 Base 클래스 또는 이를 상속한 클래스의 객체를 참조로 전달받고, 실제 객체 타입에 맞는 print 함수가 호출됩니다. 참조를 사용하면 객체 슬라이싱이 발생하지 않습니다.
가상함수호출
Base 클래스의 print 함수가 virtual로 선언되어 있기 때문에 실제 객체 타입에 따라 Derived 클래스의 print 함수가 호출됩니다.
1-11번
#include <iostream>
using namespace std;
class Base {
public:
int x, y;
Base(int a, int b) {
x = a, y = b;
}
};
class Derived : public Base {
public:
Derived(int p, int q) : x(p), y(q) {}
void print() {
cout << x << " " << y;
}
};
int main(void) {
Derived q(10, 10);
q.print();
return 0;
}
Derived 클래스의 생성자에서 Base 클래스의 생성자를 올바르게 호출하지 않았기 때문입니다. Derived 클래스는 Base 클래스를 상속하고 있으므로, Base 클래스의 생성자를 명시적으로 호출해야 합니다.
문제분석
- Base 클래스는 매개변수 두 개를 받는 생성자를 가지고 있습니다.
- Derived 클래스는 Base 클래스를 상속받습니다.
- Derived 클래스의 생성자에서 Base 클래스의 생성자를 호출하지 않고 있습니다.
c++에서 파생 클래스의 생성자는 항상 기본 클래스의 생성자를 호출해야 합니다. 기본 클래스에 기본 생성자가 없고 매개변수를 받는 생성자만 있는 경우, 파생 클래스의 생성자에서 명시적으로 기본 클래스의 생성자를 호출해야 합니다.
#include <iostream>
using namespace std;
class Base {
public:
int x, y;
Base(int a, int b) {
x = a;
y = b;
}
};
class Derived : public Base {
public:
Derived(int p, int q) : Base(p, q) {} // Base 클래스 생성자를 호출
void print() {
cout << x << " " << y;
}
};
int main() {
Derived q(10, 10);
q.print();
return 0;
}
1-12번
#include <iostream>
using namespace std;
class A {
float d;
public:
int a;
void change(int i) {
a = i;
}
void value_a() {
cout << a << endl;
}
};
class B : public A {
int a = 15;
public:
void print() {
cout << a << endl;
}
};
int main() {
B b;
b.change(10);
b.print();
b.value_a();
return 0;
}
이 c++ 프로그램은 상속, 멤버변수, 멤버 함수의 기본 개념을 보여줍니다.
클래스 'A'
-float 타입의 private 멤버 변수 'd'를 가집니다.
-int 타입의 public 멤버 변수 a를 가집니다.
-세 개의 public 멤버 함수를 가집니다. change(int i) 이 함수는 매개변수 i의 값을 멤버 변수 a에 할당합니다. value_a()이 함수는 멤버 변수 a의 값을 출력합니다.
클래스 B
클래스 A를 public으로 상속받습니다. int 타입의 private 멤버 변수 'a'를 15로 초기화합니다. print() 이 함수는 클래스 B의 멤버 변수 'a'를 출력합니다.
main 함수
main 함수에서 b 클래스의 객체 b를 생성합니다. b.change(10)을 호출하여 상속된 클래스 'A'의 멤버 변수 'a'에 10을 할당합니다. b.print()을 호출하여 클래스 B의 멤버 변수 'a'를 출력합니다. 여기서 출력되는 값은 클래스 'B'의 멤버 변수 'a'인 15입니다.
b.value_a()을 호출하여 상속된 클래스 'A'의 멤버 변수 'a'의 값을 출력합니다. 여기서 출력된 값은 change 함수에서 할당된 10입니다.
따라서 15, 10 이렇게 나오게 됩니다.
1-13번
#include <iostream>
using namespace std;
class A {
double d;
public:
virtual void func() {
cout << "In class A\n";
}
};
class B : public A {
int a = 15;
public:
void func() {
cout << "In class B\n";
}
};
int main() {
B b;
b.func();
return 0;
}
다형성의 기본 개념을 보여줍니다.
클래스 정의
클래스 A
-double 타입의 private 멤버 변수 d를 가집니다.
-public 영역에 가상함수 func()를 정의합니다. 이 함수는 In class A를 출력합니다.
클래스 B
-클래스 A를 public으로 상속받습니다.
-int 타입의 private 멤버 변수 'a'를 15로 초기화합니다.
-public 영역에 func() 함수를 재정의합니다. 이 함수는 In class B를 출력합니다.
main 함수
- main 함수에서 B클래스의 객체 b를 생성합니다.
- b.func()를 호출하여 B 클래스의 func() 함수를 생성합니다.
실행 결과는 b.func() 호출 시 B 클래스의 오버라이딩된 func() 함수가 호출되어 In class B 가 출력됩니다.
클래스 A의 func() 함수는 virtual 키워드로 선언이 되어 있습니다. 이는 'A'클래스를 상속받는 클래스들이 func()함수를 재정의할 수 있음을 의미합니다.
다형성
가상 함수를 사용하면 프로그램은 객체의 실제 타입에 따라 올바른 함수 버전을 호출합니다. 이 경우 객체 'b'는 'B' 클래스의 인스턴스이므로, func() 호출시 'B'클래스의 func() 함수가 실행됩니다.
1-14번
#include <iostream>
using namespace std;
class A {
double d;
public:
virtual void func() {
cout << "In Class A\n";
}
};
class B : public A {
int a = 15;
public:
void func() {
cout << "In class B\n";
}
};
int main() {
A* a;
a->func();
return 0;
}
클래스 A
double 타입의 private 멤버 변수 d를 가집니다.
public 영역에 가상함수 func()를 정의합니다. 이 함수는 In Class A 를 출력합니다.
클래스 B
클래스 A를 public으로 상속받습니다.
int 타입의 private 멤버 변수 'a'를 15로 초기화합니다.
public영역에 func() 함수를 재정의합니다. 이 함수는 In Class B를 출력합니다.
main 함수
main 함수에서 A클래스의 포인터 a를 선언합니다. a->func()를 호출하여 A클래스의 func() 함수를 실행하려고 합니다.
'a' 포인터가 선언되었지만 아무런 객체도 가리키지 않고 있습니다. 이는 초기화되지 않은 포인터이며 이를 역참조하는 것은 정의되지 않은 동작을 초래합니다.
이 프로그램이 a->func()를 호출하려고 할 때 포인터 'a'가 유효한 객체를 가리키지 않기 때문에 올바르게 동작하지 않습니다.
#include <iostream>
using namespace std;
class A {
double d;
public:
virtual void func() {
cout << "In Class A\n";
}
};
class B : public A {
int a = 15;
public:
void func() override {
cout << "In class B\n";
}
};
int main() {
A* a = new B(); // A 클래스의 포인터가 B 클래스의 객체를 가리키도록 합니다.
a->func(); // 올바른 객체를 가리키므로 다형성(polymorphism)이 작동합니다.
delete a; // 동적 할당된 메모리를 해제합니다.
return 0;
}
이런식으로 해주면 B 객체를 가리키게 됩니다
A* a = new B();
A 클래스의 포인터 a가 B클래스의 객체를 가리키도록 동적 할당합니다. 이를 통해 'a'는 B 객체를 가리키게 됩니다.
a-> func();는 포인터 a가 실제로 B 객체를 가리키고 있으므로 B 클래스의 func() 함수가 호출됩니다. 이는 다형성을 보여줍니다.
1-15번
#include <iostream>
using namespace std;
class A {
double d;
public:
virtual void func() {
cout << "In class A\n";
}
};
class B : public A {
int a = 15;
public:
void func() {
cout << "In class B\n";
}
};
int main() {
A* a = new A();
B b;
a = &b;
a->func();
return 0;
}
클래스 A
-double 타입의 private 멤버변수 'd'를 가집니다.
-public 영역에 가상함수 func()를 정의합니다.
클래스 B
-클래스 A를 public으로 상속받습니다.
-int 타입의 private 멤버 변수 'a'를 15로 초기화합니다.
-public영역에 func() 함수를 오버라이딩합니다. 이 함수는 In class B를 출력합니다.
MAIN 함수
A* a = new A();
A클래스의 객체를 동적으로 생성하고 이를 가리키는 포인터 a를 선언합니다. 이 시점에서 a는 'A' 클래스의 객체를 가리키고 있습니다.
B b;
B클래스의 객체 b를 생성합니다.
a = &b;
a 포인터가 B클래스의 객체 b의 주소를 가리키도록 합니다. 이제 'a'는 B 객체를 가리키고 있습니다.
a->func();
가상함수 호출입니다. 포인터 'a'가 실제로 'B' 객체를 가리키고 있으므로 'B' 클래스의 func() 함수가 생성됩니다. 이는 다형성을 보여줍니다.
실행결과
a->func() 함수 호출 시 포인터 'a'가 실제로 B 객체를 가리키고 있 으므로 B클래스의 func() 함수가 호출됩니다.
1-16번
소멸자의 호출 순서를 보여주는 것입니다.
클래스 Base
소멸자 ~Base()를 정의합니다. 이 소멸자는 객체가 소멸될 때 Base Destructor를 출력합니다.
클래스 Derived
클래스 Base를 public으로 상속받습니다. 소멸자 ~Derived를 정의합니다. 이 소멸자는 객체가 소멸될 때 Derived destructor를 출력합니다.
main 함수
Derived d;
-Derived 클래스의 객체 d를 생성합니다. 이 시점에서 Derived 객체의 생성자가 호출되지만, 생성자는 정의되어 있지 않기 때문에 아무런 출력도 없습니다.
프로그램이 종료될 때 d 객체는 소멸됩니다. 이 과정에서 소멸자가 호출됩니다.
소멸자 호출 순서
C++에서는 객체가 소멸될 때, 파생 클래스의 소멸자가 먼저 호출되고, 그 다음에는 기본 클래스의 소멸자가 호출됩니다. 따라서 Derived 객체의 소멸자가 먼저 호출되고, 그 다음에 Base 객체의 소멸자가 호출됩니다.
실행 과정 요약
1. Derived d;에서 Derived 클래스 객체 d를 생성합니다.
2. main 함수가 종료되면, d 객체가 소멸됩니다.
3. d 객체의 소멸자가 호출되어 "Derived destructor"를 출력합니다.
4. 이후 Base 클래스의 소멸자가 호출되어 "Base destructor"를 출력합니다.
'IT 프로그래밍 > 객체지향프로그래밍' 카테고리의 다른 글
객체지향프로그래밍 5 과제 (0) | 2024.06.21 |
---|---|
객체지향프로그래밍 4 과제 (0) | 2024.06.21 |
다형성 (0) | 2024.06.19 |
메서드 오버라이딩 (0) | 2024.06.19 |
상속이란? (0) | 2024.06.19 |