[C++] Chapter 04 - 클래스의 완성

Lee Jeong Min·2020년 12월 29일
0

Cpp

목록 보기
4/16
post-thumbnail
post-custom-banner

04-1 정보은닉(Information Hiding)

정보은닉의 이해

좋은 클래스가 되기 위한 조건은 '정보은닉'과 '캡슐화'이다.

클래스의 멤버 변수가 public으로 선언되면 아무렇게나 접근이 가능하기 때문에 제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 도와야하고 실수가 쉽게 발견되도록 해야한다.
--> 따라서 private 레이블을 사용하여 멤버 변수들을 감추고 public 레이블을 이용해 함수를 만들어서 멤버 변수들을 초기화 해줌!

"멤버변수를 private으로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버 변수의 접근을 유도하는 것이 바로 '정보 은닉'이며, 이는 좋은 클래스가 되기 위한 기본 조건이 된다!"

int GetX() const;
bool SetX(int xpos);
이러한 함수들을 엑세스 함수라고 하며 클래스 외부에서 접근을
목적으로 정의하는 함수들이다.

const 함수

  • 함수 내에서는 멤버 변수에 저장된 값을 변경하지 않겠다는 의미로 사용
  • Ex) void ShowRecInfo() const;

또한 const 함수 내에 const가 아닌 함수가 호출되면 컴파일 에러가 발생함!

class SimpleClass
{
private:
	int num;
    
public:
    void InitNum(int n)
    {
    	num = n;
    }
    int Getnum() // const 선언이 추가되어야 아래의 컴파일 에러 소멸
    {
    	return num;
    }
    void ShowNum() const
    {
    	cout<<GetNum()<<endl; // 컴파일 에러 발생
    }
}

이러한 제한장치를 통해 코드의 안전성을 높일 수 있음!


04-02 캡슐화(Encapsulation)

정보은닉과 캡슐화 -> 객체지향 기반의 클래스 설계에서 가장 기본적이면서도 가장 중요한 원칙!

캡슐화를 하게 되면 상호관계가 복잡했던 것을 줄일 수 있게 되고 프로그램을 간결하게 만들 수 있음. 그러나 어떠한 것들을 캡슐화를 시킬 지가 중요함!

#include <iostream>
using namespace std;

class SinivelCap
{
public:
	void Take() const { cout << "콧물이 싹~ 납니다." << endl; }
};

class SneezeCap
{
public:
	void Take() const { cout << "재채기가 멎습니다." << endl; }
};

class SnuffleCap
{
public:
	void Take() const { cout << "코가 뻥 뚫립니다." << endl; }
};

class ColdPatient
{
public:
	void TakeSinivelCap(const SinivelCap& cap) const { cap.Take(); }
	void TakeSneezeCap(const SneezeCap& cap) const { cap.Take(); }
	void TakeSnuffleCap(const SnuffleCap& cap) const { cap.Take(); }
};

int main(void)
{
	SinivelCap scap;
	SneezeCap zcap;
	SnuffleCap ncap;

	ColdPatient sufferer;
	sufferer.TakeSinivelCap(scap);
	sufferer.TakeSneezeCap(zcap);
	sufferer.TakeSnuffleCap(ncap);
	return 0;
}

이 코드를 캡슐화를 시키면 아래의 코드가 됨

#include <iostream>
using namespace std;

class SinivelCap
{
public:
	void Take() const { cout << "콧물이 싹~ 납니다." << endl; }
};

class SneezeCap
{
public:
	void Take() const { cout << "재채기가 멎습니다." << endl; }
};

class SnuffleCap
{
public:
	void Take() const { cout << "코가 뻥 뚫립니다." << endl; }
};

class CONTAC600
{
private:
	SinivelCap sin;
	SneezeCap sne;
	SnuffleCap snu;

public:
	void Take() const
	{
		sin.Take();
		sne.Take();
		snu.Take();
	}
};

class ColdPatient
{
public:
	void TakeCONTAC600(const CONTAC600& cap) const { cap.Take(); }
};

int main(void)
{
	CONTAC600 cap;
	ColdPatient sufferer;
	sufferer.TakeCONTAC600(cap);
	return 0;
}

04-03 생성자(Constructor)와 소멸자(Destructor)

생성자를 통해 객체도 생성과 동시에 초기화를 할 수 있다. ---> 기존까지는 멤버 함수를 통해 초기화를 시킴.

class SimpleClass
{
private:
    int num;
public:
    Simpleclass(int n) // 생성자
    {
        num = n;
    }
    int GetNum() const
    {
        return num;
    }
}

생성자는 객체 생성시 딱 한번 호출된다. 또한 함수의 일종이므로 오버로딩이 가능하며 매개변수에 디폴트값을 정할 수 있다.

#include <iostream>
using namespace std;

