7 minute read

🏫Chapters 10 : 상속 Inheritance

📖Dynamic Memory

  • Operators new and new[]
  • Operators delete and delete[]

    • 배열에서 delete[]를 해주어야 할당된 모든 메모리가 해제된다.
    • delete만 쓰게 되면 맨 앞의 메모리만 해제되고 그 뒤의 메모리는 릭이 발생하게 된다

    • ex1)
          #include<iostream>
          #include<new>
    
          using namespace std;
    
          int main() {
              int i, n;
              int* p;
    
              cout << "How many numbers would you like to type? ";
    
              cin >> i;
              p = new (nothrow) int[i];	// 메모리 할당에 실패했다는 exception이 날아감
    
              if (p == 0) {
                  cout << "Error: memory could not be allocated";
              }
              else {
                  for (n = 0; n < i; n++) {
                      cout << "Enter number: ";
                      cin >> p[n];
                  }
                  cout << "You have entered: ";
                  for (n = 0; n < i; n++) {
                      cout << p[n] << ", ";
                  }
                  delete[] p;
    
              }
              return 0;
          }
    
    • ex2)
          #include <iostream>
          using namespace std;
    
          class CRectangle {
          private:
              int width, height;
    
          public:
              void setvalues(int, int);
              int area(void) {
                  return (width * height);
              }
          };
    
          void CRectangle::setvalues(int a, int b) {
              width = a;
              height = b;
          }
    
          int main() {
              CRectangle a,* b,*c;				// a객체 생성 -> 스택
              CRectangle* d = new CRectangle[2];	// d객체 생성 (2개) -> 힙, 배열이므로 연속된 메모리 공간으로 할당됨
              b = new CRectangle;					// b객체 생성 -> 힙
              c = &a;								// c는 스택의 주소(a의 주소)를 가리키는 포인터
                                                  // 총 4개의 CRectangle의 객체가 생성되어 할당됨
                
              a.setvalues(1, 2);
              b->setvalues(3, 4);
              d->setvalues(5, 6);
              d[1].setvalues(7, 8);		// 배열의 방식으로 접근할 때는 . 사용, 각각의 element를 CRectangle로 취급
              d[0].setvalues(9, 10);		// 이것과 d->setValues(9,10);은 완벽히 같은 메모리를 가리킨다. d는 맨 첫번째 메모리를 가리킴
                
              c->area();	// c는 포인터이므로 -> 사용
    
              cout << "a area: " << a.area() << endl;
              cout << "*b area: "	<< b->area() << endl;
              cout << "*c area: " << 1 << c->area() << endl;
              cout << "d[0] area: " << d[0].area() << endl;
              cout << "d[l] area: " << d[1].area() << endl;
    
              delete[] d;		// delete는 힙에 잡힌 메모리만 해제한다. 스택은 delete을 이용하지 않는다
              delete b;
                
              return 0;
    
          }
    


📖Static Members

  • 클래스 통틀어서 메모리에는 한 번만 잡히고, 다른 객체들은 그 메모리를 공유한다
  • 이것을 class variable이라고 부르기도 한다

  • ex)
    #include<iostream>
    using namespace std;

    class CDummy {
    public:
        static int n;	// n은 정적변수이므로 a,b,c가 모두 공유한다
        CDummy() {		// 만약 생성자에서 n = 0으로 초기화시키면 객체가 생성될때마다 초기화되어 n은 0이 된다
            n++;
        }
        ~CDummy() {
            n--;
        }
    };

    int CDummy::n = 0;		// 자바에서는 클래스 내에서 메모리 할당을 허용하지만, C++에서는 허용하지 않는다.
                            // 따라서 외부에서 초기화한다

    int main(void) {
        CDummy a;					// 스택
        CDummy b[5];				// 스택
        CDummy* c = new CDummy();	// 힙, 여기까지 객체가 총 7개가 생성된다

        cout << a.n << endl;		// 처음엔 0이었으나, 위에서 객체가 7개가 생성되면서 정적변수인 n이 ++되면서 결과적으로 7이 됨
                                    // 정적변수 n이 하나의 클래스의 서로 다른 객체 사이에서 공유됨을 알 수 있음
        delete c;
        cout << CDummy::n << endl;	// static이므로 이렇게 명시할 수 있다. 일반적 멤버변수는 이렇게 쓸 수 없음
                                    // 조금 더 명확한 표현이라 할 수 있음

        cout << sizeof(a) << endl;	// 1이라고 출력됨
                                    // 만약 n이 정적변수가 아닌 일반적인 멤버변수였다면 4가 출력됨
                                    // 즉, 정적변수는 일반적 멤버변수와 달리 sizeof에 포함시키지 않음. 그러나 일반 멤버변수가 없을 땐 1로 출력.
        return 0;
    }


