차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
양쪽 이전 판 이전 판 다음 판 | 이전 판 | ||
activity:public:2021:cpp:210803 [2021/08/03 21:41:27] – [예제] yhy01625 | activity:public:2021:cpp:210803 [2021/08/10 18:47:39] (현재) – [C++ 스터디 #12: 클래스(2)] bkparks12 | ||
---|---|---|---|
줄 1: | 줄 1: | ||
+ | ======C++ 스터디 #12: 클래스(2)====== | ||
+ | | 시간 | 2021년 8월 2일 화요일 20:00 ~ 22:00 | | ||
+ | | 장소 | ZOOM | | ||
+ | | 참가자 | - | | ||
+ | {{youtube> | ||
+ | =====1. 소멸자===== | ||
+ | 생성된 객체가 범위를 벗어날때 소멸자를 호출한다. 소멸자는 '' | ||
+ | 소멸자는 파일을 닫거나 메모리를 반환하는 작업과 같이 프로그램을 종료하기 전 자원을 반납하는데 사용된다. \\ | ||
+ | |||
+ | <sxh cpp> | ||
+ | |||
+ | |||
+ | class MyString | ||
+ | { | ||
+ | private: | ||
+ | char* s; | ||
+ | int size; | ||
+ | |||
+ | public: | ||
+ | MyString(char* c) | ||
+ | { | ||
+ | size = strlen(c) + 1; | ||
+ | s = new char[size]; | ||
+ | strcpy(s, c); | ||
+ | } | ||
+ | ~MyString() | ||
+ | { | ||
+ | delte[]s; | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | 여기서 ~MyString이 소멸자이다. | ||
+ | |||
+ | =====2. 객체와 함수===== | ||
+ | ====2.1. 객체를 함수로 전달하기==== | ||
+ | |||
+ | <sxh cpp> | ||
+ | # | ||
+ | using namespace std; | ||
+ | |||
+ | class circle | ||
+ | { | ||
+ | public: | ||
+ | circle(int s) : size(s) {} | ||
+ | int size; | ||
+ | }; | ||
+ | |||
+ | void makedouble(circle c) | ||
+ | { | ||
+ | c.size *= 2; | ||
+ | } | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | circle circle1(10); | ||
+ | makedouble(circle1); | ||
+ | cout << circle1.size << endl; | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | 결과 : 10\\ | ||
+ | |||
+ | 이 결과를 보면 '' | ||
+ | 이때 makedouble()의 매개 변수 c는 생성될 때 다른 객체의 내용을 복사하여서 생성된다. 따라서 일반적인 생성자가 호출되는 것이 아니라 '' | ||
+ | 이 방법은 함수에서 매개 변수를 변경하더라도 원본 객체에 영향이 없어 안전성이 높다는 장점이 있다. \\ | ||
+ | |||
+ | |||
+ | ====2.2. 참조자 매개 변수 사용하기==== | ||
+ | <sxh cpp> | ||
+ | # | ||
+ | using namespace std; | ||
+ | |||
+ | class circle | ||
+ | { | ||
+ | public: | ||
+ | circle(int s) : size(s) {} | ||
+ | int size; | ||
+ | }; | ||
+ | |||
+ | void makedouble(circle& | ||
+ | { | ||
+ | c.size *= 2; | ||
+ | } | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | circle circle1(10); | ||
+ | makedouble(circle1); | ||
+ | cout << circle1.size << endl; | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | 결과 : 20\\ | ||
+ | 반면에 참조자를 통하여 객체를 변경하면 원래의 객체를 변경하는 것이다. 따라서 20이라는 결과가 나온다. \\ | ||
+ | 일반적으로 객체의 크기가 큰 경우 객체를 복사하는 시간이 오래 걸리기 때문에 객체의 참조자를 전달하는 편이 효율적이다. \\ | ||
+ | |||
+ | ====2.3. 객체의 주소를 함수로 전달하기==== | ||
+ | <sxh cpp> | ||
+ | # | ||
+ | using namespace std; | ||
+ | |||
+ | class circle | ||
+ | { | ||
+ | public: | ||
+ | circle(int s) : size(s) {} | ||
+ | int size; | ||
+ | }; | ||
+ | |||
+ | void makedouble(circle *c) | ||
+ | { | ||
+ | c->size *= 2; | ||
+ | } | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | circle circle1(10); | ||
+ | makedouble(& | ||
+ | cout << circle1.size << endl; | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | 결과 : 20\\ | ||
+ | 이 방법은 2.2.와 동일한 효과를 가진다. | ||
+ | ====2.4. 함수가 객체 반환하기==== | ||
+ | <sxh cpp> | ||
+ | # | ||
+ | using namespace std; | ||
+ | |||
+ | class Circle | ||
+ | { | ||
+ | public: | ||
+ | Circle(int s) : size(s) { } | ||
+ | int size; | ||
+ | }; | ||
+ | |||
+ | Circle createCircle() | ||
+ | { | ||
+ | Circle c(10); | ||
+ | return c; | ||
+ | } | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | Circle Circle1 = createCircle(); | ||
+ | cout << Circle1.size << endl; | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | 결과 : 10 \\ | ||
+ | 함수가 객체를 반환할 때도 객체의 내용이 복사될 뿐 원본은 전달되지 않는다. 이 경우 ' | ||
+ | =====복사 생성자===== | ||
+ | 이렇게 객체를 복사하여 객체를 생성할 때 사용하는 생성자인 복사생성자에 대해 알아보자. \\ | ||
+ | 1. 같은 종류의 객체로 초기화하는 경우 | ||
+ | <sxh cpp> | ||
+ | Myclass obj(obj2); // 여기서 복사 생성자 호출 | ||
+ | </ | ||
+ | 2. 객체를 함수에 전달하는 경우 | ||
+ | <sxh cpp> | ||
+ | Myclass func(Myclass obj) // 여기서 복사 생성자 호출 | ||
+ | {.... | ||
+ | } | ||
+ | </ | ||
+ | 3. 함수가 객체를 반환하는 경우 | ||
+ | <sxh cpp> | ||
+ | Myclass func(myclass obj) | ||
+ | { | ||
+ | Myclass tmp; | ||
+ | ... | ||
+ | return tmp; // 여기서 복사 생성자 호출 | ||
+ | } | ||
+ | </ | ||
+ | 이해하기 쉽도록 다음 예시를 살펴보자. | ||
+ | <sxh cpp> | ||
+ | # | ||
+ | using namespace std; | ||
+ | |||
+ | class Person | ||
+ | { | ||
+ | public : | ||
+ | int age; | ||
+ | Person(int a) : age(a) { } | ||
+ | }; | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | Person A(21); | ||
+ | Person clone(A); // 복사 생성자 호출 | ||
+ | |||
+ | cout << "A의 나이 : " << A.age << ", clone의 나이 : " << clone.age << endl; | ||
+ | |||
+ | A.age = 25; | ||
+ | |||
+ | cout << "A의 나이 : " << A.age << ", clone의 나이 : " << clone.age << endl; | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 이 예시에서 A의 나이를 25로 바꿔도 clone은 A를 복사한 것이기 때문에 변화가 없다. | ||
+ | |||
+ | =====예제===== | ||
+ | Circle 클래스의 반지름을 교환하는 swap 함수를 작성해보자. 원본 객체를 받아서 실제로 객체의 내용이 교환되어야 한다. \\ | ||
+ | |||
+ | 메인함수 | ||
+ | <sxh cpp> | ||
+ | int main() | ||
+ | { | ||
+ | Circle circle1(5); | ||
+ | Circle circle2(3); | ||
+ | | ||
+ | cout << " | ||
+ | |||
+ | swap(circle1, | ||
+ | |||
+ | cout << " | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | 출력 예시 :\\ | ||
+ | circle1 넓이 : 78.5, circle2 넓이 : 28.26\\ | ||
+ | circle1 넓이 : 28.26, circle2 넓이 : 78.5 | ||
+ | =====3. 클래스 안에 객체 포함하기===== | ||
+ | 객체 지향 프로그래밍에서는 하나의 객체 안에 다른 객체가 포함될 수 있다. 다음 예를 살펴보자. | ||
+ | <sxh cpp> | ||
+ | # | ||
+ | # | ||
+ | using namespace std; | ||
+ | |||
+ | class Date | ||
+ | { | ||
+ | int year, month, day; | ||
+ | public : | ||
+ | Date(int y, int m, int d) : year(y), month(m), day(d) { } | ||
+ | void print() | ||
+ | { | ||
+ | cout << year << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Person | ||
+ | { | ||
+ | string name; | ||
+ | Date birth; | ||
+ | public : | ||
+ | Person(string n, Date d) : name(n), birth(d) { } | ||
+ | void print() | ||
+ | { | ||
+ | cout << name << " : "; | ||
+ | birth.print(); | ||
+ | cout << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | Date d(2001, 6, 25); | ||
+ | Person p(" | ||
+ | p.print(); | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | 이 예시에서 Person 클래스 안에 Date 클래스를 포함하고 있는 것을 볼 수 있다. | ||
+ | =====4. 정적 변수===== | ||
+ | 정적 변수는 클래스안에서만 사용되는 전역 변수라고 생각하면 된다. 정적 변수는 앞에 '' | ||
+ | <sxh cpp> | ||
+ | class Circle | ||
+ | { | ||
+ | int x, y; | ||
+ | int radius; | ||
+ | static int count; | ||
+ | |||
+ | public : | ||
+ | Circle() : x(0), y(0), radius(0) | ||
+ | { | ||
+ | count++; | ||
+ | } | ||
+ | Circle(int x, int y, int r) : x(x), y(y), radius(r) | ||
+ | { | ||
+ | count++; | ||
+ | } | ||
+ | }; | ||
+ | int Circle :: count = 0; | ||
+ | </ | ||
+ | =====5. 프렌드 함수와 프렌드 클래스===== | ||
+ | '' | ||
+ | <sxh cpp> | ||
+ | # | ||
+ | # | ||
+ | using namespace std; | ||
+ | |||
+ | class A | ||
+ | { | ||
+ | public : | ||
+ | friend class B; | ||
+ | A(string s = "" | ||
+ | |||
+ | private : | ||
+ | string secret; | ||
+ | }; | ||
+ | |||
+ | class B | ||
+ | { | ||
+ | public : | ||
+ | B() { } | ||
+ | void print(A obj) | ||
+ | { | ||
+ | cout << obj.secret << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | A a(" | ||
+ | B b; | ||
+ | b.print(a); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | 결과 : 기밀 정보\\ | ||
+ | 이처럼 A의 friend 클래스 B는 A의 private 멤버에도 접근 할 수 있다. | ||
+ | \\ | ||
+ | |||
+ | =====6. 상속===== | ||
+ | ====6.1. 상속이란? | ||
+ | 상속이란 기존에 존재하는 클래스로부터 모든 멤버를 이어받아 새로운 클래스를 만드는 것이다.\\ | ||
+ | 이미 존재하던 클래스를 기초 클래스 혹은 부모 클래스, 상위 클래스라고 하고 상속받는 클래스를 파생 클래스 또는 자식 클래스, 하위 클래스라고 한다.\\ | ||
+ | 다음은 상속에 사용되는 구문이다. | ||
+ | <sxh cpp> | ||
+ | class <자식 클래스> | ||
+ | · · · // 추가된 멤버 변수와 멤버 함수 | ||
+ | } | ||
+ | </ | ||
+ | 대개 부모 클래스는 추상적이고 자식 클래스는 구체적이다. | ||
+ | ^ 부모 클래스 ^ 자식 클래스 ^ | ||
+ | | Animal | Dog, Cat, Tiger | | ||
+ | | Vehicle | Car, Bus, Truck, Boat, Bicycle | | ||
+ | | Shape | Rectangle, Triangle, Circle | | ||
+ | \\ | ||
+ | 부모 클래스의 멤버가 자식 클래스로 상속되어 자식 클래스는 부모 클래스의 멤버 변수와 멤버 함수를 자유롭게 사용할 수 있고, 필요하다면 자식 클래스만의 변수와 함수를 추가시킬 수도 있으며 이미 존재하는 멤부를 재정의할 수도 있다.\\ | ||
+ | 상속의 강점은 부모 클래스로부터 상속된 특징을 자식 클래스에서 추가, 교체, 상세화시킬 수 있다는 점이다.\\ | ||
+ | |||
+ | ====6.2. 필요성==== | ||
+ | 상속을 사용하면 중복되는 코드를 줄일 수 있다.\\ | ||
+ | Car, Bicycle 클래스는 공통적으로 speed, setSpeed(), getSpeed() 등의 멤버 변수와 멤버 함수를 갖는다. | ||
+ | 이 클래스들을 개별적으로 작성하면 다음과 같다. | ||
+ | <sxh cpp> | ||
+ | class Car { | ||
+ | int speed; | ||
+ | public: | ||
+ | void setSpeed(int s) { speed = s; } | ||
+ | int getSpeed() { return speed; } | ||
+ | }; | ||
+ | |||
+ | class Bicycle { | ||
+ | int speed; | ||
+ | public: | ||
+ | void setSpeed(int s) { speed = s; } | ||
+ | int getSpeed() { return speed; } | ||
+ | }; | ||
+ | </ | ||
+ | Vehicle이라는 부모 클래스를 작성하여 상속을 사용하면 다음과 같다. | ||
+ | <sxh cpp> | ||
+ | class Vehicle { | ||
+ | int speed; | ||
+ | public: | ||
+ | void setSpeed(int s) { speed = s; } | ||
+ | int getSpeed() { return speed; } | ||
+ | }; | ||
+ | |||
+ | class Car : public Vehicle { }; | ||
+ | class Bicycle : public Vehicle { }; | ||
+ | </ | ||
+ | 코드가 보다 간결해진 것을 볼 수 있다. 상속의 장점은 코드가 길어질수록 더 발휘된다.\\ | ||
+ | 중복되는 코드를 부모 클래스에 모으면 하나로 정리되어 관리하기나 유지 보수 및 변경이 수월해진다.\\ | ||
+ | |||
+ | ====예제==== | ||
+ | 아래 코드가 올바르게 실행되도록 Shape 클래스를 상속받아 Rectangle 클래스 작성하기 | ||
+ | * Shape 클래스는 멤버 변수로 x, y를 갖고 있음 | ||
+ | * Shape 클래스는 멤버 함수로 printLocation()을 갖고 있음 | ||
+ | * Rectangle 클래스는 멤버 변수로 width와 height를 추가로 갖고 있음 | ||
+ | * Rectangle 클래스는 멤버 함수로 printArea()를 추가로 갖고 있음 | ||
+ | * 모든 멤버는 '' | ||
+ | |||
+ | <sxh cpp> | ||
+ | int main() { | ||
+ | Rectangle r; | ||
+ | r.x = 3; | ||
+ | r.y = 6; | ||
+ | r.width = 2; | ||
+ | r.height = 8; | ||
+ | r.printLocation(); | ||
+ | r.printArea(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Location: (3, 6) | ||
+ | Area: 16 | ||
+ | |||
+ | **예시 답안** | ||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Shape { | ||
+ | public: | ||
+ | int x, y; | ||
+ | void printLocation() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Rectangle : public Shape { | ||
+ | public: | ||
+ | int width, height; | ||
+ | void printArea() { | ||
+ | cout << "Area: " << width * height << endl; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Rectangle r; | ||
+ | r.x = 3; | ||
+ | r.y = 6; | ||
+ | r.width = 2; | ||
+ | r.height = 8; | ||
+ | r.printLocation(); | ||
+ | r.printArea(); | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | ====6.3. 상속과 생성자/ | ||
+ | 상속된 자식 클래스의 객체가 생성될 때에는 부모 클래스의 생성자가 먼저 호출된 후 자식 클래스의 생성자가 호출된다.\\ | ||
+ | 특별히 지정하지 않으면 부모 클래스의 기본 생성자가 호출된다.\\ | ||
+ | 반대로 객체가 소멸될 때에는 자식 클래스의 소멸자가 먼저 호출된 뒤 부모 클래스의 소멸자가 호출된다.\\ | ||
+ | 다음 코드를 실행하여 생성자와 소멸자의 호출 순서를 확인해보자. | ||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Shape { | ||
+ | public: | ||
+ | Shape() { cout << " | ||
+ | ~Shape() { cout << " | ||
+ | }; | ||
+ | |||
+ | class Circle : public Shape { | ||
+ | public: | ||
+ | Circle() { cout << " | ||
+ | ~Circle() { cout << " | ||
+ | }; | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | Circle c; | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Shape() | ||
+ | Circle() | ||
+ | ~Circle() | ||
+ | ~Shape() | ||
+ | \\ | ||
+ | 부모 클래스의 생성자를 지정하지 않으면 기본 생성자가 호출된다.\\ | ||
+ | 매개 변수가 있는 생성자를 호출하려면 자식 클래스의 생성자 헤더 뒤에 콜론을 추가하여 원하는 부모 클래스의 생성자를 적어주면 된다. | ||
+ | <sxh cpp> | ||
+ | <자식 클래스의 생성자> | ||
+ | · · · | ||
+ | } | ||
+ | </ | ||
+ | \\ | ||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Shape { | ||
+ | int x = 0, y = 0; | ||
+ | public: | ||
+ | Shape() { cout << " | ||
+ | Shape(int xloc, int yloc) : x(xloc), y(yloc) { | ||
+ | cout << " | ||
+ | } | ||
+ | ~Shape() { cout << " | ||
+ | }; | ||
+ | |||
+ | class Circle : public Shape { | ||
+ | int radius; | ||
+ | public: | ||
+ | Circle(int x, int y, int r) : Shape(x, y), radius(r) { | ||
+ | cout << " | ||
+ | } | ||
+ | ~Circle() { cout << " | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Circle c(0, 0, 1); | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Shape(0, 0) | ||
+ | Circle(0, 0, 1) | ||
+ | ~Circle() | ||
+ | ~Shape() | ||
+ | \\ | ||
+ | |||
+ | ====예제==== | ||
+ | Shape 클래스를 상속받아 Rectangle 클래스 작성하기 | ||
+ | * Shape 클래스는 멤버 변수로 x, y를 갖고 있음 | ||
+ | * Rectangle 클래스는 멤버 변수로 width와 height를 추가로 갖고 있음 | ||
+ | * 각각 생성자와 소멸자 작성하기 | ||
+ | |||
+ | <sxh cpp> | ||
+ | int main() { | ||
+ | Rectangle(2, | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Shape(2, -4) | ||
+ | Rectangle(2, | ||
+ | ~Rectangle() | ||
+ | ~Shape() | ||
+ | |||
+ | **예시 답안** | ||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Shape { | ||
+ | private: | ||
+ | int x, y; | ||
+ | public: | ||
+ | Shape(int locX, int locY) : x(locX), y(locY) { | ||
+ | cout << " | ||
+ | } | ||
+ | ~Shape() { cout << " | ||
+ | }; | ||
+ | |||
+ | class Rectangle : public Shape { | ||
+ | private: | ||
+ | int width, height; | ||
+ | public: | ||
+ | Rectangle(int locX, int locY, int w, int h) : Shape(locX, locY), width(w), height(h) { | ||
+ | cout << " | ||
+ | } | ||
+ | ~Rectangle () { cout << " | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Rectangle r(2, -4, 3, 5); | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | ====6.4. 접근 지정자==== | ||
+ | 클래스에서 멤버 변수들은 보통 '' | ||
+ | 이때 사용하는 접근 지정자가 바로 '' | ||
+ | |||
+ | ^ 접근 지정자 ^ 자기 클래스 ^ 자식 클래스 ^ 외부 ^ | ||
+ | | private | ○ | ⨉ | ⨉ | | ||
+ | | protected | ○ | ○ | ⨉ | | ||
+ | | public | ○ | ○ | ○ | | ||
+ | \\ | ||
+ | |||
+ | ====6.5. 멤버 함수 재정의==== | ||
+ | 멤버 함수 재정의란 멤버 함수의 헤더는 그대로 두고 몸체만을 교체하는 것이다.\\ | ||
+ | 멤버 함수의 이름, 반환형, 매개 변수의 개수와 자료형이 모두 일치해야 재정의가 일어난다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Dog : public Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Dog d; | ||
+ | d.speak(); | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 멍멍 | ||
+ | \\ | ||
+ | 위 코드에서 d.speak(); | ||
+ | Dog 객체를 통해 접근하면 Dog 클래스의 멤버 함수에 우선권이 있어 재정의된 함수가 실행된 것이다.\\ | ||
+ | 재정의된 함수가 아니라 부모 클래스의 함수를 실행하고 싶다면 범위 연산자 ''::'' | ||
+ | |||
+ | <sxh cpp> | ||
+ | int main() { | ||
+ | Dog d; | ||
+ | d.Animal:: | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 동물 소리 | ||
+ | \\ | ||
+ | |||
+ | <fs large> | ||
+ | \\ | ||
+ | 중복 정의는 같은 이름의 함수를 여러 개 정의하는 것이고 재정의는 상속받은 멤버 함수의 내용을 변경하는 것이다.\\ | ||
+ | |||
+ | ====6.6. 상속 접근 지정자==== | ||
+ | 앞서 수행했던 상속은 모두 '' | ||
+ | |||
+ | ^ 상속 접근 지정자 ^ public ^ protected ^ private ^ | ||
+ | | 부모 클래스의 public 멤버 -> | public | protected | private | | ||
+ | | 부모 클래스의 protected 멤버 -> | protected | protected | private | | ||
+ | | 부모 클래스의 private 멤버 -> | 접근 불가 | 접근 불가 | 접근 불가 | | ||
+ | |||
+ | 상속된 클래스의 멤버의 접근 수준이 상속 접근 지정자의 접근 수준보다 낮으면 해당 상속 접근 지정자로 덮어 씌워진다고 생각하면 된다.\\ | ||
+ | 상속 접근 지정자를 따로 지정하지 않을 경우 클래스에서 접근 지정자를 특별히 표기하지 않았을 시 '' | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace; | ||
+ | |||
+ | class Base { | ||
+ | public: int a; | ||
+ | protected: int b; | ||
+ | private: int c; | ||
+ | }; | ||
+ | |||
+ | class Derived : Base { | ||
+ | // a는 사용 가능, private | ||
+ | // b는 사용 가능, private | ||
+ | // c는 사용 불가 | ||
+ | } | ||
+ | |||
+ | int main() { | ||
+ | Base baseObj; | ||
+ | Derived derivedObj; | ||
+ | |||
+ | cout << baseObj.a; | ||
+ | cout << baseObj.b; | ||
+ | cout << baseObj.c; | ||
+ | cout << derivedObj.a; | ||
+ | cout << derivedObj.b; | ||
+ | cout << derivedObj.c; | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | ====6.7. 다중 상속==== | ||
+ | 다중 상속이란 말 그대로 두 개 이상의 부모 클래스로부터 상속받는 것을 뜻한다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | class Sub : public Sup1, public Sup2 { | ||
+ | · · · | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | 다중 상속을 사용할 때의 문제점이 하나 있는데, 바로 상속해주는 서로 다른 클래스에 똑같은 이름을 가진 멤버가 있을 경우 일반적인 접근으로는 해당 멤버를 사용할 수 없다는 것이다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Sup1 { public: int x = 1; }; | ||
+ | class Sup2 { public: int x = 2; }; | ||
+ | class Sub : public Sup1, public Sup2 { }; | ||
+ | |||
+ | int main() { | ||
+ | Sub s; | ||
+ | cout << s.x; // ' | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 이를 해결하려면 가리키는 대상이 모호하지 않게 해주면 된다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | int main() { | ||
+ | Sub s; | ||
+ | cout << s.Sup1:: | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | \\ | ||
+ | |||
+ | |||
+ | =====7. 다형성===== | ||
+ | ====7.1. 다형성이란? | ||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Dog : public Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Cat : public Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Dog d; | ||
+ | Cat c; | ||
+ | d.speak(); | ||
+ | c.speak(); | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 멍멍 | ||
+ | 야옹 | ||
+ | |||
+ | 위 코드를 보면 개와 고양이 객체에서 똑같은 speak() 함수를 실행시켰는데 서로 다른 결과를 얻었다.\\ | ||
+ | 이처럼 동일한 코드를 사용하지만 객체의 타입이 다르면 서로 다른 결과를 얻는 것이 다형성이다.\\ | ||
+ | **<color # | ||
+ | ====7.2. 상향 형변환==== | ||
+ | 다형성은 객체 포인터로 수행된다. Animal 클래스에서 상속받은 Dog 클래스를 생각해보자. | ||
+ | |||
+ | <sxh cpp> | ||
+ | Animal* p = new Dog(); | ||
+ | </ | ||
+ | |||
+ | Animal 타입의 포인터로 Dog 타입의 객체를 가리키는 이 코드는 놀랍게도 정상적으로 기능한다.\\ | ||
+ | 자식 객체는 부모 객체를 포함하고 있기 때문에 부모 포인터로 자식 객체를 가리킬 수 있는 것이다.\\ | ||
+ | 이를 상향 형변환이라고 한다.\\ | ||
+ | 다만, 부모 포인터로 가리키고 있기 때문에 이 방법으로는 자식 클래스 중에서 부모에게 상속받은 부분만을 사용할 수 있다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Dog : public Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Animal* p = new Dog(); | ||
+ | p-> | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 동물 소리 | ||
+ | |||
+ | ====7.3. 가상 함수==== | ||
+ | 위의 코드에서 Animal 포인터로 접근하더라도 객체의 종류에 따라 speak() 함수가 다르게 실행된다면 좋을 것이다. 이는 부모 클래스의 멤버 함수를 '' | ||
+ | |||
+ | <sxh cpp; highlight: | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Animal { | ||
+ | public: | ||
+ | virtual void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Dog : public Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Cat : public Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Animal* p = new Dog(); | ||
+ | p-> | ||
+ | p = new Cat(); | ||
+ | p-> | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 멍멍 | ||
+ | 야옹 | ||
+ | Animal 형의 포인터로 서로 다른 형의 객체를 가리켰고, | ||
+ | |||
+ | ====7.4. 참조자와 가상 함수==== | ||
+ | 참조자를 통해서도 다형성을 수행할 수 있다.\\ | ||
+ | 포인터를 사용하여 다형성을 수행할 때처럼 부모 클래스의 참조자로 자식 클래스를 참조할 수 있고 가상 함수 또한 기능한다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Animal { | ||
+ | public: | ||
+ | virtual void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | class Dog : public Animal { | ||
+ | public: | ||
+ | void speak() { | ||
+ | cout << " | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Dog d; | ||
+ | Animal &a = d; | ||
+ | a.speak(); | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 멍멍 | ||
+ | \\ | ||
+ | |||
+ | ====7.5. 가상 소멸자==== | ||
+ | 포인터로 다형성을 수행하고 나서 객체를 삭제해보자. | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Sup { | ||
+ | public: | ||
+ | ~Sup() { cout << " | ||
+ | }; | ||
+ | |||
+ | class Sub : public Sup { | ||
+ | public: | ||
+ | ~Sub() { cout << " | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Sup* p = new Sub(); | ||
+ | delete p; | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ~Sup() | ||
+ | |||
+ | 자식 객체를 생성하여 부모 포인터로 가리킨 후 객체를 삭제하면 자식 소멸자가 호출되지 않고 부모 소멸자만 호출된다.\\ | ||
+ | 자식 소멸자도 호출되게 하려면 부모 클래스의 소멸자를 가상 함수로 선언하면 된다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Sup { | ||
+ | public: | ||
+ | virtual ~Sup() { cout << " | ||
+ | }; | ||
+ | |||
+ | class Sub : public Sup { | ||
+ | public: | ||
+ | ~Sub() { cout << " | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Sup* p = new Sub(); | ||
+ | delete p; | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ~Sub() | ||
+ | ~Sup() | ||
+ | \\ | ||
+ | |||
+ | ====7.6. 순수 가상 함수==== | ||
+ | 순수 가상 함수는 함수의 정의가 없고 선언만 된 함수이다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | virtual < | ||
+ | </ | ||
+ | |||
+ | 순수 가상 함수를 갖고 있는 클래스를 추상 클래스라고 한다.\\ | ||
+ | 추상 클래스는 추상적인 개념을 나타내거나 클래스 간 인터페이스를 나타내는 용도로 사용된다.\\ | ||
+ | 추상 클래스로는 객체를 만들 수 없고 상속으로만 사용된다.\\ | ||
+ | 또, 함수 정의가 없기에 상속받은 자식 클래스는 순수 가상 함수를 재정의해야 한다. | ||
+ | |||
+ | <sxh cpp> | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Animal { | ||
+ | public: | ||
+ | virtual void speak() = 0; | ||
+ | }; | ||
+ | |||
+ | class Dog : public Animal { | ||
+ | public: | ||
+ | void speak() { cout << " | ||
+ | }; | ||
+ | |||
+ | class Cat : public Animal { | ||
+ | public: | ||
+ | void speak() { cout << " | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Animal* p = new Dog(); | ||
+ | p-> | ||
+ | p = new Cat(); | ||
+ | p-> | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 멍멍 | ||
+ | 야옹 | ||
+ | |||
+ | 추상 클래스를 사용하지 않고 각 클래스별로 멤버 함수를 정의하여 사용할 수도 있다.\\ | ||
+ | 하지만 그럴 경우 데이터가 커지게 되면 몇 가지 공통 요소를 빠트릴 수 있다.\\ | ||
+ | 추상 클래스를 사용하게 되면 함수를 꼭 재정의해야 하기 때문에 이러한 실수를 미연에 방지할 수 있다.\\ | ||
+ | 또, 추상 클래스를 정의할 때에는 멤버 변수 없이 온전히 순수 가상 함수로만 이뤄져야 좋다.\\ | ||
+ | |||
+ | ====예제==== | ||
+ | Shape 클래스를 상속받아 Circle, Rectangle, Triangle 클래스 작성하여 다형성 수행하기 | ||
+ | * Shape 클래스는 멤버 변수로 x, y를 갖고 있음 | ||
+ | * Shape 클래스는 가상 함수로 getArea()를 갖고 있음 | ||
+ | * 아래 본문을 실행하여 올바른 결과가 나오도록 클래스 부분 작성하기 | ||
+ | |||
+ | <sxh cpp> | ||
+ | int main() { | ||
+ | Shape* s = new Circle(0, 4, 5); | ||
+ | cout << " | ||
+ | s = new Rectangle(3, | ||
+ | cout << " | ||
+ | s = new Triangle(2, 5, 7, 3); | ||
+ | cout << " | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Circle : 78.5398 | ||
+ | Rectangle : 24 | ||
+ | Triangle : 10.5 | ||
+ | |||
+ | **예시 답안** | ||
+ | <sxh cpp> | ||
+ | #define _USE_MATH_DEFINES | ||
+ | #include < | ||
+ | #include < | ||
+ | using namespace std; | ||
+ | |||
+ | class Shape { | ||
+ | private: | ||
+ | int x, y; | ||
+ | public: | ||
+ | Shape(int locX, int locY) : x(locX), y(locY) {} | ||
+ | virtual double getArea() = 0; | ||
+ | }; | ||
+ | |||
+ | class Circle : public Shape { | ||
+ | private: | ||
+ | int radius; | ||
+ | public: | ||
+ | Circle(int locX, int locY, int r) : Shape(locX, locY), radius(r) {} | ||
+ | double getArea() { return radius * radius * M_PI; } | ||
+ | }; | ||
+ | |||
+ | class Rectangle : public Shape { | ||
+ | private: | ||
+ | int width, height; | ||
+ | public: | ||
+ | Rectangle(int locX, int locY, int w, int h) : Shape(locX, locY), width(w), height(h) {} | ||
+ | double getArea() { return width * height; } | ||
+ | }; | ||
+ | |||
+ | class Triangle : public Rectangle { | ||
+ | public: | ||
+ | Triangle(int locX, int locY, int w, int h) : Rectangle(locX, | ||
+ | double getArea() { return Rectangle:: | ||
+ | }; | ||
+ | |||
+ | int main() { | ||
+ | Shape* s = new Circle(0, 4, 5); | ||
+ | cout << " | ||
+ | s = new Rectangle(3, | ||
+ | cout << " | ||
+ | s = new Triangle(2, 5, 7, 3); | ||
+ | cout << " | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ |