저는 항상 기록에 대한 집착이 있어왔는데요,

대부분은 이것들을 '손'으로 직접 작성하는 것을 선호했습니다.

 

하지만 아무래도 글을 쓰는 속도라던지, 내용을 검색하는 것에 있어 너무 한계가 있는 것 같아 티스토리 블로그를 통해

재테크와 관련하여 공부해가는 내용들을 정리해보고자 합니다 :)

 

오늘을 기점으로 공부한 내용들을 블로그에 착착 정리해 저만의 데이터베이스를 만들 수 있도록 해봐야겠습니다 ㅎㅎ

안녕하세요

 

요즘 코로나로 인해서

집에 있는 시간이 많아져서

 

남는 여가시간

무엇을 할지 몰라

심심하게 시간을

보내시는 분들이

많으리라 생각합니다.

 

그래서 앞으로는 제가

정말 재미있게 보았고,

평점좋은 영화들을 선별해서

추천을 해드리려 합니다.

 

이번 첫 글

제임스 맥어보이

극찬을 받았던 영화

23 아이덴티티 입니다.

 

 

 

30초 짜리 예고편

보셔도 대충 어떤 영화인지

이 오시리라 생각합니다.

 

 

 

 

 

 

23 아이덴티티스릴러 장르의 영화입니다.

(공포는 딱히 모르겠네요.. 저는 공포 = 귀신 이라서 ㅎㅎ)

 

 

 

 

평점 리뷰를 보니 정말 공감됩니다.

저도 23 아이덴티티를 보고 나서

안야 테일러 조이 

그리고

제임스 맥어보이

엄청난 팬이 되었거든요

 

어색함이라고는 1도 찾아볼 수 없는

두 배우의 연기였습니다.

 

 

 

 

23 아이덴티티 제목

'23개의 정체성' 입니다.

 

23 아이덴티티에서

제임스 맥어보이가 연기한

주인공내면

23개의 인격체가 들어있는 것인데요.

 

 

 

 

영화초반에 등장하는

인격체 1

 

 

 

 

인격체 2

 

 

 

 

어린아이의 인격체..

 

 

 

 

여자 인격체

 

이런 식으로 총 23개

다른 사람이 들어있습니다.

 

 

 

 

 

영화를 보면 정말

인격이 바뀔 때마다

제임스 맥어보이

전혀 다른 사람

연기한다느낌을 받았습니다.

 

23 아이덴티티를 보고

제임스 맥어보이를 처음으로

알게 되었는데요.

 

정말 연기력

대단한 배우라고 느꼈습니다.

 

 

 

 

 

제임스 맥어보이의 

23개의 다중인격 캐릭터

비하인드 영상이라고 하네요.

 

 

 

 

그리고 23 아이덴티티에서

제임스 맥어보이 만큼이나

존재감을 보이는

안야 테일러 조이 인데요.

 

안야 테일러 조이

제임스 맥어보이에게 쫓기는

장면에서 정말 긴장감이 넘쳤었습니다.

 

 

 

 

제임스 맥어보이와 

안야 테일러 조이

연기 캐미가 대단했습니다.

 

두 연기천재가 만난 듯한

기분이 들더라구요.

 

아니나 다를까

23 아이덴티티 이후로

두 배우 모두

승승장구한 것으로 알고있고,

 

특히 안야 테일러 조이

네임 밸류가 엄청나게 올랐죠.

 

 

 

 

안야 테일러 조이옆모습

정말 매력적입니다.

 

이목구비일반적으로 봤던

미국 영화여배우 얼굴과는

다소 다른 특이한 매력

있는 것 같습니다.

 

 

23 아이덴티티

메인 예고편 영상입니다.

 

 

 

 

 

여러분들께서도 이 영화를 보시고

제임스 맥어보이

안야 테일러 조이에 대해

알게 되셨으면 좋겠어요.

 

너무 훌륭한 두 배우들의 연기였습니다.

 

 

긴 글 읽어주셔서 감사합니다 :)

