C++

Chapter 03 클래스의 기본

kdi6316 2025. 1. 7. 15:28

 

Chapter 03 클래스의 기본

 

목차

 

1. C++에서의 구조체

2. 클래스(Class)와 객체(Object)

3. 객체지향 프로그래밍의 이해

위의 목차를 클릭하면 해당 글로 자동 이동 합니다.

 

C++에서의 구조체

구조체의 등장배경은?

C언어로 프로그램을 구현한다면, 구조체의 정의는 항상 뒤를 따르기 마련이다. 그렇다면 구조체가 주는 이점이 무엇이기에 이렇듯 중요한 위치를 차지하고 있는 것일까? 

 

"연관 있는 데이터를 하나로 묶으면, 프로그램의 구현 및 관리가 용이하다."

소프트웨어를 단순히 표현하면 다음과 같다.

"소프트웨어 = 데이터의 표현 + 데이터의 처리"

 

그런데 '표현해야 하는 데이터'는 항상 부류를 형성하기 마련이다. 그리고 이렇게 부류를 형성하는 데이터들은 함께 생성, 이동 및 소멸된다는 특성이 있다. 그래서 구조체는 연관있는 데이터를 묶을 수 있는 문법적 장치로 데이터의 표현에 매우 큰 도움을 준다. 예를 들어보자

ex) 레이싱게임의 자동차 구현하기

 

표현되어야 하는 유사한정보

-소유주

-연료량

-현재속도

-취득점수

-취득아이템

 

게임 사용자가 게임을 종료하면, 위의 정보는 데이터베이스(또는 파일)에 함께 저장되어야 하며, 다시 게임을 시작하면, 저장된 위의 정보는 모두 함께 복원되어야 한다. 따라서 이들 정보를 이용해서 다음과 같이 구조체를 정의하면 프로그래밍이 한결 수월해진다.

struct Car
{
	char gamerID[ID_LEN]; //소유자 ID,ID_LEN은 매크로 상수로 가정
    int fuelGauge; // 연료량
    int curSpeed; // 현재속도
};

취득점수와 취득아이템을 제외한 나머지를 포함한 구조체로 정의해 보았다.

 

C++에서의 구조체 변수의 선언

C언어에서 구조체 변수를 선언하는 방법은 다음과 같다.

struct Car basicCar;

struct Car simpleCar;

앞에 삽입된 키워드 struct는 이어서 선언되는 자료형이 구조체를 기반으로 정의된 자료형임을 나타낸다. 그리고 키워드 struct를 생략하려면 별도의 typedef선언을 추가해야 한다. 하지만 C++에서는 기본 자료형 변수의 선언방식이나 구조체를 기반으로 정의된 자료형의 변수 선언방식에 차이가 없다. C++에서는 별도의 typedef 선언 없이도 다음과 같이 변수를 선언할 수 있다.

Car basicCar;

Car simpleCar;

앞서 정의한 구조체를 기반으로한 예제

#include <iostream>
using namespace std;

#define IN_LEN	20  //4~8행: 구조체 Car와 관련된 정보를 상수화 하였다. 각각의 상수가 의미하는 바는 관련 함수를 통해서 이해할 수 있다.
#define MAX_SPD	200
#define FUEL_STEP	2
#define ACC_STEP	10
#define BRK_STEP	10

struct Car
{
	char gamerID{ID_LEN}; // 소유자 ID
    int fuelGauge; // 연료량
    int curSpeed; // 현재속도
};

void ShowCarState(const Car &car) //차의 정보를 출력하는 기능의 함수이다. 단순히 정보를 출력만하기 때문에 const참조자를 매개변수로 선언하였다
{
	cout <<"소유자ID: "<<car.gamerID<<endl;
    cout <<"연료량: "<<car.fuelGauge<<"%"<<endl;
    cout <<"현재속도: "<<car.curSpeed<<"km/s"<<endl<<endl;
}
void Accel(Car &car) //차의 가속을 위해서 엑셀을 밟은 상황을 표현해 놓은 함수이다. 엑셀을 밟을 때마다 연료가 줄어들고 스피드가 올라가는 상황을 단순히 표현하였다.
{
	if(car.fuelGauge<=0)
    	return;
    else
    	car.fuelGauge -= FUEL_STEP;
        
    if(car.curSpeed+ACC_STEP>=MAX_SPD)
    {
    	car.curSpeed=MAX_SPD;
        return;
    }
    
    car.curSpeed+=ACC_STEP;
}
void Break(Car &car) //브레이크를 밟은 상황을 표현한 함수. 브레이크도 연료의 소모가 동반되지만, 단순히 속도가 감속하는 것으로 표현
{
	if(car.curSpeed<BRK_STEP)
    {
    	car.curSpeed=0;
        return;
    }
    car.curSpeed-=BRK_STEP;
}

