유도된 클래스들의 생성과 초기화

부모클래스를 상속한 자식클래스의 크기를 보게되면
부모클래스의 크기까지 포함된걸 볼 수 있다.
sizeof로 확인한다. 패딩으로인해 정확한 사이즈는 안나온다.

다중상속을 하게됬을때 A > B > C 로 상속하게되면 C는 A의 생성자를 실행하지 못한다.

생성자는 부모부터 소멸자는 자식부터 실행된다. 잊지말자!

 

#include <iostream>

using namespace std;

class A
{
private:
	int m_i;

public:
	A(const int &a) :m_i(a) {
		cout << "A Constructor" << endl;
	}

	~A() {
		cout << "A Destructor" << endl;
	}

};

class B : public A
{
public:
	double m_d;
	B(const int & i, const double &b)
		: A(i), m_d(b) {
		cout << "B Constructor" << endl;
	}
	~B() {
		cout << "B Destructor" << endl;
	}
};

class C : public B
{
public:
	char m_c;
	C(const int & i, const double &b,const char & c)
		: B(i,b), m_c(c) {
		cout << "C Constructor" << endl;
	}
	~C() {
		cout << "C Destructor" << endl;
	}
};


int main()
{

	cout << sizeof(A) << endl;
	cout << sizeof(B) << endl;
	cout << sizeof(C) << endl;

	C c(1, 1.3, 'a');

	return 0;
}

유도된 클래스들의 생성순서
Derived

 

기본지식

접근지정자
private 은 자식도 사용못한다.
protected는 자식은 사용가능하다.

:initalizer list는 안된다. 이유는?
클래스가 생성되는 순서를 알아야한다.

자식클래스를 객체생성하면 부모클래스가 먼저 생성되는것을 확인할 수 있다.

debug를 해보게되면
부모객체,자식객체변수를 쓰레기값으로 생성되고 
부모객체부터 초기화하고 자식객체의 변수들을 초기화되는걸 볼 수 있다. 

자식클래스는 부모클래스에 있는걸 다 사용할 수 있기에
부모클래스부터 정의해서 사용할 수 있게한다.

부모클래스의 constructor를 항상 먼저 실행시킨다.

다중상속을 하여도 맨위의 부모클래스부터 연쇄적으로초기화한다.

소멸자 Destructor는 반대로 소멸된다. 자식부터 소멸된다.

 

아래코드의 생성 순서를 보자(주석으로 써놓았다.)

1.객체생성한다.

2. 자식클래스 생성자로 온다.

3.부모클래스 생성자를 실행한다.

4.자식클래스의 생성자를 초기화한다.

 

#include <iostream>

using namespace std;

class Mother
{
public:
	int m_i;
	//3.부모클래스 생성자를 실행한다.
	Mother() :m_i(1){
		cout << "Mother Constructor" << endl;
	}
	~Mother() {
		cout << "Mother Destructor" << endl;
	}
	Mother(const int &a) :m_i(a) {
		cout << "Mother Constructor" << endl;
	}
};

class Child : public Mother 
{
public:
	double m_d;
	//2. 자식클래스 생성자로 온다.
	Child()
	: //내부적으로 숨어있다.Mother(),
		//4.자식클래스의 생성자를 초기화한다.
		m_d(1.0){
		cout << "Child Constructor" << endl;
	}
	~Child() {
		cout << "Child Destructor" << endl;
	}
};


class A : public Child
{
public:
	A() {
		cout << "A" << endl;
	}
	~A() {
		cout << "A Destructor" << endl;
	}
};
int main()
{
	//1.객체생성
	Child c1;
	
	return 0;
}

Inheritance (is-a relationship)

상속이 도움되는 이유를 구체적인 사례를 보자.

학생,교수 클래스 모두 이름이 있다.
학생,교수의 공통점은 사람이다.

사람의 기본적인 데이터(이름)를 부모클래스로 생성한다.
즉, 중복되는 데이터만 부모클래스로 뽑아낸다.

부모클래스만 사용하는기능은 부모클래스에 정의한다.
set,get등이 있다.

부모클래스의 기능을 사용가능한건 this로 확인할 수 있다.

중복되는 기능을 유지보수할때 부모클래스만 수정하면되게된다.

 

아래코드는 교수,학생클래스를 생성할때 공통된 데이터인

이름을 부모클래스로 생성해 상속받는다.

 

Person.h