build.gradle에 다음 코드를 넣고 Sync Now를 눌러 준다

    buildFeatures { viewBinding = true }

 

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        Log.d("ActivityLifeCycle", "onCreate()")
    }

    override fun onStart() {
        super.onStart()

        Log.d("ActivityLifeCycle", "onStart()")
    }

    override fun onResume() {
        super.onResume()
        Log.d("ActivityLifeCycle", "onResume()")
    }

    override fun onPause() {
        super.onPause()

        Log.d("ActivityLifeCycle", "onPause()")
    }

    override fun onStop() {
        super.onStop()

        Log.d("ActivityLifeCycle", "onStop()")
    }

    override fun onRestart() {
        super.onRestart()

        Log.d("ActivityLifeCycle", "onRestart()")
    }

    override fun onDestroy() {
        super.onDestroy()

        Log.d("ActivityLifeCycle", "onDestroy()")
    }
}

 

안드로이드 스튜디오 하단에 Logcat을 클릭하고

ActivityLifeCycle을 입력하면 다음과 같은 log를 확인할 수 있다

 

1. 앱 처음 시작 시

 

2. 앱을 밖으로 나갔을 때

 

3. 앱을 다시 열었을 때

 

4. 다른 앱을 위에 열었을 때

 

5. 다시 해당 앱을 열었을 때

 

6. 앱을 종료했을 때

 

 

아래의 그림을 보면 조금 더 구조적으로 쉽게 이해할 수 있을 것이다

 

출처 : https://kairo96.gitbooks.io/android/content/ch2.4.1.html

 

예1) 게임 플레이 중에 전화가 와서 화면이 가려진 상태에서 다시 돌아왔을 경우

  • activity가 더이상 보이지 않으므로 onStop( ) 으로 상태를 저장한다
  • 전화를 끊었을 경우 onRestart( ) 메소드에서 다시 돌아왔음을 인지
  • onStar( )로 다시 실행 후,
  • onResume( )에서 다시 플레이를 재개할지 안내문을 띄워줌

 

예2) 자동 로그인 : 한 번 로그인을 했을 때 사용자가 따로 저장버튼을 누르지 않아도 사용자 정보가 저장됨

(activity가 시작될 때 사용자가 앱을 종료하는 과정에서 사용자 정보를 저장함

-> 앱을 다시 실행할 때 이 정보를 받아와서 자동로그인을 해줌)

findViewById

activity_main.xml

위와 같이 button을 추가해 주는 경우 event를 받거나 view영향을 주기 위해서는(버튼동작하도록 하기 위해서)

 

MainActivity.kt 파일에 다음의 코드를 추가해주어야 한다.

MainActivity.kt

findViewById 메소드가 main_activity.xml 레이아웃에 설정된 view들을 가져오게 된다.

 

findViewById를 사용하면 main.xml에서 적용한 글자를 넣거나, 글자의 색상ㆍ글꼴ㆍ스타일 등을 변경할 수 있는

메소드를 지원하게 된다.

 

특히, 가장 중요한 event 처리가 가능해진다.

 

하지만 이렇게 추가를 해버리면 버튼이 여러개가 되면

btn1, btn2, btn3, ... 이런식으로 하나하나 다 추가해 주어야 하는데....

 

 

View Binding

view binding을 사용하면, findViewById를 사용하지 않고도 자동으로 이름을 불러줘서

view 객체를 가져올 수 있게 된다.

 

build.gradle 파일에 다음 이미지의 10번째 줄을 추가하고,

Snyc Now를 눌러준다 (꼭 얘를 눌러야 적용이 된다!)

build.gradle (module) 파일

 

MainActivity.kt 파일에 다음 이미지의 9번째 줄의 코드를 넣어주고,

ActivityMainBinding을 import 해준다

MainActivity.kt

이제 findViewById를 사용하지 않아도, activity_main.xml을 자동으로 가져오게 된다

 

MainActivity.kt

 

14번째 코드의 의미

: inflation을 하여 layout 객체를 memory에 load한다.

 

다음과 같이 15번째 줄의 코드를 수정해주면,

MainActivity.kt

 

activity_main.xml의 view들을

activity_main.xml

 

findViewById를 사용하지 않고도 가져올 수 있게 된다

MainActivity.kt

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <utility>
using namespace std;
 
int main(void){
    
    
    // make_pair 사용
    pair<intint> p1;
    p1 = make_pair(1020);
 
   
    // 생성자 이용
    pair<intint> p2(30,40);
    
    
    // ( (x, y) , (z, w) )
    pair<pair<intint>pair<doubledouble>> pp2;
 
 
cs


'C++ > C++ 문법' 카테고리의 다른 글

#09 - 1 가상함수와 추상 클래스  (1) 2018.10.08
#8-5 가상 상속  (0) 2018.10.07
#8-3 상속과 생성자, 소멸자  (0) 2018.10.07
#8-2 상속과 객체 포인터  (0) 2018.10.07
#8-1 상속  (0) 2018.10.07

django 코드를 읽다보면 @classmethod라고 되어 있는 데코레이터를 자주 볼 수 있는데 계속 무시하고 넘어가다가 이번에 검색을 통해서 정리하게 되었다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cs:
    
    @staticmethod
    def static_method():
        print("Static method")
 
    @classmethod
    def class_method(cls):
        print("Class method")
 
    def instance_method(self):
        print("Instance method")
 
= Cs()
Cs.static_method()
Cs.class_method()
i.instance_method()    # 인스턴스메소드는 인스턴스에 속해있음 
cs


메서드

파이썬에서 메서드는 크게 인스턴스 메서드(instance method), 정적 메서드(static method), 클래스 메서드(class method)가 있다. 가장 흔히 쓰이는 인스턴스 메서드는 인스턴스 변수에 엑세스할 수 있도록 메서드의 첫번째 파라미터에 항상 객체 자신을 의미하는 "self"라는 파라미터를 갖는다.

아래 예제에서 calcArea()가 인스턴스 메서드에 해당된다. 인스턴스 메서드는 여러 파라미터를 가질 수 있지만, 첫번째 파라미터는 항상 self 를 갖는다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Rectangle:
    count = 0  # 클래스 변수
 
    # 생성자(initializer)
    def __init__(self, width, height):
        # self.* : 인스턴스변수
        self.width = width
        self.height = height
        Rectangle.count += 1
 
    # 메서드
    def calcArea(self):
        area = self.width * self.height
        return area
 
cs



클래스 변수

클래스 정의에서 메서드 밖에 존재하는 변수클래스 변수(class variable)라 하는데, 이는 해당 클래스를 사용하는 모두에게 공용으로 사용되는 변수이다. 클래스 변수는 클래스 내외부에서 "클래스명.변수명" 으로 엑세스 할 수 있다. 위의 예제에서 count는 클래스변수로서 "Rectangle.count"와 같이 엑세스할 수 있다.


인스턴스 변수

하나의 클래스로부터 여러 객체 인스턴스를 생성해서 사용할 수 있다. 클래스 변수가 하나의 클래스에 하나만 존재하는 반면, 인스턴스 변수는 각 객체 인스턴스마다 별도로 존재한다. 클래스 정의에서 메서드 안에서 사용되면서 "self.변수명"처럼 사용되는 변수를 인스턴스 변수(instance variable)라 하는데, 이는 각 객체별로 서로 다른 값을 갖는 변수이다. 인스턴스 변수는 클래스 내부에서는 self.width 과 같이 "self." 을 사용하여 엑세스하고, 클래스 밖에서는 "객체변수.인스턴스변수"와 같이 엑세스 한다.

Python은 다른 언어에서 흔히 사용하는 public, protected, private 등의 접근 제한자 (Access Modifier)를 갖지 않는다. Python 클래스는 기본적으로 모든 멤버가 public이라고 할 수 있다. Python 코딩 관례(Convention)상 내부적으로만 사용하는 변수 혹은 메서드는 그 이름 앞에 하나의 밑줄(_) 을 붙인다. 하지만 이는 코딩 관례에 따른 것일 뿐 실제 밑줄 하나를 사용한 멤버도 public 이므로 필요하면 외부에서 엑세스할 수 있다. 
만약 특정 변수명이나 메서드를 private으로 만들어야 한다면 두개의 밑줄(__)을 이름 앞에 붙이면 된다.


1
2
3
4
5
6
7
8
9
10
def __init__(self, width, height):
    self.width = width
    self.height = height
 
    # private 변수 __area
    self.__area = width * height
 
# private 메서드
def __internalRun(self):
    pass
cs



Initializer (초기자)

클래스로부터 새 객체를 생성할 때마다 실행되는 특별한 메서드로 __init__() 이라는 메서드가 있는데, 이를 흔히 클래스 Initializer 라 부른다 (주: 파이썬에서 두개의 밑줄 (__) 시작하고 두개의 밑줄로 끝나는 레이블은 보통 특별한 의미를 갖는다). Initializer는 클래스로부터 객체를 만들 때, 인스턴스 변수를 초기화하거나 객체의 초기상태를 만들기 위한 문장들을 실행하는 곳이다. 위의 __init__() 예제를 보면, width와 height라는 입력 파라미터들을 각각 self.width와 self.height라는 인스턴스변수에 할당하여 객체 내에서 계속 사용할 수 있도록 준비하고 있다.


(주: Python의 Initializer는 C#/C++/Java 등에서 일컫는 생성자(Constructor)와 약간 다르다. Python에서 클래스 생성자(Constructor)는 실제 런타임 엔진 내부에서 실행되는데, 이 생성자(Constructor) 실행 도중 클래스 안에 Initializer가 있는지 체크하여 만약 있으면 Initializer를 호출하여 객체의 변수 등을 초기화한다.).



정적 메서드와 클래스 메서드

인스턴스 메서드가 객체의 인스턴스 필드를 self를 통해 엑세스할 수 있는 반면, 정적 메서드는 이러한 self 파라미터를 갖지 않고 인스턴스 변수에 엑세스할 수 없다. 따라서, 정적 메서드는 보통 객체 필드와 독립적이지만 로직상 클래스내에 포함되는 메서드에 사용된다. 정적 메서드는 메서드 앞에 @staticmethod 라는 Decorator를 표시하여 해당 메서드가 정적 메서드임을 표시한다.


클래스 메서드는 메서드 앞에 @classmethod 라는 Decorator를 표시하여 해당 메서드가 클래스 메서드임을 표시한다. 클래스 메서드는 정적 메서드와 비슷한데, 객체 인스턴스를 의미하는 self 대신 cls 라는 클래스를 의미하는 파라미터를 전달받는다. 정적 메서드는 이러한 cls 파라미터를 전달받지 않는다. 클래스 메서드는 이렇게 전달받은 cls 파라미터를 통해 클래스 변수 등을 엑세스할 수 있다.

일반적으로 인스턴스 데이타를 엑세스 할 필요가 없는 경우 클래스 메서드나 정적 메서드를 사용하는데, 이때 보통 클래스 변수를 엑세스할 필요가 있을 때는 클래스 메서드를, 이를 엑세스할 필요가 없을 때는 정적 메서드를 사용한다.

아래 예제에서 isSquare() 메서드는 정적 메서드로서 cls 파라미터를 전달받지 않고 메서드 내에서 클래스 변수를 사용하지 않고 있다. 반면, printCount() 메서드는 클래스 메서드로서 cls 파라미터를 전달받고 메서드 내에서 클래스 변수 count 를 사용하고 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Rectangle:
    count = 0  # 클래스 변수
 
    def __init__(self, width, height):
        self.width = width
        self.height = height
        Rectangle.count += 1
 
    # 인스턴스 메서드
    def calcArea(self):
        area = self.width * self.height
        return area
 
    # 정적 메서드
    @staticmethod
    def isSquare(rectWidth, rectHeight):
        return rectWidth == rectHeight   
 
    # 클래스 메서드
    @classmethod
    def printCount(cls):
        print(cls.count)   
 
 
# 테스트
square = Rectangle.isSquare(55)        
print(square)   # True        
 
rect1 = Rectangle(55)
rect2 = Rectangle(25)
rect1.printCount()  # 2 
cs


|9.1| 상속 관계에서의 함수 중복


< 예시 : 상속 관계에서 함수를 중복하는 경우 >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
 
class Base {
public:
    void f() { cout << "Base::f() called" << endl; }
};
 
class Derived : public Base {
public:
    void f() { cout << "Derived::f() called" << endl; }
};
 
void main() {
    Derived d, *pDer;
    pDer = &d; // 객체 d를 가리킨다.
    pDer->f(); // Derived의 멤버 f() 호출 
 
    Base* pBase;
    pBase = pDer; // 업캐스팅. 객체 d를 가리킨다.
    pBase->f(); // Base의 멤버 f() 호출
}
cs


실행 결과


Derived::f() called

Base::f() called


=> 파생 클래스에서 기본 클래스와 동일한 식의 함수를 중복 작성하는 겨우, 

기본 클래스에 대한 포인터로는 기본 클래스의 함수를 호출하고(up-casting의 경우에도),

파생 클래스의 포인터로는 파생 클래스에 작성된 함수를 호출한다.




|9.2| 가상 함수와 오버라이딩


: 가상 함수(virtual function)오버라이딩(overriding)은 상속에 기반을 둔 기술로 객체 지향 언어의 꽃이다!



● 오버라이딩


: 오버라이딩은 파생 클래스에서 기본 클래스에 작성된 가상 함수를 중복 작성하여, 기본 클래스에 작성된 가상 함수를 무력화시키고,

객체의 주인 노릇을 하는 것이다.


기본 클래스의 포인터를 이용하든 파생 클래스의 포인터를 이용하든 가상 함수를 호출하면, 파생 클래스에 오버라이딩된 함수가 항상 실행된다.


파생 클래스에서 기본 클래스의 가상 함수와 완전히 동일한 원형의 함수를 재정의 하는 것을 '함수 오버라이딩(function overriding)' 이라고 한다.



● 가상 함수


: 가상 함수(virtual function)란 virtual 키워드로 선언된 멤버 함수로,

virtual 키워드는 컴파일러에게 자신에 대한 호출 바인딩을 실행 시간까지 미루도록 지시하는 키워드이다.



● Overloading(함수 중복) vs Overriding 



< overloading >


1
2
3
4
5
6
7
8
9
10
11
12
13
class Base {
public:
    void f() {
        cout << "BASE::f() called" << endl;
    }
};
 
class Derived : public Base {
public:
    void f() {
        cout << "Derived::f() called" << endl;
    }
};
cs


=> Base의 f()와 Derived 의 f()는 각각 동등한 호출 기회를 가짐



< overriding >


1
2
3
4
5
6
7
8
9
10
11
12
13
class Base {
public:
    virtual void f() {
        cout << "BASE::f() called" << endl;
    }
};
 
class Derived : public Base {
public:
    virtual void f() {
        cout << "Derived::f() called" << endl;
    }
};
cs


=> Base의 f()는 존재감을 잃고, 항상 Derived의 f()가 호출됨



※ 변수 오버라이딩이란 용어는 없다. 항상 멤버 함수 에게만 적용된다.




< 예시 : 오버라이딩 >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
 
class Base {
public:
    virtual void f() { cout << "Base::f() called" << endl; }
};
 
class Derived : public Base {
public:
    virtual void f() { cout << "Derived::f() called" << endl; }
};
 
void main() {
    Derived d, *pDer;
    pDer = &d; // 객체 d를 가리킨다.
    pDer->f(); // Derived::f() 호출 
 
    Base* pBase;
    pBase = pDer; // 업캐스팅. 객체 d를 가리킨다.
    pBase->f(); // 동적 바인딩 발생!! Derived::f() 실행
}
cs


실행결과


Derived::f() called

Derived::f() called



함수 오버로딩의 경우 pBase가 Base 타입의 포인터이므로 다음 코드는 Base의 f()을 호출할 것 으로 예상되지만,

pBase가 가리키는 객체는 overriding한 Derived의 f()를 포함하므로 동적 바인딩 통해 Derived의 f()가 호출된다.


-> Base의 f()에 대한 모든 호출은 실행 시간 중에 Derived의 f()함수로 동적 바인딩된다.




● 오버라이딩의 목적


: 파생 클래스들이 자신의 목적에 맞게 가상 함수를 재정의 하도록 하는 것이다. ( 객체 지향 언어의 다형성(polymorphism)을 실현한다. )


 

< 예시 :  ' 오버라이딩을 통한 다형성 '  의 이해 >



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Shape {
protected:
    virtual void draw(){ }
};
 
class Circle : public Shape {
protected:
    virtual void draw() { } // Circle 그린다.
    
};
 
class Rect : public Shape {
protected:
    virtual void draw() { } // Rect 그린다.
 
};
 
class Line : public Shape {
protected:
    virtual void draw() { } // Line 그린다.
 
};
 
void paint(Shape *p) {
    p->draw();
}
 
paint(new Circle()); // Circle을 그린다
paint(new Rect()); // Rect를 그린다
paint(new Line()); // Line을 그린다
cs




● 동적 바인딩 : 오버라이딩된 함수가 무조건 호출


: 가상 함수를 호출하는 코드를 컴파일 할 때, 컴파일러는 바인딩을 실행 시간에 결정하도록 미루어둔다.


  나중에 가상 함수가 호출되면, 실행 중에 객체 내에 오버라이딩된 가상 함수를 동적으로 찾아 호출한다.


  이 과정을 동적 바인딩(dynamic binding) 이라고 부른다. 


  오버라이딩은 파생 클래스에서 재정의한 가상 함수의 호출을 보장받는 선언이다.



● 동적 바인딩이 발생하는 구체적 경우


: 동적 바인딩은 파생 클래스의 객체에 대해, 기본 클래스의 포인터로 가상 함수가 호출될 때 일어난다.


- 기본 클래스 내의 멤버 함수가 가상 함수 호출

- 파생 클래스 내의 멤버 함수가 가상 함수 호출

- main()과 같은 외부 함수에서 기본 클래스의 포이터로 가상 함수 호출

- 다른 클래스에서 가상 함수 호출



● 동적 바인딩 예시



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
 
class Shape {
public:
    void paint() { 
        draw();                        // 얘가
    }
    virtual void draw() {            // 얘를 호출함
        cout << "Shape::draw() called" << endl
    }
};
 
int main() {
    Shape * pShape = new Shape();
    pShape->paint(); 
    delete pShape;
}
cs


실행 결과


Shape::draw() called



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;
 
class Shape {
public:
    void paint() { 
        draw();                                // 얘가
    }
    virtual void draw() {                    // 얘를 호출하지 않고
        cout << "Shape::draw() called" << endl
    }
};
 
class Circle : public Shape {
public:
    virtual void draw() {                    // 얘를 호출함
        cout << "Circle::draw() called" << endl
    }
};
 
int main() {
    Shape *pShape = new Circle();
    pShape->paint(); 
    delete pShape;
}
cs


실행 결과


Circle::draw() called



-> 기본 클래스에서 자신의 멤버를 호출하더라도 그것이 가상 함수이면 역시 동적 바인딩이 발생한다.




● 오버라이딩의 성공 조건


: 함수 이름, 매개 변수 타입, 개수 , 그리고 리턴 타입까지 일치해야 오버라이딩이 성공한다.




● virtual 지시어 생략 가능


: 클래스의 virtual 키워드는 파생 클래스로 상속되므로, 파생 클래스에서 virtual 키워드를 생략할 수 있다.



● 범위 지정 연산자(::)로 존재감을 상실한 함수를 호출하자



< 예시 >


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;
 
class Shape {
public:
    virtual void draw() { 
        cout << "--Shape--"
    }
};
 
class Circle : public Shape {
public:
    int x;
    virtual void draw() { 
        Shape::draw(); // 기본 클래스의 draw() 호출
        cout << "Circle" << endl;
    }
};
 
int main() {
    Circle circle;
    Shape * pShape = &circle;
 
    pShape->draw();  // 동적 바인딩 발생. draw()는 virtual이므로
    pShape->Shape::draw(); // 정적 바인딩 발생. 범위지정연산자로 인해
}
cs




● 가상 소멸자


: 파생 클래스의 객체가 기본 크래스에 대한 포인터로 delete되는 상황에서도 정상적인 소멸이 되도록 하기 위해서

  기본 클래스의 소멸자를 만들 때 가상 함수로 작성할 것을 권한다.



1) 소멸자를 가상 함수로 선언하지 않은 경우


1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base {
public:
    ~Base();
};
 
class Derived : public Base {
public:
    ~Derived();
};
 
int main() {
    Base *= new Derived();
    delete p; // ~Base()만 실행됨
}
cs


-> p가 Base 타입이므로 ~Base() 소멸자는 실행되나,  ~Derived()는 실행되지 않는다.



2) 소멸자를 가상 함수로 선언한 경우


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base {
public:
    virtual ~Base();
};
 
class Derived : public Base {
public:
    virtual ~Derived();
};
 
int main() {
    Base *= new Derived();
    delete p; /* 1. ~Base() 호출
                 2. ~Derived()실행(동적 바인딩)
                 3. ~Base() 실행*/
}
cs


-> 소멸자를 가상 함수로 선언하면, 객체를 기본 클래스의 포인터로 소멸하든, 파생 클래스의 포인터로 소멸하든 파생 클래스와 기본 클래스의 소멸자를 모두 실행하는 정상적인 소멸 과정이 진행된다.


따라서 클래스의 소멸자를 작성할 떄 고민없이 무조건 virtual로 선언 하는 것이 뒤탈이 없겠다.



< 예시 : 소멸자를 가상 함수로 선언 >



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
 
class Base {
public:
    virtual ~Base() { cout << "~Base()" << endl; }
};
 
class Derived: public Base {
public:
    virtual ~Derived() { cout << "~Derived()" << endl; }
};
 
int main() {
    Derived *dp  = new Derived();
    Base *bp = new Derived();
 
    delete dp; // Derived의 포인터로 소멸
    delete bp; // Base의 포인터로 소멸
}
 
cs


실행 결과


~Derived()

~Base()

~Derived()

~Base()


※ 생성자는 가상 함수가 될 수 없으며, 생성자에서 가상 함수를 호출해도 동적 바인딩이 일어나지 않는다.

  하지만 소멸자는 가상 함수가 될 수 있으며 가상 함수로 만드는 것이 바람직하다.



'C++ > C++ 문법' 카테고리의 다른 글

pair 사용  (0) 2019.05.17
#8-5 가상 상속  (0) 2018.10.07
#8-3 상속과 생성자, 소멸자  (0) 2018.10.07
#8-2 상속과 객체 포인터  (0) 2018.10.07
#8-1 상속  (0) 2018.10.07


● 다중 상속의 문제점


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class BaseIO {
public:
    int mode;
};
 
class In : public BaseIO {
public:
    int readPos;
};
 
class Out : public BaseIO {
public:
    int writePos;
};
 
class InOut : public In, public Out {
public:
    bool safe;
};
 
int main() {
    InOut ioObj;
 
    ioObj.readPos = 10;
    ioObj.writePos = 20;
    ioObj.safe = true;
    ioObj.mode = 5;
}
cs


In과 Out 중 어느 클래스의 기본 클래스의 mode 인지 모호해진다.


=> 다이아몬드 형의 다중 상속 구조는 기본 클래스의 멤버가 중복 상속 되어 객체 속에 조재하는 상황을 초래하여 컴파일 오류를 발생시킨다.




● 가상 상속


: virtual 키워드를 이용하여 가상 상속을 선언하여 다중 상속에서 생기는 멤버 중복 생성 문제를 해결한다.


