SW/C++

C++11 : 클래스와 객체에 대해 알아볼까요? (개념 및 예제)

얇은생각 2019. 1. 4. 07:30
반응형

클래스가 왜 필요할까요? 컴파일러가 int형의 모든 속성과 연산 기능에 대해 이미 정의했으므로 컴파일러가 알아서 프로그램을 수행합니다. 하지만 1실생활의 여러 상황을 프로그래밍하려면 int와 같은 기본 자료형으로는 표현에 한계가 있습니다. 이러한 한계를 극복하려면 앞서 배운 C++ 구조체처럼 사용자가 직접 정의할 수 있는 자료형이 필요합니다. 실생활의 상황을 표현하는 자료형을 사용하는 것은 추상화라는 개념의 출발이라 할 수 있습니다. 추상화란 실생활에서 객체의 속성과 기능을 표현하는 개념이라고 양지해주세요. C++ 클래스 역시 실생활의 상황을 추상화할 수 있는 사용자 정의 자료형인 것입니다.



C++ 클래스와 객체 개념 바로 알기

이전에 포스팅하고 다루어 보았던 구조체 예제를 클래스로 바꾸어 보겠습니다.


#include <iostream>
using namespace std;
 
#define NAME_LEN 20
#define SEX_LEN 10
#define JOB_LEN 20
#define CHARACTER_LEN 20
 
class Chulsoo
{
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
 
    void introduce()
    {
        cout << "이름: " << name << endl;
        cout << "성별: " << sex << endl;
        cout << "직업: " << job << endl;
        cout << "성격: " << character << endl;
        cout << "나이: " << age << endl;
        cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
    }
 
    void eat(char * food)
    {
        cout << "철수는 " << food << "를 먹는다" << endl;
    }
 
    void sleep()
    {
        cout << "철수는 잔다" << endl;
    }
 
    void drive(char * destination)
    {
        cout << "철수는 " << destination << "으로 운전한다" << endl;
    }
 
    void write()
    {
        cout << "철수는 책을 쓴다" << endl;
    }
 
    void read()
    {
        cout << "철수는 책을 읽는다" << endl;
    }
};
 
class Younghee
{
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
 
    void introduce()
    {
        cout << "이름: " << name << endl;
        cout << "성별: " << sex << endl;
        cout << "직업: " << job << endl;
        cout << "성격: " << character << endl;
        cout << "나이: " << age << endl;
        cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
    }
 
    void eat(char * food)
    {
        cout << "영희는 " << food << "를 먹는다" << endl;
    }
 
    void sleep()
    {
        cout << "영희는 잔다" << endl;
    }
 
    void shopping()
    {
        cout << "영희는 쇼핑을 한다" << endl;
    }
};
 
int main(void)
{
    Chulsoo chulsoo = { "철수""남성""작가""diligent"32true };
    Younghee younghee = { "영희""여성""주부""impatient"32true };
    chulsoo.drive("레스토랑");
    chulsoo.eat("스테이크");
    younghee.eat("스테이크");
    chulsoo.drive("집");
    younghee.sleep();
    chulsoo.write();
    chulsoo.read();
    chulsoo.sleep();
    cout << endl;
    chulsoo.introduce();
    cout << endl;
    younghee.introduce();
    return 0;
}
cs


이 예제는 오류가 많습니다. 오류를 잡아가면서 구조체와 클래스의 차이점을 쉽게 알 수 있습니다. 코드의 변경 사항을 살펴보면 struct 키워드를 class로 변경하였습니다. 다음의 오류들은 어떤 것이 문제일까요?



데이터 은닉

C++를 포함한 객체지향 프로그래밍은 데이터 은닉과 캡슐화를 지향합니다. 데이터 은닉은 클래스 안의 속성과 기능을 선택적으로 숨기는 것을 의미합니다. 


왜 숨겨야 할까요?   철수와 영희 클래스가 있습니다. 여러 멤버 변수 중 이름을 나타내는 char name[NAME_LEN] 이라는 변수가 있습니다. 


만약 영희 클래스의 멤버 함수나 main 함수 등 철수 클래스가 아닌 영역에서 철수 클래스의 멤버 변수인 이름을 수정하려고 하면 어떤 상황이 벌어질까요? 실생활에서는 철수가 개명하지 않는 이상 이름은 변경되면 안되므로 있어서는 안되는 상황입니다. 


