반응형

1. 클래스

클래스는 객체지향 프로그래밍의 핵심적인 요소이다.

객체지향 프로그래밍은 프로그램을 각 객체들 간의 상호작용을 통해 나타내는 것이 목적이다.

그런 객체들을 생성하기 위하여 객체들의 특징들만 모아 일반화시킨 것이 클래스이다.

클래스 내부의 변수를 멤버 변수, 함수를 메서드라고 한다.

보통 클래스를 붕어빵 틀에 비유하고, 객체들을 붕어빵에 비유한다.
붕어빵 틀을 가지고 여러 가지 다양한 붕어빵을 만들어낼 수 있듯, 클래스 하나로 여러 속성만 변경시켜 여러 객체를 만들어 낼 수 있다.
#include <iostream>

class Person
{
public:
	std::string name;
	int age;
	void introduce()
	{
		std::cout << name << "(" << age << "세)" <<std::endl;
	}
};

int main()
{
	Person p;
	p.name = "김철수";
	p.age = 123;
	p.introduce();
	return 0;
}

그림 1. 실행결과.

위의 예제는 사람이 이름과 나이라는 특징(멤버)을 가지고, 자기 자신을 소개할 수 있다는 것을 메서드(함수)로

일반화하여 나타낸 클래스 Person을 정의하고, main함수에서 p라는 변수로 객체로 구체화하여,

각 특징들에 값을 부여하고 introduce 메서드를 호출하고 있다.

 

클래스 내부의 public이라는 키워드는 무엇을 의미할까?

이는 접근제어 지시자라고 하며, 어디서 접근할 수 있는지에 대해 제한해둔 것이다.

c++의 접근제어 지시자는 다음 3가지가 존재한다.

접근제어지시자 public private protected
접근 범위 어디서든지 접근가능 클래스 내부에서만 접근가능 상속받은 클래스에서 추가적으로 접근가능.
이외에는 private와 동일.

2. 생성자

위에서 정의한 클래스는 뭔가 엉성하다.

우리가 멤버 하나하나에 접근하여 초기화를 한다니 너무 불편하다.또 private로 멤버 변수 정의했을 경우, 내부에 접근할 수 없어 변숫값을 변경할 수 없을 것이다.객체지향 프로그래밍에서는 초기화를 담당하는 서브루틴인 생성자를 두어 이런 문제를 해결하였다.

#include <iostream>

class Person
{
// private: 가 생략되어있다.
	std::string name;
	int age;
public:
	Person(std::string name, int age)
	{
		this->name = name;
		this->age = age;
		std::cout << "생성자 호출!" << std::endl;
	}
	void introduce()
	{
		std::cout << name << "(" << age << "세)" << std::endl;
	}
};

int main()
{
	Person p("김철수",123);
	p.introduce();
	return 0;
}

위 코드는 그림 1과 같은 결과를 가진다.

 

Person 클래스를 살펴보면, name과 age 멤버 변수들이 public위에 선언된 것을 볼 수 있다.아무런 접근제어 지시자를 선언하지 않고 멤버 변수나 메서드를 정의하면, 자동으로 private로 지정된다.

 

public영역을 보면, Person(std::string, int)로 정의된 반환형이 없는 함수 형태의 서브루틴이 있다.이것을 생성자라고 하며, 객체가 생성될 때 한 번만 호출된다.생성자는 주로 멤버들의 초기화를 담당한다.


3. this 포인터

생성자 내부에는 this라는 키워드가 있는데, 이는 객체 자기 자신을 가리키는 포인터이다.왜 필요한지 의문이 들 수 있는데, 한 클래스로 여러 객체를 생성했다고 해보자.여러 객체들은 각각 조금씩 다른 특징(멤버)을 가지고 있으므로 멤버 변수는 객체들 각각의 독립적인 공간에 있게 될 것이다.

 

하지만 메서드의 경우, 객체에 따라 각각 다른 멤버들을 매개변수로 받지만, 내부 루틴은 같으므로 객체별로 일일이 메서드를 할당해주는 것은 비효율적이다.

 