int main(void)
{
	Car run99 = {"run99",100,0}; // 구조체 변수의 선언 및 초기화 진행
    Accel(run99); //51~55행 엑셀과 브레이크를 밟은 상황을 연출하고 있다.
    Accel(run99);
    ShowCarState(run99);
    Break(run99);
    ShowCarState(run99);
    
    Car sped77={"sped77",100,0};
    Accel(sped77);
    Break(sped77);
    ShowCarState(sped77);
    return 0;
}

함수는 결국 데이터의 처리를 담당하는 도구이니, 데이터와 함께 부류를 형성하는 것은 매우 당연하다.

위의 세 개의 함수는 "구조체 Car와 함께 부류를 형성하여, Car와 관련된 데이터의 처리를 담당하는 함수들이다."

따라서 위의 함수들은 구조체 Car에 종속적인 함수들이라고 말할 수 있다. 그럼에도 불구하고 전역함수의 형태를 띠기 때문에, 이 함수들이 구조체 Car에 종속적임을 나타내지 못하고 있는 상황이다. 따라서 엉뚱하게도 다른 역역에서 이 함수를 호출하는 실수를 범할 수도 있는 상황이다.

 

구조체 안에 함수 삽입하기

구조체 Car에 종속적인 함수들을 구조체 안에 함께 묶어버리면 자동차와 관련된 데이터와 함수를 모두 묶는 셈이 되기 때문에 확실한 구분이 가능하다. C++에서는 구조체 안에 함수를 삽입하는 것을 허용한다.

struct Car
{
	char gamerID{ID_LEN}; // 소유자 ID
    int fuelGauge; // 연료량
    int curSpeed; // 현재속도
    
    void ShowCarState() // 함수가 구조체 내에 삽입되면서 구조체 내에 선언된 변수에 직접접근이 가능해 졌다
	{
	cout <<"소유자ID: "<<car.gamerID<<endl;
    cout <<"연료량: "<<car.fuelGauge<<"%"<<endl;
    cout <<"현재속도: "<<car.curSpeed<<"km/s"<<endl<<endl;
	}
    
    void Accel() 
	{
	if(car.fuelGauge<=0)
    	return;
    else
    	car.fuelGauge -= FUEL_STEP;
        
    if(car.curSpeed+ACC_STEP>=MAX_SPD)
    {
    	car.curSpeed=MAX_SPD;
        return;
    }
    
    car.curSpeed+=ACC_STEP;
	}
    
    void Break(Car &car) 
	{
	if(car.curSpeed<BRK_STEP)
    {
    	car.curSpeed=0;
        return;
    }
    car.curSpeed-=BRK_STEP;
	}

    
};

int main(void)
{
	Car run99 = {"run99",100,0}; // 구조체 변수의 선언 및 초기화 진행
    Accel(run99); //51~55행 엑셀과 브레이크를 밟은 상황을 연출하고 있다.
    Accel(run99);
    ShowCarState(run99);
    Break(run99);
    ShowCarState(run99);
    
    Car sped77={"sped77",100,0};
    Accel(sped77);
    Break(sped77);
    ShowCarState(sped77);
    return 0;
}

 

구조체 안에 enum 상수의 선언

구조체 안에서만 필요한 상수를 선언할때에는 열거형 enum를 구조체 안에 사용하여 구조체 내에서만 유효한 상수를 정의하면 된다.

struct Car
{
	enum
    {
    	ID_LEN	=20,
        MAX_SPD	=200,
        FUEL_STEP	=2,
        ACC_STEP	=10,
        BRK_STEP	=10
    };
    
    char gamerID[ID_LEN];
    int fuelGauge;
    int curSpeed;
    
    void ShowCarState(){.....}
    void Accel(){.....}
    void Break(){.....}
};

 

함수는 외부로 뺄 수 있다

구조체의 경우 보는순간, 정의되어 있는 ㅎ마수의 종류와 기능이 한눈에 들어오게끔 코드를 작성하는 것이 좋다. 따라서 구조체 내에 정의된 함수의 수가 많거나 그 길이가 길다면, 다음과 같이 구조체 밖으로 함수를 빼낼 필요가 있다.

struct Car
{
	..........
    void ShowCarState();
    void Accel();
    ............
};

void Car::ShowCarState()
{
	................
}
void Car::Accel()
{
	..............
}

즉 함수의 원형선언을 구조체 안에 두고, 함수의 정의를 구조체 밖으로 빼내는 것이다. 다만, 빼낸 다음에 해당 함수가 어디에 정의되어 있는지에 대한 정보만 추가해주면 된다.

 