#pragma once
#include <string>
#include <iostream>

class Person 
{
private:
	std::string m_name;

public:
	Person(const std::string & name) 
	:m_name(name) {}

	void setName(const std::string & name) {
		m_name = name;
	}

	std::string getName() const{
		return m_name;
	}

	void doNothing() const
	{
		std::cout << m_name << " doNothing" << std::endl;
	}
};

Student.h

#pragma once

#include "Person.h"

class Student : public Person{
private:
	int m_intel;

public:
	Student(const std::string & name = "noName", const int & intel = 0)
		: Person(name), m_intel(intel) {}

	void setIntel(const int & intel) {
		m_intel = intel;
	}

	int getIntel()
	{
		return m_intel;
	}

	friend std::ostream & operator << (std::ostream & out, const Student & s) {
		out << s.getName() << " " << s.m_intel;
		return out;
	}
};

Teacher.h

#pragma once

#include "Person.h"

class Teacher :public Person
{
private:


public:
	Teacher(const std::string & name = "noName")
		:Person(name) {

	}

	friend std::ostream & operator << (std::ostream & out, const Teacher & t1) {
		out << t1.getName();
		return out;
	}

};

main.cpp

#include "Student.h"
#include "Teacher.h"

using namespace std;

int main()
{
	string name = "";
	Student s("Jack Jack");
	Teacher t("Choi");

	cout << s.getName() << endl;
	cout << t.getName() << endl;

	s.doNothing();
	t.doNothing();

	return 0;
}

객체지향의 핵심
is-a relationship
inheritance

자식클래스는 상속받은 부모클래스의 기능을 모두 사용할 수 있다.
상속을 받은 클래스(자식클래스)를 derived class라고도 한다.

여러클래스에 일반화된것들을 뽑아서 부모클래스로 생성한다.
즉, 여러클래스에서 재사용이 매우용이하다.

부모클래스,자식클래스에 이름이 같은 메소드가 있다면 자식클래스의 메소드가 실행된다.
private는 자식에게도 허용이안된다. 이럴때 protected를 사용거나 부모클래스 생성자를 사용한다.

자식클래스의 생성이유는 부모클래스의 기능과 자식클래스의 기능을 합쳐쓰기 위함이다.

자식클래스에서 생성자Constructor를 만들려면 부모클래스를 불러와 정의해준다.

당연히 자식클래스에서 부모클래스를 사용할수 있기 때문에

부모클래스가 먼저생성되고 자식클래스가 생성된다. 

즉, 자식클래스가 생성될때 부모클래스의 생성자를 같이 실행한다.

즉, 기본생성자가 없으면 정의 해주어야한다.
아니면 자식클래스 생성자에서 부모클래스생성자를 선언해준다.

 

#include <iostream>

using namespace std;

class Mother {
private:
	int m_i;

public:
	//Mother(){}

	Mother(const int & i_in) 
	: m_i(i_in) {
		cout << "constructor" << endl;
	}

	void setValue(const int & i) {
		m_i = i;
	}

	int getValue() {
		return m_i;
	}
};

class Child : public Mother
{
private:
	double m_d;

public:
	Child(const double & d_in, const int & i_in) 
	: Mother(i_in),m_d(d_in)
	{}

	void setValue(const double & d_in,const int & i_in) {
		Mother::setValue(i_in);
	}

	void setValue(const double & d) {
		m_d = d;
	}

	double getValue() {
		return m_d;
	}
};

//Mother 클래스 재사용
class Daughter : public Mother
{

};

int main()
{
	Mother mother(11);
	mother.setValue(1234);
	cout << mother.getValue() << endl;

	Child child(12,222);
	cout << child.getValue() << endl;
	cout << child.Mother::getValue() << endl;

	return 0;
}

컨테이너클래스 Container Classes

다른클래스를 담는 역할을하는 클래스를 컨테이너 클래스라고한다.

vertor,array등이 있다.

IntArr 클래스를 한번 만들어보자.

 

#include <iostream>
#include <vector>
#include <array>
#include <cassert>
#include <initializer_list>

using namespace std;

class IntArr
{
private:
	int m_length = 0;
	int *m_data = nullptr;

	int beforeLen = 0;
	int *beforeData = nullptr;

public:
	void ixCheck(const int & ix) {
		assert(ix <= m_length);
		assert(ix >= 0);
	}

	//Constructor
	IntArr() {}

