15.1) 상속(inheritance)
객체지향 프로그래밍에서는 기존 클래스로부터 새로운 클래스를 정의할 수 있으며, 이를 상속이라고 합니다.
15.2) 기본 클래스와 파생 클래스
상속을 이용하면 일반적인 클래스에서 몇 가지 property나 behavior를 더해 특별한 클래스를 만들 수 있습니다. 기본클래스(base class); 부모클래스(parent class), 상위 클래스(super class)에서 상속받은 파생 클래스(derived class);자식 클래스(child class), 하위 클래스(sub class)를 만들 수 있습니다. 부모 클래스가 자식클래스보다 더 상위 클래스이지만 자식 클래스가 부모 클래스의 코드를 포함한 코드들을 가지고 있기 때문에 자식 클래스의 크기가 부모 클래스의 크기보다 큽니다.
class 자식클래스: public 부모클래스
로 상속할 수 있습니다.
①기본 클래스의 private 데이터 필드는 파생클래스에서 직접 사용할 수 없다.
기본 클래스의 private 데이터 필드는 파생클래스에서 직접 사용할 수 없습니다. 만약 기본 클래스에 정의된 데이터 필드를 사용하고 싶다면 접근자(get)나 변경자(set)을 사용하여 접근, 변경해야 합니다.
Circle::Circle(double radius, const string& color, bool filled) ▼
Circle::Circle(double radius, const string& color, bool filled) {
setRadius(radius);
setColor(color);
setFilled(filled);
}
Geometric의 데이터 필드는 자기 자신만 접근할 수 있는 private이므로 Circle객체에서 직접 접근할 수 없습니다. 따라서 아래와 같은 표현은 불가능합니다. Circle 객체에서 color와 filled를 읽고 쓰고 싶다면 public인 set함수와 get함수를 사용해야 합니다.
틀린 코드 ▼
Circle::Circle(double radius, const string& c, bool f) {
this->radius=radius; //옳은 표현
color =c; //틀린 표현
filled=f; //틀린 표현
}
② 상속은 is-a 관계를 표현하기 위해 사용됩니다.
③ c++은 다중 상속을 지원합니다.
④ #include "GeometricObject.h"는 Circle.h 에만 적어도 됩니다. Circle.cpp에 안 적어도 됩니다.
전체 코드 ▼
//filename.cpp
#include "GeometricObject.h"
#include "Rectangle.h"
#include "Circle.h"
#include <iostream>
using namespace std;
int main() {
GeometricObject shape;
shape.setColor("red");
shape.setFilled(true);
cout << shape.toString() << endl << " color: " << shape.getColor()
<< " filled: " << (shape.isFilled() ? "true" : "false") << endl << endl;
Circle circle(5);
circle.setColor("Black");
circle.setFilled(false);
cout << circle.toString() << endl << " color: " << circle.getColor()
<< " filled: " << (circle.isFilled() ? "true" : "false") << endl
<< " radius: " << circle.getRadius() << " area: " << circle.getArea()
<< " perimeter: " << circle.getPerimeter() << endl << endl;
Rectangle rectangle(2,3);
rectangle.setColor("orange");
rectangle.setFilled(true);
cout << rectangle.toString() << endl << " color: " << rectangle.getColor()
<< " filled: " << (rectangle.isFilled() ? "true" : "false") << endl
<< " width: " << rectangle.getWidth() <<" height: "<<rectangle.getHeight()
<<" area: " << rectangle.getArea()
<< " perimeter: " << rectangle.getPerimeter() << endl << endl;
return 0;
}
//GeometricObject.h
#ifndef GEOMETRICOBJECT_H
#define GEOMETRICOBJECT_H
#include <string>
using namespace std;
class GeometricObject {
private:
string color;
bool filled;
public:
GeometricObject();
GeometricObject(string color, bool filled);
string getColor() const;
void setColor(string color);
bool isFilled() const;
void setFilled(bool Filled);
string toString() const;
};
#endif
//GeometricObject.cpp
#include "GeometricObject.h"
GeometricObject::GeometricObject() {
color = "white";
filled = false;
}
GeometricObject::GeometricObject(string color, bool filled) {
this->color = color;
this->filled = filled;
}
string GeometricObject::getColor() const {
return color;
}
void GeometricObject::setColor(string color) {
this->color = color;
}
bool GeometricObject::isFilled() const { return filled; }
void GeometricObject::setFilled(bool Filled) { this->filled = filled; }
string GeometricObject::toString() const {
return "GeometricObject";
}
//Circle.h
#ifndef Circle_H
#define Circle_H
#include "GeometricObject.h"
class Circle: public GeometricObject
{
public:
Circle();
Circle(double);
Circle(double radius, const string& color, bool filled);
double getArea() const;
double getRadius() const;
void setRadius(double radius);
double getPerimeter() const;
double getDiameter() const;
string toString() const;
private:
double radius;
};
#endif
//Circle.cpp
#include "Circle.h"
double PI = 3.141592;
Circle::Circle() {
radius = 1;
}
Circle::Circle(double newRadius) {
radius = newRadius;
}
Circle::Circle(double radius, const string& color, bool filled) {
setRadius(radius);
setColor(color);
setFilled(filled);
}
double Circle::getArea() const {
return radius * radius * PI;
}
double Circle::getRadius() const {
return radius;
}
void Circle::setRadius(double radius) {
this->radius = (radius >= 0) ? radius : 0;
}
double Circle::getPerimeter() const {
return 2* radius * PI;
}
double Circle::getDiameter() const {
return 2 * radius;
}
string Circle::toString() const {
return "Circle object";
}
//Rectangle.h
#ifndef RECTANGLE_H
#define RECTANGLE_H
#include "GeometricObject.h"
class Rectangle: public GeometricObject {
private:
double width;
double height;
public:
Rectangle();
Rectangle(double width, double height);
Rectangle(double width, double height, const string& color, bool filled);
double getWidth() const;
void setWidth(double width);
double getHeight() const;
void setHeight(double height);
double getArea() const;
double getPerimeter() const;
string toString() const;
};
#endif
//Rectangle.cpp
#include "Rectangle.h"
Rectangle::Rectangle() {
width = 1;
height = 1;
}
Rectangle::Rectangle(double width, double height) {
setWidth(width);
setHeight(height);
}
Rectangle::Rectangle(double width, double height, const string& color, bool filled) {
setWidth(width);
setHeight(height);
setColor(color);
setFilled(filled);
}
double Rectangle::getWidth() const {
return width;
}
void Rectangle::setWidth(double width) {
this->width =(width>=0)? width:0;
}
double Rectangle::getHeight() const {
return height;
}
void Rectangle::setHeight(double height) {
this->height = (height >= 0) ? height : 0;
}
double Rectangle::getArea() const {
return width * height;
}
double Rectangle::getPerimeter() const {
return 2 * (width + height);
}
string Rectangle::toString() const {
return "Rectangle object";
}
15.3) 제너릭 프로그래밍
부모 클래스의 객체를 매개변수로 받도록 함수를 선언하면 자식 클래스의 객체도 인자로 넣을 수 있습니다.
예를들어 아래의 displayGeometricObject(e)함수는 GeometricObject객체와 Circle객체, Rectangle객체를 모두 인자로 받을 수 있습니다.
//함수 선언
void displayGeometricObject(const GeometricObject& shape)
{
cout << shape.getColor()<<endl;
}
//함수 사용
displayGeometricObject(GeometricObject("Black", true);
displayGeometricObject(Circle(5));
displayGeometricObject(Rectangle(2,3));
15.4) 생성자와 소멸자
15.4.1) 기본 클래스 생성자의 호출
① 데이터 필드와 함수와 달리 부모 클래스의 생성자는 파생 클래스로 상속되지 않습니다. 하지만 파생 클래스의 생성자는 항상 기본 클래스의 생성자를 호출하고 나서 실행됩니다.
② 기본 생성자가 암시적으로 표현되지 않으면 본 클래스의 인수 없는 생성자가 호출됩니다.
③ 파생 클래스의 생성자는 명시적으로나 암묵적으로나 부모 클래스의 생성자를 호출할 수 있습니다. 생성자의 초기화 목록으로부터 명시적으로 부모 클래스의 생성자를 호출하는 방법을 알아봅시다.
부모 클래스의 인수 없는 생성자 ▼
//암묵적으로 호출하면 Circle::Circle(double newRadius) { radius = newRadius; } |
//명시적으로 호출하면 Circle::Circle(double newRadius) : GeometricObject(){ radius = newRadius; } |
부모 클래스의 인수 있는 생성자▼
//암묵적으로 호출하면 Circle::Circle(double newRadius) { radius = newRadius; } |
//명시적으로 호출하면 Circle::Circle(double newRadius) : GeometricObject(color,filled){ radius = newRadius; } |
//명시적으로 호출하면 Circle::Circle(double newRadius) : GeometricObject(color,filled), radius(radius){ } |
④Circle에서 정의된 데이터 필드도 부모클래스의 생성자와 함께 초기화할 수 있습니다.
15.4.2) ① 생성자와 소멸자의 연쇄적 처리
인스턴스를 생성할 때, 파생 클래스A는 상속 연결을 따라 부모 클래스 B의 생성자를 먼저 호출합니다. 부모 클래스 B이 부모 클래스 C가 있다면, B의 생성자가 호출되기 전에 C의 생성자가 호출됩니다. C->B->A이를 생성자 연쇄적 처리(constructor chaining)이라고 합니다.
②반대로 파생 클래스의 소멸할 때는 파생클래스의 소멸자가 먼저 호출되고 기본 클래스의 소멸자를 호출합니다. 따라서 파생 클래스 A의 객체를 소멸시킬때, A->B->C 순으로 소멸자가 호출됩니다. 이를 소멸자 연쇄적 처리(destructor chaining)이라고 합니다.
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Performs tasks for Person's constructor" << endl;
}
~Person()
{
cout << "Performs tasks for Person's destructor" << endl;
}
};
class Employee : public Person
{
public:
Employee()
{
cout << "Performs tasks for Employee's constructor" << endl;
}
~Employee()
{
cout << "Performs tasks for Employee's destructor" << endl;
}
};
class Faculty: public Employee
{
public:
Faculty()
{
cout << "Performs tasks for Faculty's constructor" << endl;
}
~Faculty()
{
cout << "Performs tasks for Faculty's destructor" << endl;
}
};
int main()
{
Faculty faculty;
return 0;
}
⑤ 클래스를 확장가능하도록 설계했다면 프로그래밍 오류를 피하기 위해 인수없는 생성자를 제공하는 것이 바람직합니다.
15.8) 보호 키워드(protected)
기본 클래스의 protected키워드인 데이터필드는 자신과 파생 클래스만 접근이 가능합니다.
이와 같은 접근성 키워드(accessibility); 가시성(visibility) 키워드에는 private, protected, public이 있습니다.
#include <iostream>
using namespace std;
class B
{
public:
int i;
protected:
int j;
private:
int k;
};
class A: public B
{
public:
void display() const
{
cout <<<< i <<<< endl; // 접근 가능
cout <<< j <<< endl; // 접근 가능
cout <<< k <<< endl; // 접근 불가능
}
};
int main()
{
A a;
cout << a.i <<< endl; // 접근 가능
cout << a.j <<< endl; // 접근 불가능
cout << a.k <<< endl; // 접근 불가능
return 0;
}
checkpoint)
15.1 파생 클래스는 기본 클래스의 하위(부분) 집합이다. 참인가 혹은 거짓인가?
거짓, 실제로 파생 클래스는 기본 클래스보다 큼. 하위에 있을 뿐 부분 집합은 아님. 오히려 기본 클래스를 파생클래스의 부분 집합으로 볼 수 있음
15.2 C++에서 클래스는 여러 개의 기본 클래스로부터 파생될 수 있는가?
예, c++은 다중 상속을 지원한다.
15.3 다음 클래스에서 문제점을 찾아보아라.
class Circle
{
public:
Circle(double radius){ radius = radius; }
double getRadius(){ return radius; }
double getArea(){ return radius *radius* 3.14159; }
private:
double radius;
};
class B: Circle
{
public:
B(double radius, double length)
{
radius = radius;
length =length;
}
// 원의 getArea() * length 값 반환
double getArea() {return getArea() * length; }
private: double length;
};
Circle의 데이터 필드를 생성자Circle(double radius)가 변경하는 것이므로 this->radius = radius;로 바꾸어야 한다.
함수 B가 B 생성자를 통해 Circle의 데이터 필드를 바꾸고자 하는 것이므로 setRadius(radius);로 바꾸어야 한다.
this->length= length;도 바꿔줘야 한다.
class Circle
{
public:
Circle(double radius){ this->radius = radius; }
double getRadius(){ return radius; }
double getArea(){ return radius *radius* 3.14159; }
private:
double radius;
};
class B: Circle
{
public:
B(double radius, double length)
{
setRadius(radius);
this->length= length;
}
// 원의 getArea() * length 값 반환
double getArea() {return getArea() * length; }
private: double length;
};
15.4 파생 클래스에서 생성자를 호출할 때, 기본 클래스의 인수 없는 생성자가 항상 호출된 다. 참인가 혹은 거짓인가?
기본 생성자가 암시적으로 표현되지 않으면 본 클래스의 인수 없는 생성자가 호출됩니다.
15.5 (a) 프로그램의 실행 출력은 무엇인가? (b) 프로그램은 컴파일에서 어떤 문제점이 발생하는가?
//a
#include <iostream>
using namespace std;
class Parent
{
public:
Parent()
{
cout <<"Parent's no-arg constructor is invoked";
}
};
class Child: public Parent
{
};
int main()
{
Child c;
return 0;
}
Parent's no-arg constructor is invoked
//b
#include <iostream>
using namespace std;
class Parent
{
public:
Parent(int x){}
};
class Child: public Parent
{
};
int main()
{
Child c;
return 0;
}
명시적으로 기본 생성자를 표시하지 않은 Child생성자는 부모 클래스의 인수없는 생성자를 불러오려고 합니다. 하지만
Parent가 인수없는 생성자가 없기 때문에(명시적으로 인수없는 생성자를 표시하지 않고 인수있는 생성자를 만들어놓으면 인수없는 생성자가 자동으로 생기지 않는다.) 오류가 발생합니다.
Parent에 인수 없는 생성자를 만들어주어야 합니다.
15.6) 다음 코드의 출력은 무엇인가?
#include <iostream>
using namespace std;
class Parent
{
public:
Parent()
{
cout << "Parent's no-arg constructor is invoked" << endl;
}
~Parent()
{
cout << "Parent's destructor is invoked" << endl;
}
};
class Child: public Parent
{
public:
Child()
{
cout << "Child's no-arg constructor is invoked" << endl;
}
~Child()
{
cout << "Child's destructor is invoked" << endl;
}
};
int main()
{
Child c1;
Child c2;
return 0;
}
Parent's no-arg constructor is invoked
Child's no-arg constructor is invoked
Child's destructor is invoked
Parent's destructor is invoked
15.7) 만일 기본 클래스가 사용자 정의 복사 생성자와 대입 연산자를 갖는 경우, 파생 클래스 에서의 복사 생성자와 대입 연산자는 어떻게 정의해야 하는가?
기본 클래스에 있는 데이터 필드가 적합하게 복사되었음을 보장하기 위해 파생 클래스의 복사 생성자와 대입 연산자도 사용자 정의를 해주어야 한다.
Child::Child(const Child& object): Parent(object)
{ // Child에 있는 데이터 필드를 복사하기 위한 코드 작성
}
Child의 대입 연산자를 위한 코드는 일반적으로 다음과 같이 작성된다.
Child& Child::operator=(const Child& object)
{
Parent::operator(object)
// 기본 클래스에 있는 대입 연산자를 적용하기 위해 Parent::operator=(object) 사용
/ / Child에 있는 데이터 필드를 복사하기 위한 코드 작성
}
15.8) 만일 기본 클래스가 사용자 정의 소멸자를 갖는 경우, 파생 클래스에서 소멸자를 구현 해야 하는가?
파생 클래스의 소멸자가 호출될 때, 자동으로 기본 클래스의 소멸자가 호출된다. 파생 클래스의 소멸자는 파생 클래스에서 동적으로 생성된 메모리를 소멸시키기 위해서만 필요하다
15.17 만한가? 만일 클래스에서 전용(private)으로 선언된 경우, 다른 클래스에서 접근할 수 있는가? 아니오
만일 멤버가 클래스에서 보호(protected)로 선언된 경우, 다른 클래스에서 접근할 수 있는가? 그 클래스의 파생 클래스에서만 가능하다.
만약 멤버가 클래스에서 공용(public)으로 선언된 경우, 다른 클래스 에서 접근할 수 있는가? 네
'c, c++ > c++로 시작하는 객체지향 프로그래밍' 카테고리의 다른 글
13.1~ 13.5)파일 입력과 출력 (0) | 2024.05.24 |
---|---|
15.5~15.9) 상속과 "다형성" (0) | 2024.05.09 |
12.1~12.5 (2) | 2024.04.25 |
11.6~11.15 (0) | 2024.04.12 |
11.1~11.5 (1) | 2024.04.04 |