사실 구조체 안에 함수가 정의되어 있으면, 다음의 의미가 더불어 내포된다.

"함수를 인라인으로 처리해라!"

위의 예제와 같이 함수를 구조체 밖으로 빼내면, 이러한 의미가 사라진다. 따라서 인라인의 의미를 그대로 유지하려면 다음과 같이 키워드  inline을 이용해서 인라인 처리를 명시적으로 지시해야 한다

inline void Car::ShowCarState(){.......};

inline void Car::Accel(){.......};

 

클래스(Class)와 객체(Object)

C++의 구조체는 클래스의 일종이다, 그렇다면 클래스와 구조체에는 어떠한 차이점이 있을까?

 

클래스와 구조체의 유일한 차이점

키워드 struct를 대신해서 class를 사용하면, 구조체가 아닌 클래스가 된다. 즉,  아레의 코드는 클래스의 정의이다.

class Car
{
	char gamerID[CAR_CONST::ID_LEN];
    int fuelGauge;
    int curSpeed;
    
    void ShowCarState(){......}
    void Accel(){......}
    void Break(){......}
};

struct에서 class로 바꿨을 뿐인데 앞서 예제에서 보였던 방식으로 변수 선언이 불가능 하다

ex) Car run99 = {"run99",100,0}; // 불가능 하다

이유는 클래스 내에 선언된 함수가 아닌, 다른 영역에서 변수를 초기화하려 했기 때문이다. 클래스는 기본적으로(별도의 선언을 하지 않으면) 클래스 내에 선언된 변수는 클래스 내에 선언된 함수에서만 접근이 가능하다. 따라서 다음과 같은 형태로 클래스 변수를 선언해야 한다.

 

그렇다면 어떻게 초기화를 해야할까?

int main(void)
{
	Car run99;
    strcpy(run99.gamerID,"rung99"); (x)
    run99.fuelGauge=100; (x)
    run99.curSpeed=0; (x)
}

이들은 모두 컴파일 되지않는다. 클래스 내에서 선언된 변수는 기본적으로 클래스 내에 선언된 함수에서만 접근이 가능하기 때문이다. 클래스는 멤버의 접근과 관련해서 다음과 같이 이야기한다.

"접근과 관련해서 별도의 선언을 하지 않으면, 클래스 내에 선언된 변수 및 함수에 대한 접근은 허용하지 않는다, 접근과 관련된 지시를 별도로 내려달라"

이렇든 클래스는 정의를 하는 과정에서 각각의 변수 및 함수의 접근 허용범위를 별도로 선언해야 한다. 그리고 이것이 키워드 struct를 이용해서 정의하는 구조체와 키워드 class를 이용해서 정의하는 클래스의 차이점이다

 

접근제어 지시자(접근제어 레이블)

C++의 접근제어 지시자는 다음과 같이 총 세가지가 존재한다.

public 어디서든 접근 허용
protected 상속관계에 놓여있을 때, 유도 클래스에서의 접근허용
private 클래스 내(클래스 내에 정의된 함수)에서만 접근허용
#include <iostream>
#include <cstring>
using namespace std;

namespace CAR_CONST
{
	enem
    {
    	ID_LEN=20,
        MAX_SPD=200,
        FUEL_STEP=2,
        ACC_STEP=10,
        BRK_STEP=10
    };
}

class Car
{
pirvate: //다른 함수에서 접근 불가
	char gamerID[CAR_CONST::ID_LEN];
    int fuelGauge;
    int curSpeed;
public: //다른 함수에서 함수호출이 가능하다
	void InitMembers(char * ID,int fuel); //클래스 안에 선언된 변수의 초기화를 목적으로 정의된 함수
    void ShowCarState();
    void Accel();
    void Break();
};

void Car::InitMembers(char * ID,int fuel)
{
	strcpy(gamerID,ID);
    fuelGauge=fuel;
    curSpeed=0;
}

void Car::ShowCarState()
{
	..................
}
void Car::Accel(){
	.................
}
void Car::Break(){...............}

-접근제어 지시자 A가 선언되면, 그 이후에 등장하는 변수나 함수는 A에 해당하는 범위 내에서 접근이 가능하다.

-그러나 새로운 접근제어 지시자 B가 선언되면, 그 이후로 등장하는 변수나 함수는 B에 해당하는 범위 내에서 접근이 가능하다

-함수의 정의를 클래스 밖으로 빼도, 이는 클래스으 ㅣ일부이기 때문에,함수 내에서는 private으로 선언된 변수에 접근이 가능하다.

-키워드 struct를 이용해서 정의한 구조체(클래스)에 선언된 변수와 함수에 별도의 접근제어 지시자를 선언하지 않으면 모든 변수와 함수는 public으로 선언된다