따라서 this라는 객체별로 자신을 가리키는 포인터를 넘겨줌으로써 하나의 정의된 위치에서각 객체가 지니고 있는 다른 멤버들을 가지고 같은 루틴을 반복하게끔 한 것이다.

 

위의 예제에서는 비교적 간단한 이유로 사용한 것인데,Person클래스의 생성자의 매개변수가 멤버 변수의 이름과 동일하므로 구별하기 위해서 this를 사용했다.

 

이외에도, 자기 자신에 대한 포인터를 반환해야 하는 등의 이유로 this포인터를 사용해야 할 수도 있다.


4. 생성자와 디폴트 매개변수

C++에서는 디폴트 매개변수라는 것이 있다.

#include<iostream>

int Sum1ToN(int n = 10)
{
	return n * (n + 1) / 2;
}

int main()
{
	std::cout << "1부터 10까지의 합:" << Sum1ToN() << std::endl;
	std::cout << "1부터 100까지의 합:" << Sum1ToN(100) << std::endl;
	return 0;
}

그림 2. 실행결과.

1부터 N까지의 합을 구해주는 함수 Sum1ToN을 정의했다.

함수의 매개변수를 살펴보면,

int n = 10이라고 10을 기본적으로 대입하고 있다.

이는 해당 매개변수에 값이 할당되지 않을 경우 주어진 값을 넣어서 진행시키라는 의미이다.

그래서 메인 함수를 살펴보면 아무런 값을 전달하지 않았을 때,

n에 10이 들어가서 1부터 10까지의 합이 반환된 것을 확인할 수 있다.

 

이를 생성자에도 적용시키면 어떨까?

붕어빵 클래스를 만들었다고 해보자.

사람들이 보통 붕어빵이라고 하면 팥앙금이 들어간 붕어빵을 떠올린다고 하자.

그러면 붕어빵이라는 객체를 생성할 때 재료로 아무것도 전달하지 않으면 

팥이 들어간 붕어빵을 만드는 게 타당하지 않을까?

#include<iostream>

class FishBread
{
	std::string ingridient;
public:
	FishBread(std::string ingridient = "팥")
	{
		this->ingridient = ingridient;
	}
	void Eat()
	{
		std::cout << "냠냠! " << ingridient << "(이)가 들어간 붕어빵이다!" << std::endl;
	}
};

int main()
{
	FishBread redBean; // redBean()과 같은 의미
	FishBread chouxCream("슈크림");

	redBean.Eat();
	chouxCream.Eat();
	
	return 0;
}

 

그림 3. 실행결과.

메인 함수를 보면 생성자에 아무것도 전달하지 않았을 때,

팥이라는 문자열이 들어가서 객체가 생성된 것을 확인할 수 있다.


5. 기본 생성자

위의 소스에서 메인 함수의 첫 줄 코드를 살펴보면 redBean과 redBean()이 같은 의미라고 되어있다.

글의 맨 첫 번째 소스로 돌아가서 클래스를 살펴보면 생성자가 없는 것을 확인할 수 있다.

그러면 생성자가 없는데 우리는 생성자를 호출해서 객체를 생성했다는 말이 된다.

어떻게 가능한 것일까?

 

C++에서 컴파일러는 클래스에 어떠한 생성자도 정의되지 않을 경우

내용이 텅 비어 있는 기본 생성자를 암시적으로 생성시켜준다.

 

그래서 우리가 생성자를 정의하지 않아도 객체가 생성되었던 것이다.


6. 클래스 배열

클래스의 경우에도 배열로 객체들을 정의하여 사용할 수 있다.

다만, 일반적인 배열과 다르게 선언에서 생성자가 항상 호출된다는 것을 명심하여야 한다.

#include<iostream>

class Monster
{
	std::string name;
	int atk;
	int def;
public:
	Monster(std::string name, int atk, int def)
	{
		this->name = name;
		this->atk = atk;
		this->def = def;
	}
	void Info()
	{
		std::cout << "이름: " << name << std::endl;
		std::cout << "- 공격력: " << atk << std::endl;
		std::cout << "- 방어력: " << def << std::endl;
	}
};