이처럼 철수 클래스의 멤버를 철수 클래스가 아닌 다른 영역의 함수에서 변경하려고 하면 C++가 추구하는 객체지향 프로그래밍 규율에 어긋나기 때문에 오류가 발생합니다. 이것은 객체지향 프로그래밍의 데이터 은닉으로 설명할 수 있습니다. 바로 이것이 캡슐화의 시작인 것이죠.


Main 함수에서 철수 클래스의 멤버 변수에 값을 넣고 있습니다. 이는 데이터 은닉을 위반하는 상황이므로 철수 클래스가 아닌 다른 영역에서 초기화 작업을 수행할 수 없어서 오류가 발생하는 것입니다. 그렇다면 데이터 은닉을 위반하지 않으면서 오류가 발생하지 않게 하는 방법은 무엇이 있을까요?


#include <iostream>
using namespace std;
 
#define NAME_LEN 20
#define SEX_LEN 10
#define JOB_LEN 20
#define CHARACTER_LEN 20
 
class Chulsoo
{
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
 
    void introduce()
    {
        cout << "이름: " << name << endl;
        cout << "성별: " << sex << endl;
        cout << "직업: " << job << endl;
        cout << "성격: " << character << endl;
        cout << "나이: " << age << endl;
        cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
    }
 
    void eat(char * food)
    {
        cout << "철수는 " << food << "를 먹는다" << endl;
    }
 
    void sleep()
    {
        cout << "철수는 잔다" << endl;
    }
 
    void drive(char * destination)
    {
        cout << "철수는 " << destination << "으로 운전한다" << endl;
    }
 
    void write()
    {
        cout << "철수는 책을 쓴다" << endl;
    }
 
    void read()
    {
        cout << "철수는 책을 읽는다" << endl;
    }
 
public:
    void constructor(char * name, char * sex, char * job, char * character, int age, bool marriageStatus)
    {
        strcpy_s(this->name, name);
        strcpy_s(this->sex, sex);
        strcpy_s(this->job, job);
        strcpy_s(this->character, character);
        this->age = age;
        this->marriageStatus = marriageStatus;
    }
};
 
class Younghee
{
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
 
    void introduce()
    {
        cout << "이름: " << name << endl;
        cout << "성별: " << sex << endl;
        cout << "직업: " << job << endl;
        cout << "성격: " << character << endl;
        cout << "나이: " << age << endl;
        cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
    }
 
    void eat(char * food)
    {
        cout << "영희는 " << food << "를 먹는다" << endl;
    }
 
    void sleep()
    {
        cout << "영희는 잔다" << endl;
    }
 
    void shopping()
    {
        cout << "영희는 쇼핑을 한다" << endl;
    }
 
public:
    void constructor(char * name, char * sex, char * job, char * character, int age, bool marriageStatus)
    {
        strcpy_s(this->name, name);
        strcpy_s(this->sex, sex);
        strcpy_s(this->job, job);
        strcpy_s(this->character, character);
        this->age = age;
        this->marriageStatus = marriageStatus;
    }
};
 
int main(void)
{
    Chulsoo chulsoo;
    Younghee younghee;
    chulsoo.constructor("철수""남성""작가""diligent"32true);
    younghee.constructor("영희""여성""주부""impatient"32true);
    chulsoo.drive("레스토랑");
    chulsoo.eat("스테이크");
    younghee.eat("스테이크");
    chulsoo.drive("집");
    younghee.sleep();
    chulsoo.write();
    chulsoo.read();
    chulsoo.sleep();
    cout << endl;
    chulsoo.introduce();
    cout << endl;
    younghee.introduce();
    return 0;
}
cs


예제를 실행해보면 여전히 오류가 발생하지만, 첫 번째 그룹의 메시지는 없어졌을 것입니다. Main() 함수에서 초기화하기 때문에 발생한 오류를 해결하기 위해 철수 클래스의 멤버 변수를 철수 클래스 영역에서 초기화한 차이가 있습니다.



멤버 접근 지정자: public, private, protected

클래스 멤버 접근 지정자에는 public, private, protected 3개가 있습니다.