: virtual 키워드는 컴파일러에게 파생 클래스의 객체가 생성될 때 기본 클래스의 멤버 공간을 오직 한 번만 할당하고,

이미 할당되어 있다면 그 공간을 공유하도록 지시한다.



1
2
3
4
5
6
class In : virtual public BaseIO { // BaseIO클래스를 가상 상속함
    ...
};
class Out : virtual public BaseIO{ // BaseIO클래스를 가상 상속함
    ...
};
cs






'C++ > C++ 문법' 카테고리의 다른 글

pair 사용  (0) 2019.05.17
#09 - 1 가상함수와 추상 클래스  (1) 2018.10.08
#8-3 상속과 생성자, 소멸자  (0) 2018.10.07
#8-2 상속과 객체 포인터  (0) 2018.10.07
#8-1 상속  (0) 2018.10.07


● public 상속


: 그냥 그대로 상속된다



● protected 상속


: public 멤버들은 protected 접근 지정으로 변경되어 파생 클래스에 상속 확장된다.



● private 상속


: protected, public 멤버들은 모두 private 접근 지정으로 변경되어 파생 클래스에 상속 확장된다.




● 다중 상속


: 예제만 보면 이해 될 듯( , 로 여러개의 부모 클래스 표시 )



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;
 
class Adder {
protected:
    int add(int a, int b) { return a+b; }
};
 
