[C++] 03. 클래스의 기본

kkado·2023년 10월 12일
0

열혈 C++

목록 보기
3/16
post-thumbnail

💬 윤성우 님의 <열혈 C++ 프로그래밍> 책을 혼자 공부하며 배운 내용을 정리합니다. 글의 모든 내용은 책에서 발췌하였습니다.


구조체부터 살펴보자

C에서 배운 구조체는 서로 연관 있는 데이터를 묶을 수 있는 문법적 장치 이다. 자동차의 속도, 자동차의 연료량 등 어떤 객체에 종속되어 있는 변수들을 하나로 묶을 수 있어 관리하기 용이하다는 장점이 있다.

C에서는 구조체를 사용하기 위해서는 구조체 이름 앞에 struct 라고 명시해 주어서 이어서 선언되는 자료형이 구조체로 정의된 자료형임을 나타내었지만 C++에서는 그럴 필요 없이 그냥 Car basicCar; 처럼 사용할 수 있다.

그리고 아래와 같은 간단한 예제를 만들 수 있다.

struct Car {
    char ID[20];
    int fuelGauge;
    int curSpeed;
};

void ShowCarState(const Car &car) {
    std::cout << "소유자 : " << car.ID << "\n";
    std::cout << "연료 : " << car.fuelGauge << "\n";
    std::cout << "현재 속도 : " << car.curSpeed << "\n";
}

void Accel(Car &car) {
    if (car.fuelGauge <= 0)
        return;
    else
        car.fuelGauge -= 2;
    

    if (car.curSpeed + 10 >= 100)
        car.curSpeed = 100;
    else
        car.curSpeed += 10;
}

void Break(Car &car) {
    if (car.curSpeed < 10)
        car.curSpeed = 0;
    else
        car.curSpeed -= 10;
}

int main() {
    Car firstCar = {"firstCar", 100, 0};
    Car secondCar = {"secondCar", 200, 0};

    Accel(firstCar);
    ShowCarState(secondCar);
}

C++에서는 구조체 안에 함수를 삽입하는 것도 허용한다. 따라서 다음과 같이도 가능하다.

struct Car {
    char ID[20];
    int fuelGauge;
    int curSpeed;

    void ShowCarState() {
        std::cout << "소유자 : " << ID << "\n";
        std::cout << "연료 : " << fuelGauge << "\n";
        std::cout << "현재 속도 : " << curSpeed << "\n";
    }

    void Accel() {
        if (fuelGauge <= 0)
            return;
        else
            fuelGauge -= 2;
        

        if (curSpeed + 10 >= 100)
            curSpeed = 100;
        else
            curSpeed += 10;
    }

    void Break() {
        if (curSpeed < 10)
            curSpeed = 0;
        else
            curSpeed -= 10;
    }
};


int main() {
    Car firstCar = {"firstCar", 100, 0};
    Car secondCar = {"secondCar", 200, 0};
    
    firstCar.Accel();
    secondCar.ShowCarState();
}

함수를 구조체 안에 넣기 전에는 함수에 매개변수로 구조체를 받아서 어떤 구조체에 대한 함수인지를 명시하였으나 구조체 안에 넣은 후에는 이런 명시가 필요없어졌다. 구조체 내에 선언된 변수에 직접 접근이 가능해졌기 때문이다.

만약 여러 구조체가 있다면, 각각의 구조체는 하나의 함수를 공유하여 사용한다.


구조체 안에서 enum 상수의 선언

만약 구조체 안에서만 사용되는 상수로서 매크로 상수를 사용한다면, 전체 코드에서, 즉 구조체와 상관없는 위치에서도 접근 가능하니 차라리 구조체 안에 넣어버리는 것이 좋을 것이다. 이 때 enum 키워드를 통해 열거형 상수를 만들면 좋다.

struct Car {
    enum {
        ID_LEN = 20,
        MAX_SPD = 200,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10,
    };
};

아니면 namespace 안에 정의해도 좋다.

가독성을 위해 함수를 외부로 뺄 수 있다

구조체 안이 여러 함수의 구현부가 길어짐에 따라 가독성이 떨어질 수 있다.
이 때 구조체 내에서 함수의 선언만 진행하고 그 구현부는 구조체 외부로 뺄 수 있다.

struct Car {
    enum {
        ID_LEN = 20,
        MAX_SPD = 200,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10,
    };