public : 클래스 내부 영역이든 상관없이 클래스 멤버에 접근할 수 있도록 한다.

private : 클래스 내부 영역에서만 클래스 멤버에 접근할 수 있도록 한다.

protected 기본적으로 클래스 내부 영역에서만 클래스 멤버에 접근할 수 있지만, 클래스 간의 상속관계에서 하위 클래스에서도 상위 클래스 멤버에 접근할 수 있도록 한다.


철수와 영희 클래스의 constructor() 멤버 함수는 접근 지정자를 public으로 지정했으므로 클래스 내부 영역이든 외부 영역이든 상관없이 해당 멤버 함수에 접근할 수 있습니다. 즉 철수와 영희 클래스의 외부 영역인 main() 함수에서 각 클래스 내부에 선언 및 정의한 constructor() 멤버 함수에 접근할 수 있습니다. 


C++ 클래스에서는 특별한 접근 지정자를 지정하지 않으면 private 키워드의 영향을 받기 때문입니다. C++ 클래스의 디폴트 멤버 접근 지정자는 private입니다. 


constructor() 함수를 살펴보겠습니다. 철수와 영희 클래스에서 constructor() 함수의 코드는 똑같습니다. constructor() 함수는 생성자 (constructor)와 같은 역할을 하여 함수 이름을 constructor라고 지었습니다. 생성자는 클래스의 멤버 변수들을 초기화할 때 쓰이는 함수라고만 알아두시면 됩니다.


constructor() 함수에서 strcpy_s() 함수를 호출합니다. 이 함수는 C에서 배운 문자열 복사 함수 strcpy()와 같은 역할을 수행합니다. 함수 이름에 '_s'가 추가된 이유는 Visual C++ 최신 버전에서 strcpy() 함수의 보안성 강화를 위해서 만들었기 때문입니다. 



자신을 가리키는 포인터 : this

this라는 키워드가 보입니다. this는 한글로 이것이라는 의미이며, 클래스 내부에서 자기 클래스를 가리키는 포인터 역할을 합니다. 자세한 내용은 추후에 더 다루도록 하겠습니다.


this 키워드는 왜 사용할까요? constructor() 함수 관점에서 name 변수는 첫번째 매개변수로 받은 name일 수도 있습니다. 그러나 클래스의 멤버 변수인 name일 수도 있습니다. 따라서 constructor() 함수에서 name이라는 변수를 사용할 때는 두 name 변수 중 어떤 것인지를 지정할 필요가 있습니다. 이처럼 이름이 같은 변수를 서로 구분해야할 때 this 포인터를 사용합니다. 


constructor() 함수의 첫 번째 매개변수로 받은 name 변수의 문자열을 클래스의 멤버 변수인 name에 복사합니다. 



속성과 기능에 따른 접근 지정

오류 메시지 그룹이 사라진 이유에 대해서 알게 되었습니다. 오류의 주된 내용은 private 멤버에 접근할 수 없다는 내용이므로 앞서 설명한 접근 지정자를 제대로 이해한 독자라면 두 번째 오류 메시지 그룹을 없앨 수 있을 것입니다. 멤버 함수를 선언 및 정의할 때 public 접근 지정자를 사용하면 됩니다. 


#include <iostream>
using namespace std;
 
#define NAME_LEN 20
#define SEX_LEN 10
#define JOB_LEN 20
#define CHARACTER_LEN 20
 
class Chulsoo
{
private:
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
 
public:
    void introduce()
    {
        cout << "이름: " << name << endl;
        cout << "성별: " << sex << endl;
        cout << "직업: " << job << endl;
        cout << "성격: " << character << endl;
        cout << "나이: " << age << endl;
        cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
    }
 
    void eat(char * food)
    {
        cout << "철수는 " << food << "를 먹는다" << endl;
    }
 
    void sleep()
    {
        cout << "철수는 잔다" << endl;
    }
 
    void drive(char * destination)
    {
        cout << "철수는 " << destination << "으로 운전한다" << endl;
    }
 
    void write()
    {
        cout << "철수는 책을 쓴다" << endl;
    }
 
    void read()
    {
        cout << "철수는 책을 읽는다" << endl;
    }
 