	IntArr(const int & len) 
	{
		assert(len >= 0);
		m_length = len;
		m_data = new int[m_length];
	}

	IntArr(const initializer_list<int> & list) 
	{
		m_length = list.size();
		m_data = new int[m_length];
		int a = 0;
		for (auto & ele : list) {
			m_data[a] = ele;
			a++;
		}
	}
	//Destrurctor
	~IntArr() {
		delete[] this -> m_data;
	}

	//initialize()
	IntArr & operator = (const initializer_list<int> & list) {
		delete[] m_data;
		m_length = list.size();
		m_data = new int[m_length];
		
		if (m_data != nullptr) {
			int a = 0;
			for (auto & ele : list) {
				m_data[a] = ele;
				a++;
			}
		}
		else {
			m_data = nullptr;
		}
		return *this;
	}

	int getSize() {
		return m_length;
	}

	

	void copyArr() {
		beforeLen = m_length;
		beforeData = new int[beforeLen];
		for (int i = 0; i < beforeLen; i++) {
			beforeData[i] = m_data[i];
		}
	}
	//reset()
	void reset() {
		m_length = 0;
		m_data = nullptr;
	}

	//resize()
	void resize(const int & ix) {
		this -> ixCheck(ix);
		this -> copyArr();

		m_length = ix;
		m_data = new int[m_length];

		for (int i = 0; i < m_length; i++) {
			m_data[i] = beforeData[i];
		}

		delete[] beforeData;
	}
	//insertBefore(const int & value, const int & ix);
	void insertBefore(const int & value, const int & ix) {
		this->ixCheck(ix);
		this->m_data[ix] = value;
	}
	//remover(const int & ix);
	void remover(const int & ix) {
		this->ixCheck(ix);
		this->copyArr();

		m_length = beforeLen - 1;
		m_data = new int[m_length];

		int count = 0;
		for (int i = 0; i < beforeLen; i++) {
			if (i != ix) {
				m_data[count] = beforeData[i];
				count++;
			}
		}

		delete[] beforeData;

	}

	IntArr & push_back(const int &val) {
		this->copyArr();

		m_length = m_length + 1;
		m_data = new int[m_length];

		for (int i = 0; i < m_length; i++) {
	
			if (m_length - 1 == i) {
				m_data[i] = val;
			}
			else {
				m_data[i] = beforeData[i];
			}
			
		}
		delete[] beforeData;

		return *this;
	};

	friend ostream & operator << (ostream & out, IntArr &iA) {
		for (int i = 0; i < iA.m_length; i++) {
			
			out << iA.m_data[i] << " ";
		}
		return out;
	}
};

int main()
{
	IntArr arr{ 1,2,3,4 };
	cout << arr << endl;
	cout << arr.getSize() << endl;

	arr = { 1,2,3 };
	cout << arr << endl;
	cout << arr.getSize() << endl;

	arr.resize(2);
	cout << arr << endl;
	cout << arr.getSize() << endl;

	arr.remover(2);
	cout << arr << endl;
	cout << arr.getSize() << endl;
	arr.insertBefore(100, 0);
	cout << arr << endl;
	cout << arr.getSize() << endl;

	arr.push_back(1).push_back(3);
	cout << arr << endl;
	cout << arr.getSize() << endl;

	return 0;
}

의존관계 Dependencies

서로간의 연결 강도가 낮다.

 

ex:자바 스프링에서 여러기능을 추가할때 사용한다.

재사용이 가능한 클래스를 어느 클래스에서 사용할때를 말한다.

다른 클래스를 사용하더라도 그 클래스의 자세한 내용은 몰라도된다.
즉,잠시 빌려 사용한다고 생각하면된다.

main.cpp

#include "Worker.h"

using namespace std;

int main()
{
	Worker w;
	w.doSomething();

	return 0;
}

Timer.h

#pragma once
#include <chrono>
#include <iostream>

using namespace std;

class Timer {

	using clock_t = chrono::high_resolution_clock;
	using second_t = chrono::duration<double, ratio<1>>;

	chrono::time_point<clock_t> start_time = clock_t::now();

public:
	void elapsed() {
		chrono::time_point<clock_t> end_time = clock_t::now();

		cout << chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
	}
};

Worker.h

#pragma once

class Worker {
public:
	//doSomething에서 Timer클래스를 사용한다.
	//하지만 여기서 사용하는지 알 수 없다.
	void doSomething();
};;

Worker.cpp

