[C++ Programming] Chapters 10 : 상속 Inheritance
🏫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의 디폴트 생성자를 호출한다