    void constructor(char * name, char * sex, char * job, char * character, int age, bool marriageStatus)
    {
        strcpy_s(this->name, name);
        strcpy_s(this->sex, sex);
        strcpy_s(this->job, job);
        strcpy_s(this->character, character);
        this->age = age;
        this->marriageStatus = marriageStatus;
    }
};
 
class Younghee
{
private:
    char name[NAME_LEN];
    char sex[SEX_LEN];
    char job[JOB_LEN];
    char character[CHARACTER_LEN];
    int age;
    bool marriageStatus;
 
public:
    void introduce()
    {
        cout << "이름: " << name << endl;
        cout << "성별: " << sex << endl;
        cout << "직업: " << job << endl;
        cout << "성격: " << character << endl;
        cout << "나이: " << age << endl;
        cout << "결혼여부: " << (marriageStatus ? "YES" : "NO"<< endl;
    }
 
    void eat(char * food)
    {
        cout << "영희는 " << food << "를 먹는다" << endl;
    }
 
    void sleep()
    {
        cout << "영희는 잔다" << endl;
    }
 
    void shopping()
    {
        cout << "영희는 쇼핑을 한다" << endl;
    }
 
    void constructor(char * name, char * sex, char * job, char * character, int age, bool marriageStatus)
    {
        strcpy_s(this->name, name);
        strcpy_s(this->sex, sex);
        strcpy_s(this->job, job);
        strcpy_s(this->character, character);
        this->age = age;
        this->marriageStatus = marriageStatus;
    }
};
 
int main(void)
{
    Chulsoo chulsoo;
    Younghee younghee;
    chulsoo.constructor("철수""남성""작가""diligent"32true);
    younghee.constructor("영희""여성""주부""impatient"32true);
    chulsoo.drive("레스토랑");
    chulsoo.eat("스테이크");
    younghee.eat("스테이크");
    chulsoo.drive("집");
    younghee.sleep();
    chulsoo.write();
    chulsoo.read();
    chulsoo.sleep();
    cout << endl;
    chulsoo.introduce();
    cout << endl;
    younghee.introduce();
    return 0;
}
cs


클래스의 데이터 은닉을 위해서 클래스 멤버 변수 맨 위에 private 키워드를 추가했습니다. 접근 지정자는 입력한 위치 아래쪽에 연속으로 적용됩니다. 따라서 이제 각 클래스의 모든 멤버 변수는 해당 클래스 내부에서만 접근할 수 있고 외부에서는 접근할 수 없습니다. 마찬가지로 클래스의 멤버 함수들은 public 접근 지정자를 적용했습니다. 따라서 각 클래스의 모든 멤버 함수는 외부에서 접근할 수 있습니다. 


추상화의 핵심은 실생활에서 객체의 속성과 기능을 표현하는 개념입니다. 여기서 속성은 private 접근 지정자를 필두로 하는 멤버 변수들이고, 기능은 public 접근 지정자를 필두로 하는 멤버 함수들입니다. 


C++ 클래스를 통해서 철수와 영희의 실생활를 추상화 할 수 있습니다. 컴파일러가 int형을 제공하여 다양한 수학적 계산을 수행할 수 있듯이, chulsoo와 younghee 라는 클래스 자료형을 사용자가 정의하여 만들었기 때문에 철수와 영희의 실생활을 표현할 수 있는 것입니다. C++클래스는 실생활을 표현하기 위한 사용자 정의 자료형입니다. 



객체와 객체지향 프로그래밍

클래스는 실생활을 표현하기 위한 사용자 정의 자료형이고, 클래스 변수는 객체라고 합니다. 붕어빵을 만드는 틀은 클래스이고, 붕어빵 틀로 만든 붕어빵은 실제로 존재하는 클래스 변수, 즉 객체입니다.


즉, 클래스 변수를 선언하는 것은 객체를 생성하는 것이라 말할 수 있습니다. 각각의 객체는 클래스의 실체입니다.


객체에 대해서 자세하게 설명하기 위해서 객체지향 프로그래밍이 무엇인지 알아야 합니다. 객체 간의 기능(함수)을 통해서 실생활을 프로그래밍하는 것을 객체 지향 프로그래밍이라고 하며, C++와 같은 객체 중심의 프로그래밍 언어를 객체지향 프로그래밍 언어라고 합니다.

반응형