<CPP>/BASIC

19. 클라스(Class)

CodeGrimie 2021. 1. 22. 17:13

처음에 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 

 

C++ Core Guidelines

 

isocpp.github.io