19. 클라스(Class)
처음에 C++은 C에 클라스(Class)란 기능 하나가 더해졌다고 해서 C with Class로 불렸다. (C = C + 1;)
이후 버전업을 진행하면서 C++로 이름이 변경되었지만 여전히 C++에서 클라스가 차지하는 비중은 매우 높다.
객체(Object)
클라스(Class)는 보기보다 복잡한 뜻을 가진 단어로 우리나라 말로 번역하면
'특정 범주에 속하는 것으로 간주하거나 할당된 것' 을 의미한다.
그러니까 요약하자면 같은 범주에 있는 변수나 함수들을 한 곳에 모아둔 것이라 볼 수 있는데
이게 프로그래밍에서 흔히 말하는 객체(Object)다.
C++은 이 객체를 중심으로 프로그램을 제작할 수 있기 때문에
OOP(Object Oriented Progamming), 객체지향 프로그래밍 언어로 분류된다.
물론 C와 동일하게 절차 지향 프로그래밍도 가능하다.
여담으로 영어로는 클래스로 읽는 게 아니라 클라스로 발음하는 게 옳다고 한다.
클라스의 기본 구조
클라스는 C언어의 구조체(Structure)에서 파생되어 나온 기능인만큼겉모습도 많이 비슷하다.
▼ 구조체의 구조와 유사하지만 내부에 함수(메서드)를 작성할 수 있다.
#include <cstdio>
// 클라스(class)
class Point2D
{
float x;
float y;
void Print() { printf("(%f, %f)", x, y); };
};
int main()
{
Point2D p1;
return (0);
}
간단하게 요약하자면 클라스는 구조체에 함수를 추가한 것이다.
16. 사용자 정의 자료형 에서도 언급했지만 C++ 표준 위원회에서는 구조체를 사용하는 걸 권장하지 않는다.
구조체는 오로지 C로 작성된 프로그램과 호환해야 할 때만 사용해야 한다.(반드시)
구조체와 달리 별도의 재정의 없이도 일반 변수처럼 Point2D p1;으로 사용할 수 있어서 편리하다.
클라스의 크기
▼ 함수는 크기에 들어가지 않는다.
#include <cstdio>
// 클라스(class)
class Point2D
{
float x;
float y;
void Print() { printf("(%f, %f)\n", x, y); };
};
int main()
{
Point2D p1;
printf("Class Point2D Size : %lld\n", sizeof(p1));
return (0);
}
Point2D 클라스의 크기는 8Byte(float(4) + float(4))로 나올 것이다.
C의 구조체와 동일하게 내부 변수 선언 순서에 따라서 채워 넣기(Padding)이 있다는 것을 알 수 있다.
함수의 경우엔 다른 클래스들도 동일하게 사용할 수 있기 때문에 중복으로 메모리 할당을 하는 것을 피하기 위해서 다른 어딘가에 메모리를 할당해둔다.
멤버 접근 범위 지정자(Access Specifier)
공공재(Public)
C 구조체에서 내부 변수는 누구라도 접근하고 값을 변경할 수 있다.
▼ 같은 기능을 하는 C의 구조체와 C++ 클래스 코드
// 구조체(Struct)
struct Point2D_STR
{
float x;
float y;
};
// 클라스(class)
class Point2D_CLS
{
public:
float x;
float y;
};
int main()
{
struct Point2D_STR p1 = {0.0f, 0.0f};
Point2D_CLS p2 = {0.0f, 0.0f};
return (0);
}
클래스에 공공재(Public)이 붙어 있는데 기본적으로 클라스는 사적재(Private)로 제작되기 때문이다.
C++ 언어 철학에서는 객체가 가지는 기능(함수)을 주고받는 것을 중요하게 생각한다.
가령 예를 들어서 우리가 감기약을 먹을 때 그 감기약이 감기를 이겨내는데 도움을 주는 기능 때문에
감기약을 먹는 것이지 안에 내부 성능이 무엇인지 때문에 먹지는 않는다.
이를 캡슐(Capsule)화라고 한다.
그래서 기본적으로 C++에서는 특정한 명시가 없다면 기본적으로 사적재(Private)로 생각하기 때문에 밖에서 사용하려면 공공재(Public)이라고 따로 명시해줘야 한다.
사적재(Private)
위에서 설명했듯이 C++은 기본적으로 사적재(Private)로 생각한다.
그리고 다르게 말하면 사적재가 잘 유지되는 코드가 객체지향적이라고 해석할 수도 있다.
▼ 접근 불가능하다고 에러가 발생한다.
#include <cstdio>
class Phill {
int _ingredient;
public:
void PrintIngredient()
{
printf("Ingredient : %d\n", _ingredient);
};
void Cure() { /* Cure Peaple*/ };
};
int main()
{
Phill p1;
p1.PrintIngredient();
p1._ingredient = 1; // ERROR
return (0);
}
큰 이유가 없는 한 C++에서 변수들은 되도록이면 사적재(Private)로 관리하는 것이 좋다.
보호재(Protected)
보호재는 상속한 자식들도 접근해야 하는 경우 사용한다.
헤더 파일(.hpp), 소스 파일(.cpp)
C++의 클라스를 최대한 활용하기 위해서는 소스 파일과 헤더 파일을 구분하는 게 좋다.
객체가 하는 기능에 따라 클라스를 만들기 때문에 분업화하기 좋다.
▼ 원래 C /C++ 코드는 헤더와 바디 부분으로 나뉜다.
//////////////////////////////
// HEADER
//////////////////////////////
#include <cstdio>
void PrintHelloWorld();
//////////////////////////////
//BODY
//////////////////////////////
int main()
{
PrintHelloWorld();
return (0);
}
void PrintHelloWorld()
{
printf("Hello, World\n");
}
X, Y 좌표를 가지는 Point2D라는 간단한 클라스를 예제로 살펴본다.
헤더파일(.hpp)
▼ 헤더파일(.hpp)은 선언만 하는 게 이상적이다.
#ifndef _POINT2D_
#define _POINT2D_
class Point2D
{
private:
float _x;
float _y;
public:
Point2D(float x, float y);
Point2D();
~Point2D();
void SetPoint2D(float x, float y);
float GetX();
float GetY();
};
#endif
C++헤더 파일의 확장자는 .hpp 나 .hh 둘 다 사용할 수 있다.C 헤더 파일의 확장자가 .h이기 때문에 C 헤더와 CPP 헤더와 겹치지 않도록 하기 위함이다.물론 .h를 사용해도 컴파일하는 데는 문제없지만 권장되지는 않는다.
소스파일(.cpp)
▼ 소스파일(.cpp)은 구현만 하는 게 이상적이다.
#include <cstdio>
#include "Point2D.hpp"
Point2D::Point2D(float x, float y)
{
printf("Point2D Constructor Call\n");
_x = x;
_y = y;
}
Point2D::Point2D()
{
printf("Point2D Constructor Call\n");
_x = 0;
_y = 0;
}
Point2D::~Point2D()
{
printf("Point2D Destructor Call\n");
_x = 0;
_y = 0;
}
void Point2D::SetPoint2D(float x, float y)
{
_x = x;
_y = y;
}
float Point2D::GetX()
{
return (_x);
}
float Point2D::GetY()
{
return (_y);
}
단순히 헤더 파일에서 선언한 함수들을 구현한다.
헤더 파일과 마찬가지로 C와 겹치지 않도록 .CPP나 .CC로 두 개의 확장자 모두 사용 가능하다.
생성자(Constructor)와 소멸자(Destrcutor)
초반에 위치한 Point2D()와 ~Point2D() 함수는 클라스의 생명주기(Life Cycle)와 연관되어 있다.
이름에서도 알 수 있다시피 클라스를 생성할 때 Point2D()를 호출하고
클라스가 소멸할 때 ~Point2D()를 호출한다.
▼ 이렇게 보면 클라스의 생명 주기를 한눈에 볼 수 있다.
#ifndef _EXAMPLE_
#define _EXAMPLE_
class Example
{
public:
Example();
void DoSomething();
~Example();
};
#endif
클라스가 정상적으로 생성하고 소멸했는지가 중요하기 때문에 보통 여기에 출력문을 작성해서 정상여부를 확인한다.
그래서 위 코드처럼 보통 생성자에서 클라스의 변수를 초기화 한다.
혹은 생성자에 멤버 초기화 리스트(Member Initialize List)를 사용할 수 있다.
▼ 멤버 초기화 리스트(Member Initialize List)
#ifndef _POINT2D_
#define _POINT2D_
class Point2D
{
private:
float _x;
float _y;
public:
Point2D() : _x(0.0f), _y(0.0f);
~Point2D();
};
#endif
▼ Point2D를 사용하는 Main 함수의 코드는 훨씬 간결해졌다.
#include <cstdio>
#include "Point2D.hpp"
int main()
{
Point2D p1 = {100.0f, 100.0f};
printf("P1 : (%f, %f)\n", p1.GetX(), p1.GetY());
return (0);
}
Point2D를 따로 빼내어서 작성하면서 파일 수는 3개로 늘어났지만 Main 함수의 코드는 극적으로 짧아졌다.
이게 클라스의 큰 장점이다.
만약 다른 프로그램에서 Point2D를 사용해야 할 일이 있다면 그저 헤더 파일을 포함하기만 하면 된다.
이걸 재사용 가능 코드라고 부른다.
복사 생성자
복사 생성자는 말 그대로 다른 클라스를 참조해서 내부 값을 대입하는 연산자다.
Point B(A); 가 가능하도록 해주기 때문에 클라스를 하나의 변수로서 사용할 수 있도록 도와준다.
▼ 생성자에게 클라스 타입을 넘겨주는 거라고 생각하면 된다.
#ifndef __POINT_H__
#define __POINT_H__
class Point
{
private:
int _x, _y;
public:
Point(int x, int y);
Point();
~Point();
Point(const Point& ref);
void SetPoint(int x, int y);
int GetX();
int GetY();
};
#endif
연산자 오버로딩
클라스의 연산자를 오버로딩해서 프로그래머가 원하는 작동을 하도록 설정할 수 있다.
A+B를 A*B로도 만들 수 있는 엄청난 기능이다.
▼ (클라스 명) operator(연산자)(대상 타입)으로 작성할 수 있다.
#ifndef __POINT_H__
#define __POINT_H__
class Point
{
private:
int _x, _y;
public:
Point(int x, int y);
Point();
~Point();
Point operator=(const Point& pPoint);
Point operator+(const Point& pPoint);
Point operator+=(const Point& pPoint);
Point operator-(const Point& pPoint);
Point operator-=(const Point& pPoint);
Point operator*(const int& num);
Point operator*=(const int& num);
Point operator/(const int& num);
Point operator/=(const int& num);
Point operator%(const int& num);
Point operator%=(const int& num);
void SetPoint(int x, int y);
int GetX();
int GetY();
};
#endif
위에 작성된 것 말고도 많은 연산자들이 있다.
필요에 따라 만들어서 사용하는 것이 가장 좋다.
C++ 스타일 가이드
C++ 은 C와 달리 객체(Object)를 고려해서 작성해야 하기 때문에
좋은 스타일 가이드를 읽고 따르면 어떤 실수를 방지하고 어떤 기능들이 편리한지 알 수 있다.
지금 보고 있는 가이드는 C++ 코어 스타일 가이드다.
isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#main