📖Friend

    #include<iostream>
    using namespace std;

    class CSquare;	// 당장 아래 클래스에서 해당 클래스를 사용하므로 선언해줌
                    // 선언이 있으므로 컴파일러가 통과시켜줌

    class CRectangle {
    private:
        int width, height;
    public:
        int area() {
            return (width * height);
        }
        void convert(CSquare a);
    };

    class CSquare {
    private:
        int side;
    public:
        void setSide(int a) {
            side = a;
        }
        friend class CRectangle;
    };

    void CRectangle::convert(CSquare a) {
        width = a.side;
        height = a.side;

    }

    int main(void) {
        CSquare sqr;
        CRectangle rect;

        sqr.setSide(4);
        rect.convert(sqr);

        cout << rect.area();
        return 0;
    }


함수에 대한 static?

  • 어차피 함수는 하나의 메모리를 공유하는 것인데 차이가 있는지 궁금할 수 있다. sizeof를 계산할 땐 멤버변수만 계산한다.
    따라서 굳이 정적함수가 필요할까?

  • ex)

    #include<iostream>
    using namespace std;

    class CDummy {
    public:
        static int n;	
        int m;
        CDummy() {		
            n++;
        }
        ~CDummy() {
            n--;
        }
        static int getN(){
            return n;
        }
        int getM(){
            return m;
        }

        static int getN(){ return n + m;}  // 에러가 발생한다. 왜냐하면 m이 누구의 m인지 특정할 수 없기 때문이다(모호성)
        int getM { return n + m;}          // 문제가 없다. 왜냐하면 이때는 누구의 m인지 특정할 수 있다
    };

    int CDummy::n = 0;		
                            

    int main(void) {

        getM();          // getM은 호출 불가능. 멤버함수이므로 객체에 대해서만 호출할 수 있다
        CDummy::getN();  // getN은 정적함수이므로 객체가 생성되지 않아도 호출할 수 있다 
                         // 멤버함수는 하나의 메모리를 공유한다고 해도 객체에 속한 것이기 때문에 생성된 객체를 통해서 호출해야한다               

        CDummy a;					
        CDummy b[5];				
        CDummy* c = new CDummy();	

        cout << a.n << endl;		
                                    
        delete c;
        cout << CDummy::n << endl;	
                                    

        cout << sizeof(a) << endl;	
                                    
                                    
        return 0;
    }


🌟상속

  • 현 오픈소스 시대에 재사용은 매우 활발해졌다. C++이 나왔을 때에는 오픈소스 시대가 아니었음에도 불구하고 소스코드의 재사용을 허락하는 특징을 가지고 있는 것이 상속이다.
    기존의 기능을 상속받아 원하는 부분을 수정, 추가를 할 수 있다. (override, overload) 클래스의 상속은 이러한 변형을 가능케 하는 소스코드의 재사용을 목적으로 두고 설계된 개념이다.

  • visibility 가시성 : 물려주는 것은 좋지만, 부모자식간에 남은 아니지만 동일한 인물은 아닌 것과 같이 프라이버시가 있다. 따라서 물려주더라도 어디까지 보여줄 것인가에 대한 문제를 논할 필요가 있다.

    public, protected, private

    • private : 상속받았지만 접근할 수 없다. friend와 자기 자신의 클래스 내에서만 접근할 수 있다.
    • protected : 상속받은 자식만 접근할 수 있다.
    • public : 모두가 접근할 수 있다.
  • EX
    #include<iostream>

    using namespace std;

    class CPolygon {
    protected:
        int width, height;
    public:
        void setValues(int a, int b) {
            width = a;
            height = b;
        }
    };

    class CRectangle :public CPolygon {
    public: 
        int area() {				// 상속받은 멤버함수나 변수 외에 필요한 함수를 추가로 정의하여 사용
            return width * height;	// protected가 선언된 변수이므로 자식클래스가 접근할 수 있다
        }
    };

    class CTriangle :public CPolygon {
    public:
        int area() {
            return width * height / 2;
        }
    };

    int main(void) {
        CRectangle rect;
        CTriangle tri;

        rect.setValues(4, 5);		// 부모클래스로부터 상속받은 public 함수이므로 호출할 수 있다
        tri.setValues(4, 5);		// 부모가 정의해놓은 함수를 재사용한 것이다

        cout << rect.area() << endl;	
        cout << tri.area() << endl;
        
        return 0;
    }


  • Ex2
#include <string>
#include <vector>
#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////////////////////
/////////////     기본적인 문자열에 대한 클래스 구현해보기			///////////	
///////////////////////////////////////////////////////////////////////////
class MyString
{
protected:	// private으로 선언한다면 자식클래스가 pstr에 접근할 수 없기 때문에 protected로 바꿈
	char* pstr;
	void initPstr();

public:
	MyString();
	~MyString();
	int getLength();
	void setString(const char* t);
	char* getPstr();
	ostream& operator<<(ostream& os);
	friend ostream& operator<<(ostream& os, const MyString& s);
};