class Subtractor {
protected:
    int minus(int a, int b)  { return a-b; }
};
 
class Calculator : public Adder, public Subtractor { // 다중 상속
public:
    int calc(char op, int a, int b);
};
 
int Calculator::calc(char op, int a, int b) {
    int res=0;
    switch(op) {
        case '+' : res = add(a, b); break;
        case '-' : res = minus(a, b); break;
    }
    return res;
}
 
int main() {
    Calculator handCalculator;
    cout << "2 + 4 = " << handCalculator.calc('+'24<< endl;
    cout << "100 - 8 = " << handCalculator.calc('-'1008<< endl;
}
cs


● 파생 클래스와 기본 클래스의 생성자 호출 및 실행


Q1)  파생 클래스의 객체가 생성될 때, 파생 클래스의 생성자와 기본 클래스의 생성자가 모두 실행되는가?


-> 둘 다 실행된다. 파생 클래스, 기본 클래스 모두 각각의 멤버 초기화나 필요한 초기화를 각각 수행한다.


Q2) 파생 클래스의 생성자와 기본 클래스의 생성자 중에서 누가 먼저 실행되는가?


-> 기본 클래스의 생성자가 먼저 실행된다.



다음을 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;
 
class  A {
public:
    A() { cout << "생성자 A" << endl; }
    ~A() {cout << "소멸자 A" << endl; }
};
 