int main()
{
	Monster list[3]; // 에러발생!
	for (int i = 0; i < 3; i++)
		list[i].Info();
	return 0;
}

이 소스는 정상적으로 실행되지 않는다.

그림 4. 에러

에러를 살펴보면 기본 생성자를 요구하고 있다.

이는 배열 생성과정에서 생성자가 호출되기 때문인데,

매개변수를 아무것도 주지 않아서 기본 생성자를 호출하고 있는 것이다.

해결 방법에 대해 알아보자.

6.1. 매개변수 전달하기

#include<iostream>

class Monster
{
	std::string name;
	int atk;
	int def;
public:
	Monster(std::string name, int atk, int def)
	{
		this->name = name;
		this->atk = atk;
		this->def = def;
	}
	void Info()
	{
		std::cout << "이름: " << name << std::endl;
		std::cout << "- 공격력: " << atk << std::endl;
		std::cout << "- 방어력: " << def << std::endl;
	}
};

int main()
{
	Monster list[3] = { Monster("괴물",20,10),Monster("큰괴물",40,20),Monster("아주큰괴물",100,50) };
	for (int i = 0; i < 3; i++)
		list[i].Info();
	return 0;
}

그림 5. 실행결과.

main함수를 살펴보면 배열의 각 원소에 알맞은 매개변수를 넣어 

생성자를 호출해 주고 있는 것을 확인할 수 있다.


6.2. 생성자에 디폴트 매개변수 사용

#include<iostream>

class Monster
{
	std::string name;
	int atk;
	int def;
public:
	Monster(std::string name = "괴물", int atk = 20, int def = 10)
	{
		this->name = name;
		this->atk = atk;
		this->def = def;
	}
	void Info()
	{
		std::cout << "이름: " << name << std::endl;
		std::cout << "- 공격력: " << atk << std::endl;
		std::cout << "- 방어력: " << def << std::endl;
	}
};

int main()
{
	Monster list[3];
	for (int i = 0; i < 3; i++)
		list[i].Info();
	return 0;
}

그림 6. 실행결과.

방법은 우선 배열만 생성되게 하는 의미가 더 크다.

여기서는 나중에 멤버 변수 값을 변경하여 사용하는 것이 더 유용할 것이라 판단된다.


7. 캡슐화

캡슐화는 객체지향 프로그래밍의 핵심적인 내용이다.

객체의 속성과 행위를 하나로 묶고, 실제 구현 내용 일부를 외부에 감추어 은닉한다. - 위키백과

이는 왜 기본적으로 멤버 변수를 선언하면 private으로 선언되는지와 연관이 있다.사용자는 어떤 클래스를 사용할 때, 클래스의 세부 정보인 멤버들에 대해 전부 알 필요가 없다.

 

무슨 말이냐면, 누군가 TV를 본다고 할 때, 리모컨 버튼을 눌러 원하는 채널로 이동한다고 해보자.사용자는 리모컨이 적외선 신호를 어떻게 쏘고 TV가 어떻게 수신하는지에 대해 알 필요가 없고,그 중간 과정에 대한 지식이 없어도 버튼만 누르면 채널 이동이 가능하다.

 

이처럼 사용자에게 필요한 만큼의 인터페이스만 공개하고,나머지는 감추어 잘 묶어주는 것이 캡슐화이다.

 

이론적으로는 그리 어려운 내용이 아니지만, 캡슐화를 잘 적용하는 것은 상당히 힘들다.왜냐하면 캡슐화는 단순히 숨기기만 한다고 되는 것이 아니기 때문이다.

객체를 정의할 때 어디서부터 어디까지를 묶고 분리할지,

어떤 것은 감추고 어떤 것은 공개할지 정하는 것은 매우 추상적이기 때문이다.


 

읽을거리.

OOP(객체지향 프로그래밍) - OOP의 정의

반응형

'개념정리 > C++' 카테고리의 다른 글

6. Friend 키워드  (0) 2021.04.21
5. 참조자  (0) 2021.04.20
4. 동적할당과 소멸자  (0) 2021.04.19
2. Namespace  (0) 2021.04.17
1. 기본 자료형 및 입출력  (0) 2021.04.15

+ Recent posts