////////////////////////// <summary> //////////////////////////////////////
////////////          MyString을 상속받은 문제를 풀기위한 클래스		///////////
////////////		  solve라는 함수를 추가함						/////////// 
////////////////////////// </summary> /////////////////////////////////////
class FiveString : public MyString {
public:
    FiveString();
	bool solve();
};


////////////////////////// <summary> //////////////////////////////////////
////////////					Main 함수						/////////// 
////////////////////////// </summary> /////////////////////////////////////
int main(void) {
	MyString mystr;
	mystr.setString("test");
	cout << mystr.getLength() << " : " << mystr.getPstr() << endl;
	cout << mystr.getLength() << " : " << mystr << endl;

	FiveString str;
	str.setString("1234");
	cout << str.getLength() << " : " << str << " : " << str.solve() << endl;
	
	return 0;
}


///////////////////////////// MyString 멤버함수 ////////////////////////////////////////
MyString::MyString()
{
    cout << "I'm MyString()" << endl;
	pstr = NULL;
	initPstr();
}

MyString::~MyString()
{
    cout << "I'm ~MyString()" << endl;
	if (pstr != NULL) {
		delete[] pstr;
	}
}

void MyString::initPstr()

{
	pstr = new char[10];
}

int MyString::getLength()
{
	int i;
	
	for (i = 0; i < 10; i++) {
		if (pstr[i] == '\0')
			break;
	}

	return i;
}

void MyString::setString(const char* t)
{
	for (int i = 0; i < 10; i++) {
		pstr[i] = t[i];
		if (t[i] == NULL) {
			break;
		}
	}
}

ostream& MyString::operator<<(ostream& os)
{
	os << pstr;
	return os;
}

ostream& operator<<(ostream& os, const MyString& s)
{
	os << s.pstr;
	return os;
}

char* MyString::getPstr()
{
	return this->pstr;
}

///////////////////////////// FiveString 멤버함수 ////////////////////////////////////////
FiveString:FiveString(){
    cout << "I'm FiveString()" << endl;
}

bool FiveString::solve()
{
	int len = getLength();

	if (len == 4 || len == 6) {
		for (int i = 0; i < 10 && pstr[i] != '\0'; i++) {
			if (pstr[i] < '0' || pstr[i]>'9') {
				return false;
			}
		}
	}
	else {
		return false;
	}

	return true;
}
result
I'm MyString()
4 : test
4 : test
I'm MyString()
I'm FiveString()
4 : 1234 : 1
I'm ~MyString()
I'm ~MyString()
  • 실행시켰을 때, FiveString의 생성자보다 부모클래스인 MyString의 클래스가 먼저 호출된다. 따라서 내가 만들어지기 전에 부모의 객체가 먼저 만들어진다.
  • 또한, 소멸자에서도 나의 소멸자는 직접 명시하지 않은 디폴트 명시자이지만, 부모의 소멸자가 찍히는 것을 볼 수 있다.
    따라서 내가 생각하기엔 FiveString의 객체만 만든것처럼 보이지만, 실제로는 부모의 객체도 생성되고 소멸될때 같이 소멸됨을 알 수 있다.
    켜켜이 객체가 쌓이는 것과 같다.

  • 상속되지 않는 것
    • 생성자와 소멸자는 부모와 자식거 따로따로 존재한다. 이건 상속되지 않는다
    • friend 또한 상속되지 않는다. 부모의 friend가 자식의 friend는 아니다
    • assignment operator 대입연산자는 상속되지 않는다


  • Ex3
      #include<iostream>
      using namespace std;
    
      class mother{
          public:
              mother(){
                  cout << " mother : no parameter" << endl;
              }
              mother(int a){
                  cout << "mother: int parameter" << endl;
              }
      }
    
      class daughter : public mother{
          public:
              daughter(int a){
                  cout << " daughter: int parameter" << endl;
              }
      } 
    
      class son : public mother{
          public:
              son(int a) : mother(a){
                  cout << "son : int parameter" << endl;
              }
      }
    
      int main(void){
          daughter july(0);
          son dany(0);
    
          return 0;
      }
    
    결과
    mother : no parameter
    daughter: int parameter
    mother: int parameter
    son : int parameter
    
    • daughter클래스는 son클래스는 부모의 생성자를 명시적으로 선택한 것. mother의 객체를 만드는 것은 똑같지만, 직접 명시한 son클래스는 int를 인자로 받는 생성자가 호출되고, daughter클래스는 mother의 디폴트 생성자를 호출한다