#pragma once
#include "Worker.h"
#include "Timer.h"


void Worker::doSomething() {
	//잠시 Timer Class의 기능을 사용한다.
	Timer timer;
	timer.elapsed();
}

제휴관계 Association

어느 한쪽이 주 반대쪽이 부가 되는 개념이 아닌 개념이다.
어느쪽이든 주,부가 될수 있다.

다른관계들보단 많이 사용하지 않는다.

서로가 서로를 사용한다. 용도이외에는 무관하다.

의사,환자

의사1이 환자1을 만난다.
환자1이 의사1를 만난다.
의사1이 환자2를 만난다.
환자2가 의사1을 만난다.

즉, 서로 기록은 하지만 터치하지 못한다.
또 양방향으로 기록하는게 아닌 단방향으로 기록할 수 있다.

 

#include <iostream>
#include <vector>
#include <string>

using namespace std;

class Doctor;

class Patient {
private:
	string m_name;
	vector<Doctor*> m_doc;

public:
	Patient(string name)
		:m_name(name) {

	}

	void addDoc(Doctor *new_doc) 
	{
		m_doc.push_back(new_doc);
	}

	void meetDot();
	friend class Doctor;
};

class Doctor {
private:
	string m_name;
	vector<Patient*> m_pat;
	
public:
	Doctor(string name)
		:m_name(name) {

	}

	void addPai(Patient *new_pai)
	{
		m_pat.push_back(new_pai);
	}

	void meetPai() {
		for (auto & ele : m_pat) {
			cout << m_name << " - >Meet Pat : " << ele->m_name << endl;
		}
	}
	friend class Patient;
};
void Patient::meetDot() {
	for (auto & ele : m_doc) {
		cout << m_name << " - >Meet Doc : " << ele->m_name << endl;
	}
}
int main()
{
	Patient *p1 = new Patient("choi");
	Patient *p2 = new Patient("lee");
	Patient *p3 = new Patient("kim");

	Doctor *d1 = new Doctor("D_kim");
	Doctor *d2 = new Doctor("D_jung");

	p1->addDoc(d1);
	d1->addPai(p1);
	p2->addDoc(d1);
	d1->addPai(p2);
	p1->meetDot();
	p2->meetDot();
	d1->meetPai();
	delete p1;
	delete p2;
	delete p3;

	delete d1;
	delete d2;
	return 0;
}

집합aggregation
Has-a 어떤 사람이 자동차를 가지고 있다.
사람이 없어도 자동차는 있을 수 있다(느슨한관계)


학교에는 수업,교수,학생이 있다.

한교수가 여러개의 강의를 할 수 있고
한학생이 여러 수업을 들을 수 있다.