-키워드 class를 이용해서 정의한 클래스에 선언된 변수와 함수에 별도의 접근제어 지시자를 선언하지 않으면, 모든 변수와 함수는 private로 선언된다.

 

C++에서의 파일 분할

어떠한 프로그램이건 하나의 파일에 모든 것을 담진 않는다. C++은 클래스 별로 헤더파일과 소스파일을 생성해서 클래스의 선언과 정의를 분리하는 경우가 많기 때문에 많은 수의 파일이 만들어진다.

ex)

Car.h -> 클래스의 선언을 담는다(클래스를 구성하는 외형적인 틀)

Car.cpp -> 클래스의 정의(멤버함수의 정의)를 담는다

 

인라인 함수는 헤더파일에 함께 넣어야한다

"컴파일 과정에서 함수의 호출 문이 있는 곳에 함수의 몸체 부분이 삽입되어야 하기 때문이다"

 

 

객체지향 프로그래밍의 이해

객체는 영어로 Object이다. 그리고 이의 사전적 의미는 다음과 같다. 물론 더 많은 의미가 있지만 C++에서 말하는 Object의 의미는 이것이다. "사물, 또는 대상"

즉, Object는 우리 주변에 존재하는 물건(연필,나무,지갑,돈 등등)이나 대상(철수,친구,선생님 등등)전부를 의미한다

그렇다면 객체를 지향하는 프로그래밍이라는 것은 무엇인가?

예를 들어보겠다

"나는 과일장수에게 두 개의 사과를 구매했다"

이 문장에 삽입되어있는 객체의 종류는 다음과 같다

나,과일장수,사과

'나' 라는 객체가 '과일장수'라는 객체로부터 '과일' 객체를 구매하는 액션을 취할 수 있어야 한다.

객체지향 프로그래밍에서는 '나' ,'과일장수' 와 같은 객체를 등장시킬 수 있을 뿐만 아니라 '나'라는 객체가 '과일장수'라는 객체로부터 '과일' 객체를 구매하는 행위도 그대로 표현할 수 있다. 즉

객체지향 프로그래밍은 현실에 존재하는 사물과 대상, 그리고 그에 따른 행동을 있는 그대로 실체화 시키는 형태의 프로그래밍이다

 

객체를 이루는 것은 데이터와 기능이다

프로그램상에 과일장수 객체가 존재한다고 가정해 보자, 이 객체는 무엇으로 이뤄져야 하는가? 

-과일장수는 과일은 판다(행동)

-과일장수는 사과 20개, 오렌지 10개를 보유하고 있다(상태)

-과일장수의 과일판매 수익은 현재까지 50,000원 이다(상태)

 

이처럼 객체는 하나 이상의 상태 정보(데이터)와 하나 이상의 행동(기능)으로 구성 이 되며, 상태 정보는 변수를 통해서 표현이 되고,행동은 함수를 통해서 표현이 된다

-보유하고 있는 사과의 수 -> int numOfApples;

-판매 수익 -> int myMoney;

 

그렇다면 과일장수의 행위인 과일의 판매를 함수로 표현해보자

int SaleApples(int money)//사과 구매액이 함수의 인자로 전자
{
	int num = money/1000; // 사과과 개당 1000원이라고 가정
    numOfApples -= money; // 사과의 수가 줄어들고
    myMoney += money; // 판매 수익이 발생한다
    return num; // 실제 구매가 발생한 사과의 수를 반환
}

이렇게 해서 과일장수 객체를 구성하게 되는 변수와 함수가 마련되었으니, 이제 이들을 묶어서 객체로 실체화하는 일만 남았다.

 

'과일장수'의 정의와 멤버변수의 상수화에 대한 논의

객체를 생성하기에 앞서 객체의 생성을 위한 틀을 먼저 만들어야 한다

Class FruitSeller
{
private:
	int APPLE_PRICE;
    int numOfApples;
    int myMoney;
    
    
public:
	int SaleApples(int money)
    {
    	int num=money/APPLE_PRICE;
        numOfApples -= num;
        myMoney+=money;
        return num;
    }
}

위의 틀처럼 변수를 선언하고 함수를 정의 하였다

위의 클래스 정의는 과일장수의 틀이 되는것이다

 

클래스 기반의 두 가지 객체생성 방법

만들어 놓은 클래스에 접근하려면 객체를 생성해야한다

ClassName objName; //일반적인 변수의 선언방식

ClassName * ptrObj=new ClassName; //동적 할당방식(힙 할당방식)

 

동적할당을 위해서는 다음과 같이하면 된다

ClassName * ptrObj = new ClassName;