class SimpleClass
{
public:
	int num1;
	int num2;
private:
	SimpleClass()
	{
		num1 = 0;
		num2 = 0;
	}
	SimpleClass(int n)
	{
		num1 = n;
		num2 = 0;
	}
	SimpleClass(int n1, int n2)
	{
		num1 = n1;
		num2 = n2;
	}
};

이러한 형태로 오버로딩이 가능하다.

Simpleclass sc; // 선언가능
Simpleclass sc(); // 선언불가능

왜?

Simpleclass sc1()
{
	Simpleclass sc(20,30);
    return sc;
}

이러한 함수가 정의되어 있을 때, main 함수내에서 Simpleclass sc();와 같은 문장이 선언되면 객체를 생성하기 위한 객체생성문인지 함수의 원형선언인지 구분할 수 없기 때문에 C++에서는 이를 함수의 원형선언에만 사용하기로 약속!

멤버 이니셜라이저를 이용한 객체 초기화

Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
:upLeft(x1, y1), lowRight(x2, y2)
{
//empty
}

멤버 이니셜라이저를 사용하다 보면 생성자의 몸체부분이 그냥 비는 일이 종종 발생함 --> 신경 X

멤버 이니셜라이저를 이용한 변수 및 const 상수 초기화

class SoSimple
{
Private:
    int num1;
    int num2;
Public:
    SoSimple(int n1, int n2): num1(n1)
    {
    	num2 = n2;
    }
}

이니셜라이저를 이용해 num1을 정의하게 되면 다음과 같이 선언과 동시에 초기화가 됨 int num1 = n1; 이러한 특징으로 const 변수는 선언과 동시에 초기화를 해야하기 때문에 const 멤버변수도 초기화가 가능함, 참조자도 마찬가지

디폴트 생성자와 소멸자 또한 중요!


04-4 클래스와 배열 그리고 this 포인터

일반적인 객체 배열

SoSimple arr[10]; 

포인터 배열

SoSimple * parr[10];
#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
	char* name;
	int age;
public:
	Person(char* myname, int myage)
	{
		int len = strlen(myname) + 1;
		name = new char[len];
		strcpy(name, myname);
		age = myage;
	}
	Person()
	{
		name = NULL;
		age = 0;
		cout << "called Person()" << endl;
	}
	void SetPersonInfo(char* myname, int myage)
	{
		name = myname;
		age = myage;
	}
	void ShowPersonInfo() const
	{
		cout << "이름: " << name << ", ";
		cout << "나이: " << age << endl;
	}
	~Person()
	{
		delete[] name;
		cout << "called destructor!" << endl;
	}
};

int main(void)
{
	Person * parr[3];
	char namestr[100];
	int age;

	for (int i = 0; i < 3; i++)
	{
		cout << "이름: ";
		cin >> namestr;
		cout << "나이: ";
		cin >> age;
		parr[i] = new Person(namestr, age);
	}
	parr[0]->ShowPersonInfo();
	parr[1]->ShowPersonInfo();
	parr[2]->ShowPersonInfo();
	delete parr[0];
	delete parr[1];
	delete parr[2];
	return 0;
}

다음과 같은 코드가 있을 때 이 코드는 객체 포인터 배열로 선언된 것이고 객체 포인터 배열과 객체 배열의 차이점은 객체 배열 선언시 Person arr[3]; 과 같이 선언하며 이 경우 void형 생성자인 Person()이 실행되지만 객체 포인터 배열의 경우 Person(char* name, int age) 생성자가 실행이됨

this 포인터의 이해

this 포인터는 객체의 주소값을 가리키는 데 사용함

이러한 this포인터로 다음과 같은 활용을 할 수 있음

#include <iostream>
using namespace std;

class TwoNumber
{
private:
	int num1;
	int num2;
public:
	TwoNumber(int num1, int num2)
	{
		this->num1 = num1;
		this->num2 = num2;
	}
	/*
	TwoNumber(int num1, int num2) : num1(num1), num2(num2)
	{
		//empty
	}
	*/

	void ShowTwoNumber()
	{
		cout << this->num1 << endl;
		cout << this->num2 << endl;
	}
};

int main(void)
{
	TwoNumber two(2, 4);
	two.ShowTwoNumber();
	return 0;
}

멤버 변수를 초기화할 시 변수의 이름이 같아도 this를 이용해 같은 이름이더라도 초기화가 가능함. 또한 멤버 이니셜라이저로도 초기화가 됨. 멤버 이니셜라이저는 ()안의 변수를 매개변수라고 생각하기 때문!

또한 this의 경우 *this를 반환시 참조자와 같이 써서 참조의 정보를 반환시키는데에 사용할 수 있음.

profile
It is possible for ordinary people to choose to be extraordinary.
post-custom-banner

0개의 댓글