class  B : public A {
public:
    B() { cout << "생성자 B" << endl; }
    ~ B() {cout << "소멸자 B" << endl; }
};
 
class  C : public B {
public:
    C() { cout << "생성자 C" << endl; }
    ~ C() { cout << "소멸자 C" << endl; }
};
 
int main() {
    C c; // 객체 c 생성
    return 0// 객체 c 소멸
 
cs



C c; 

에서 파생 클래스의 생성자가 먼저 호출되지만, 결국 기본 클래스의 생성자가 먼저 실행되고 나서야 파생 클래스의 생성자가 실행된다.


why? : 파생 클래스의 생성자에서 기본 클래스의 생성자를 호출할 때 인자 값을 전달해야 하기 때문이다.



● 소멸자의 실행 순서


: 생성자의 실행 순서와 반대로 실행된다. 


why? : 파생 클래스의 소멸자 코드를 실행한 후 기본 클래스의 소멸자를 호출하도록 컴파일 하기 때문이다.




● 파생 클래스에서 기본 클래스 생성자 호출


파생 클래스를 작성하는 개발자는 함께 실행할 기본 클래스의 생성자를 지정하여야 한다.

하지 않을 경우, 묵시적으로 기본 클래스의 기본 생성자가 실행되도록 컴파일한다.


※ 만일 기본 생성자가 선언되어 있지 않으면 '사용할 수 있는 적절한 기본 생성자가 없습니다. ' 라는 오류를 발생시킨다.




아래는 명시적으로 생성하는 경우의 예제


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
 
class  A {
public:
    A() { cout << "생성자 A" << endl; }
    A(int x) {
        cout << "매개변수생성자 A" << x << endl
    }
};
 
class  B : public A {
public:
    B() { // A() 호출하도록 컴파일됨
        cout << "생성자 B" << endl;
    }
    B(int x) : A(x+3) {
        cout << "매개변수생성자 B" << x << endl;
    }
};
 
int main() {
    B b(5);
}
cs



명시적으로 기본 생성자 호출 코드를 작성하지 않는 경우 컴파일러가 다음과 같이 기본 생성자 호출 코드를 삽입한다.


1
2
3
4
5
6
7
8
class B {
    B() : A() {
        cout << "생성자 B" << endl;
    }
    B(int x) : A() {
        cout << "매개변수생성자 b" << x << endl;
    }
};
cs


'C++ > C++ 문법' 카테고리의 다른 글

#09 - 1 가상함수와 추상 클래스  (1) 2018.10.08
#8-5 가상 상속  (0) 2018.10.07
#8-2 상속과 객체 포인터  (0) 2018.10.07
#8-1 상속  (0) 2018.10.07
#7-B 연산자 중복  (0) 2018.10.07