구성관계로 개발하면 정보가 공유되지 않는다.
또, 강의 객체가 없어지면 모든 정보가 사라진다.
객체1 : 학교{강의{교수,학생}
객체2 : 학교{강의2{교수,학생}

집합관계로 개발하게되면 
강의,교수,학생을 따로 생성하고 주소를 공유한다.
강의1,강의2,교수1,교수2,학생1,학생2,학생3을 생성한다.
강의1 안에 교수1,학생1,학생2의 메모리 주소를 공유한다.
강의2 안에 교수2,학생1,학생2,학생3의 메모리 주소를 공유한다.

이렇게하면 강의1이 없어지더라도 교수1,학생1,학생2의 정보는 남는다.


다만, 분산처리할땐 메모리가 분리되어있어 사용못한다.

 

#include <iostream>

//강의
#include "Lecture.h"
//교수
#include "Teacher.h"
//학생
#include "Student.h"

int main()
{
	using namespace std;
	//다른곳에서 쓰게되면 동적할당으로 사용함.
	Student std1("Jack", 0);
	Student std2("cho", 0);
	Student std3("ahn", 0);

	Teacher t1("t_lee");
	Teacher t2("t_choi");

	Lecture lec1("test1");
	lec1.assignTeacher(&t1);
	lec1.registerStudent(&std1);
	lec1.registerStudent(&std3);

	Lecture lec2("test2");
	lec2.assignTeacher(&t2);
	lec2.registerStudent(&std1);
	lec2.registerStudent(&std2);
	lec2.registerStudent(&std3);

	{
		cout << lec1 << endl;
		cout << lec2 << endl;

		lec1.study();
		cout << lec1 << endl;
		cout << lec2 << endl;


	}
}

구성(요소)Composition 
Part-of 두뇌는 육체의 일부이다.
육체가 없으면 두뇌는 없다

몬스터 클래스에 위치 클래스가 있다고한다면
몬스터가 없어지면 그몬스터의 위치클래스도 없어져야한다.
또, 위치 클래스는 몬스터의 이름을 알지 못한다.

즉, 몬스터 클래스에서 사용된 위치클래스는 그냥 몬스터의 일부이다.

또한 몬스터클래스가 위치클래스의 동작원리를 알 필요가 없다.

Composition 관계는 전체/일부로 생각하면 된다.

몬스터클래스 {(몬스터전체)
위치 클래스 : 몬스터클래스의 정보안에 위치가 존재한다.(몬스터의 일부)
}

 

main.cpp

#include <iostream>
#include "Monster.h"
using namespace std;



int main() {
	//몬스터 정보안에 위치가 들어있다.
	Monster mon1("name", Position2D(0,0));
	mon1.getLocation();
	cout << mon1 << endl;
	Monster mon2("name2", Position2D(0, 0));
	mon2.getLocation();
	{
		mon1.moveTo(Position2D(1, 1));
		cout << mon1 << endl;
	}
	return 0;
}

Monster.h

#pragma once

#include <string>
#include "Position2D.h"

class Monster
{
private:
	std::string m_name;

	//subClass
	Position2D m_location;//m_x,m_y

	//int m_x;//location
	//int m_y;

public:
	//서브클래스로 위치클래스를 사용한다.
	Monster(const std::string name_in, const Position2D & pos_in)
		:m_name(name_in), m_location(pos_in) {

	}
	//set의 원리를 알 수 없다.
	void moveTo(const Position2D & pos_target) {
		m_location.set(pos_target);
	}

	void getLocation () {
		std::cout << m_location << std::endl;
	}


	friend std::ostream & operator << (std::ostream & out, Monster & monster) {
		out << monster.m_name << " " << monster.m_location;
		return out; 
	}
};

Position2D.h

#pragma once
#include <iostream>

class Position2D
{
private:
	int m_x;
	int m_y;

public:
	Position2D(const int & x_in,const int & y_in)
		: m_x(x_in),m_y(y_in)
	{}

	void set(const Position2D & pos_target) {
		set(pos_target.m_x, pos_target.m_y);
	}

	void set(const int & x_in, const int & y_in) {
		m_x = x_in;
		m_y = y_in;
	}

	friend std::ostream & operator << (std::ostream & out, Position2D & position2D) {
		out << position2D.m_x << " " << position2D.m_y;
		return out;
	}
};

이니셜라이져 리스트
사용자정의자료형에서 생성자,대입연산자를 만들때 편하게 사용하는것

array등에 사용하는 {}를 말한다.

initalizer_list를 사용한다.
파라미터 initalizer_list로 받아 변환해준다고 생각하면된다

#include <iostream>
#include <cassert>
#include <initializer_list>

using namespace std;

class IntArray {
private:
	unsigned m_length = 0;
	int *m_data = nullptr;

public:
	IntArray(unsigned length)
		:m_length(length) {
		m_data = new int[m_length];
	}

	IntArray(const std::initializer_list<int> & list) 
	:IntArray(list.size()){
		cout << "initializer_list" << endl;
		int count = 0;
		for (auto & element : list) {
			m_data[count] = element;
			++count;
		}
	}

	IntArray& operator =(const std::initializer_list<int> & list)
	{
		delete[] m_data;

		m_length = list.size();
		m_data = new int[m_length];
		cout << "= initializer_list" << endl;

		if (m_data != nullptr) {
			int count = 0;
			for (auto & element : list) {
				m_data[count] = element;
				++count;
			}
		}
		else {
			m_data = nullptr;
		}

		return *this;
	}

	~IntArray() {
		delete[] this->m_data;
	}

	friend ostream & operator << (ostream &out, IntArray &iA) {
		for (unsigned i = 0; i < iA.m_length; i++) {
			out << iA.m_data[i] << " ";
		}
		out << endl;
		return out;
	}
};

int main()
{
	auto il = { 1,2,3 };

	IntArray iA{ 12,3,3 };
	cout << &iA << endl;
	cout << iA << endl;
	iA = { 1,2,3,4,5,6 };
	cout << &iA << endl;
	cout << iA << endl;
	return 0;
}

+ Recent posts