    char ID[20];
    int fuelGauge;
    int curSpeed;

    void ShowCarState();
    void Accel();
    void Break();
};


void Car::ShowCarState() {
    std::cout << "소유자 : " << ID << "\n";
    std::cout << "연료 : " << fuelGauge << "\n";
    std::cout << "현재 속도 : " << curSpeed << "\n";
}

void Car::Accel() {
    if (fuelGauge <= 0)
        return;
    else
        fuelGauge -= 2;
    

    if (curSpeed + 10 >= 100)
        curSpeed = 100;
    else
        curSpeed += 10;
}

void Car::Break() {
    if (curSpeed < 10)
        curSpeed = 0;
    else
        curSpeed -= 10;
}

구조체를 딱 보았을 때 어떤 변수가 있고 어떤 함수(기능)이 있는지 한눈에 확인할 수 있어 가독성이 좋아진다.

또한, 구조체 안에 함수가 정의되어 있다면 함수를 인라인으로 처리하라는 메시지가 내포되게 된다. 만약 함수를 구조체 밖으로 빼고 난 후에도 인라인의 의미를 유지하고 싶다면 함수 앞에 inline을 명시해 주어야 한다.


구조체와 클래스의 차이

사실 앞서 살펴본 구조체는 클래스와 별반 다를 바 없다. 클래스는 다음과 같이 사용한다.

class Car {
    char ID[20];
    int fuelGauge;
    int curSpeed;

    void ShowCarState();
    void Accel();
    void Break();
};

그리고 객체를 생성할 때가 다르다.

Car basicCar;

그러면 초기화를 어떻게 하는가.

Car basicCar;
basicCar.fuelGauge = 100;

이런 식으로 하는가? 아니다.

클래스 내에 선언된 변수는 클래스 내에 선언된 함수에서만 접근이 가능하다.

그러나, 클래스의 멤버의 접근에 관해서 추가적으로 명시해주면 접근할 수 있게 된다.

접근 제어 지시자

  • public : 어디서든 접근 허용
  • protected : 해당 클래스를 상속받는 하위 클래스에서의 접근 허용
  • private : 클래스 내에서만 접근 허용

struct Car {
private:
    char ID[20];
    int fuelGauge;
    int curSpeed;
    
public:
    void ShowCarState();
    void Accel();
    void Break();
};

접근 제어 지시자와 관련하여 몇 가지 알아둬야 할 특징이 있다.

  • 접근제어 지시자 A가 선언되면 그 이후에 등장하는 함수나 변수는 다른 접근제어 지시자가 등장할 때까지 모두 A의 범위에서 접근 가능함
  • 함수의 정의를 클래스 밖으로 빼도, 이 함수는 클래스의 일부이기 때문에 private으로 선언한 변수에 접근이 가능함
  • 클래스에 별도의 접근제어 지시자를 선언하지 않으면 모든 변수와 함수는 private
  • 구조체에 별도의 접근제어 지시자를 선언하지 않으면 모든 변수와 함수는 public

C++에서의 파일 분할

보통 클래스를 사용하면서 파일을 나눌 때에는, 헤더 파일(.h)에 클래스의 선언(declaration)을 담고, 소스 파일(.cpp)에 클래스의 정의(definition)를 담는 것이 일반적이다.

클래스의 선언이라 함은 다음과 같다.

struct Car {
private:
    char ID[20];
    int fuelGauge;
    int curSpeed;
    
public:
    void ShowCarState();
    void Accel();
    void Break();
};

클래스를 구성하는 뼈대, 구조라고 볼 수 있을 것이다.

함수의 정의와 선언은 컴파일 된 후에 링커에 의해 하나의 실행 파일로 묶이게 된다.

헤더 파일에다 클래스를 선언하고, 소스 파일에서 이 헤더 파일을 include해서 구현하고, 실질적으로 클래스를 사용하는 파일에서 이 헤더 파일을 include해서 사용한다. 즉 실질적으로 클래스를 사용하는 부분과 클래스를 정의한 부분은 떨어져 있어도 되고 include 되어 있지 않아도 된다.

인라인 함수는 헤더와 함께 두어 컴파일러가 클래스의 선언과 동시에 참조할 수 있게 해야 한다.


profile
울면안돼 쫄면안돼 냉면됩니다

0개의 댓글