Skip to content

11. 클래스(Class)


1. 키워드

  • 클래스(Class)와 class 키워드
  • 객체 지향 프로그래밍(OOP: Object-Oriented Programming)
  • 멤버 변수(Member Variable)와 프로퍼티(Property)
  • 멤버 함수(Member Function)와 메서드(Method)
  • 인스턴스(Instance)
  • 접근 제어(Access Control)
  • public, private, protected 접근 제어 지시자
  • this 포인터


2. 클래스의 개념

  • C++에서 클래스란 구조체의 상위 호환으로 이해할 수 있다.
  • C++의 구조체는 멤버로 함수를 포함할 수 있기에, C의 구조체보다 좀 더 확장된 의미를 가진다.
  • C++에서 이러한 구조체와 클래스의 차이는 기본 접근 제어의 차이일 뿐, 나머지는 거의 같다.
  • C++에서는 이러한 클래스를 가지고 객체 지향 프로그램을 작성할 수 있다.
  • 클래스의 멤버 변수를 프로퍼티, 멤버 함수를 메서드라고도 한다.


1) OOP

  • OOP에서는 모든 데이터를 객체(Object)로 취급하며, 객체가 바로 프로그래밍의 중심이 된다.
  • 객체란 간단하게 실생활에서 인식할 수 있는 사물로 이해할 수 있다.
  • 이러한 객체의 상태(State)와 행동(Behavior)을 구체화하는 형태의 프로그래밍이 바로 OOP이다.
  • 또한, 이와 같은 객체를 만들어 내기 위한 틀과 같은 개념이 바로 클래스이다.


2) OOP의 특징

  • OOP가 가지는 특징은 다음과 같다.


1] 정보 은닉(Hiding)

2] 상속성(Inheritance)

3] 추상화(Abstraction)

4] 다형성(Polymorphism)

5] 캡슐화(Encapsulation)


OOP 특징 쉽게 외우기

  • "HI, APE"로 외우면 기억하기 좋다.


3) 객체의 예

  • 객체의 예는 다음과 같다.


1] 객체(Object)

  • 고양이

2] 멤버 변수 또는 프로퍼티

  • cat.name_ = "나비"
  • cat.family_ = "코리안 숏 헤어"
  • cat.age_ = 1
  • cat.weight_ = 0.1

3] 멤버 함수 또는 메서드

  • cat.Mew()
  • cat.Eat()
  • cat.Sleep()
  • cat.Play()


  • 고양이 객체는 모두 위와 같은 멤버 변수(프로퍼티)와 멤버 함수(메서드)를 가지지만, 각 멤버 변수의 값은 인스턴스마다 전부 다를 것이다.


4) 인스턴스

  • C++에서 클래스는 구조체와 마찬가지로 사용자가 정의할 수 있는 일종의 타입이다.
  • 따라서 클래스를 사용하기 위해서는 우선 해당 클래스 타입의 객체를 선언해야 한다.
  • 이렇게 선언된 해당 클래스 타입의 객체를 인스턴스라고 하며, 메모리에 대입된 객체를 의미한다.
  • 하나의 클래스에서 여러 개의 인스턴스를 생성할 수 있다.
  • 이러한 인스턴스는 독립된 메모리 공간에 저장된 자신만의 멤버 변수를 가지지만, 멤버 함수는 모든 인스턴스가 공유한다.


3. 클래스의 선언

  • 클래스란 OOP의 특징 중 하나인 추상화를 사용자 정의 타입으로 구현한 것이라 할 수 있다.
  • C++에서 이러한 클래스를 선언하는 방법은 구조체를 선언하는 방법과 거의 같다.
  • 구조체는 struct 키워드를 사용하지만, 클래스는 class 키워드와 접근 제어 지시자를 함께 사용한다.


  • C++에서 클래스는 다음과 같이 정의한다.


class 클래스이름
{
접근제어지시자1:
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
    멤버함수1의 원형
    멤버함수2의 원형
    ...
};


  • 다음은 Book이라는 이름의 클래스를 정의하는 그림이다.


001


  • 접근 제어 지시자는 OOP의 특징 중 하나인 정보 은닉을 위한 키워드이다.
  • public 영역은 모든 객체에서 접근할 수 있지만, private 영역은 해당 객체 내의 멤버 변수나 멤버 함수만이 접근할 수 있다.


  • 이렇게 선언된 클래스의 정의를 가지고 다음과 같이 Book 객체를 선언할 수 있다.


클래스이름 객체참조변수이름;
Book web_book;


1) 멤버 함수의 정의

  • 클래스에서 멤버 함수를 정의하는 방법은 일반 함수의 정의와 크게 다르지 않다.
  • C++에서는 멤버 함수를 클래스의 선언 안이나 밖에서 모두 정의할 수 있도록 허용한다.
  • 클래스의 선언 밖에서 멤버 함수를 정의할 때에는 ::(범위 지정 연산자)를 사용하여 해당 함수가 어느 클래스에 속하는지를 명시해야 한다.


반환타입 클래스이름::멤버함수이름(매개변수목록) { 함수의몸체; }


  • 다음의 코드는 클래스 선언 밖에서 Book 클래스의 Move() 멤버 함수를 정의하는 것이다.


void Book::Move(int page) { current_page_ = page; }


  • 만약 멤버 함수가 클래스의 선언 안에서 정의되면, 이 함수는 인라인 함수로 처리되어 위와 같이 ::를 사용하여 소속 클래스를 명시할 필요가 없게 된다.
  • 또한, 클래스 선언 밖에서 정의된 멤버 함수도 inline 키워드를 사용하여 인라인 함수로 처리할 수 있다.
  • 이처럼 하나의 클래스에서 생성된 인스턴스는 각각 독립된 메모리 공간에 저장된 자신만의 멤버 변수를 가지지만, 멤버 함수는 모든 인스턴스가 공유하게 된다.
  • 참고로 멤버 함수는 클래스의 선언 안이나 밖에서 모두 정의할 수 있지만, 클래스가 일단 선언된 후에 멤버 함수를 추가할 수는 없다.


2) 멤버 함수의 호출

  • C++에서 위와 같이 정의한 멤버 함수를 호출하는 방법은 구조체의 경우와 같다.
  • 멤버 함수는 .(멤버 참조 연산자)를 사용하여 호출할 수 있다.


  • 멤버 함수를 호출하는 방법은 다음과 같다.


1. 객체이름.멤버함수이름();            // 매개변수가 없는 멤버 함수의 호출
2. 객체이름.멤버함수이름(전달할인수목록);  // 매개변수가 있는 멤버 함수의 호출
web_book.Move(30);


4. 접근 제어

1) 정보 은닉

  • C++에서 구조체의 모든 멤버는 외부에서 언제나 접근할 수 있다.
  • 하지만 클래스는 OOP의 기본 규칙 중 하나인 정보 은닉에 대해서도 생각해야만 한다.
  • 정보 은닉이란 사용자가 굳이 알 필요가 없는 정보는 사용자로부터 숨겨야 한다는 개념이다.
  • 그렇게 함으로써 사용자는 언제나 최소한의 정보만으로 프로그램을 손쉽게 사용할 수 있게 된다.


2) 접근 제어

  • C++에서는 이러한 정보 은닉을 위해 접근 제어라는 기능을 제공하고 있다.
  • 접근 제어란 접근 제어 지시자를 사용해 클래스 외부에서의 직접적인 접근을 허용하지 않는 멤버를 설정할 수 있도록 하여, 정보 은닉을 구체화하는 것을 의미한다.
  • C++에서는 다음과 같은 세 가지의 접근 제어 지시자를 제공한다.


1] public

2] private

3] protected


  • 클래스의 기본 접근 제어 권한은 private이며, 구조체 및 공용체는 public이다.


(1) public 접근 제어 지시자

  • public 접근 제어 지시자를 사용하여 선언된 클래스 멤버는 외부로 공개되며, 해당 객체를 사용하는 프로그램 어디에서나 직접 접근할 수 있다.
  • 따라서 public 멤버 함수는 해당 객체의 private 멤버와 프로그램 사이의 인터페이스(Interface) 역할을 하게 된다.
  • 프로그램은 이러한 public 멤버 함수를 통해 해당 객체의 private 멤버에도 접근할 수 있다.


  • 다음 그림은 클래스의 public 멤버에 접근할 수 있는 영역을 나타낸다.


002


  • C++ 클래스 선언 시 public 접근 제어 지시자는 다음과 같이 지정할 수 있다.


class Book {
   public:
    string title_;    // 책의 제목
    int total_page_;  // 총 페이지
    double percent_;  // 해당 책을 읽은 정도

    void Move(int page);  // 현재 페이지를 전달받은 페이지로 이동시킴
    void Open();          // 현재 페이지로 책을 엶
    void Read();          // 현재 페이지에서 다음 페이지로 넘어감
};


(2) private 접근 제어 지시자

  • private 접근 제어 지시자를 사용하여 선언된 클래스 멤버는 외부에 공개되지 않으며, 외부에서 직접 접근할 수도 없다.
  • 프로그램은 private 멤버에 직접 접근할 수 없으며, 해당 객체의 public 멤버 함수를 통해서만 접근할 수 있다.
  • 클래스의 기본 접근 제어 권한은 private으로 설정되어 있으므로, 클래스 선언 시 private 접근 제어 지시자는 생략할 수 있다.
  • 일반적으로 private 멤버는 public 인터페이스를 직접 구상하지 않는 클래스의 세부적인 동작을 구현하는 데 사용된다.


  • 다음 그림은 클래스의 private 멤버에 접근할 수 있는 영역을 나타낸다.


003


  • C++ 클래스 선언 시 private 접근 제어 지시자는 다음과 같이 지정할 수 있다.


class Book {
   private:              // 생략 가능함
    int current_page_;   // 현재 페이지
    void set_percent();  // 해당 책을 읽은 정도를 구함

   public:
    string title_;
    int total_page_;
    double percent_;

    void Move(int page);
    void Open();
    void Read();
};


(3) protected 접근 제어 지시자

  • C++ 클래스는 private 멤버로 정보를 은닉하고, public 멤버로 사용자나 프로그램과의 인터페이스를 구축한다.
  • 여기에 파생 클래스(Derived Class)와 관련된 접근 제어 지시자가 하나 더 존재한다.
  • protected 멤버는 파생 클래스에 대해서는 public 멤버처럼 취급되며, 외부에서는 private 멤버처럼 취급된다.
  • protected 멤버에 접근할 수 있는 영역은 다음과 같다.


1] 이 멤버를 선언한 클래스의 멤버 함수

2] 이 멤버를 선언한 클래스의 프렌드

3] 이 멤버를 선언한 클래스에서 public 또는 protected 접근 제어로 파생된 클래스


  • 다음 그림은 클래스의 protected 멤버에 접근할 수 있는 영역을 나타낸다.


004


5. this 포인터

1) 멤버 함수의 공유

  • C++에서 하나의 클래스에서 생성된 인스턴스는 각각 독립된 메모리 공간에 저장된 자신만의 멤버 변수를 가지지만, 멤버 함수는 모든 인스턴스가 공유한다.
  • 따라서 Book 클래스를 이용해 두 개의 인스턴스를 생성해도, ThickerBook()이라는 멤버 함수는 하나만이 존재한다.
  • 즉, 더 두꺼운 책의 이름을 출력해 주는 ThickerBook() 멤버 함수는 두 인스턴스가 같이 공유하게 된다.


  • ThickerBook() 멤버 함수의 정의는 다음과 같다.


const Book &Book::ThickerBook(const Book &comp_book) {
    if (comp_book.total_page_ > total_page_) {
        return comp_book;
    } else {
        return ? ? ? ;  // 자신을 호출한 인스턴스를 반환하는데 알 수가 없음
    }
}


  • 그리고 ThickerBook() 멤버 함수는 다음과 같이 호출될 것이다.


web_book.ThickerBook(html_book);


  • 이때 ThickerBook() 멤버 함수는 자신을 호출한 객체가 web_book 객체임을 인수를 통해 전달 받아야만 한다.
  • 그래야만 else 문에서의 반환값을 정확히 명시할 수 있기 때문이다.


2) this 포인터

  • 위와 같은 이유로 C++에서는 모든 멤버 함수가 자신만의 this 포인터를 가지고 있다.
  • this 포인터는 해당 멤버 함수를 호출한 객체를 가리키게 되며, 호출된 멤버 함수의 숨은 인수로 전달된다.
  • 이렇게 하면 호출된 멤버 함수는 자신을 호출한 객체가 무엇인지 정확히 파악할 수 있다.


  • this 포인터를 사용하여 위의 ThickerBook() 멤버 함수를 다시 정의하면 다음과 같다.


const Book &Book::ThickerBook(const Book &comp_book) {
    if (comp_book.total_page_ > total_page_) {
        return comp_book;
    } else {
        return *this;  // 자신을 호출한 인스턴스를 반환함
    }
}


  • this는 포인터이므로, 반환할 때에는 *(참조 연산자)를 사용하여 호출한 객체 그 자체를 반환해야 한다.


#include <iostream>
#include <string>
using namespace std;

class Book {
   private:
    int current_page_;
    void set_percent();

   public:
    Book(const string &title, int total_page);
    string title_;
    int total_page_;
    double percent_;
    void Move(int page);
    void Open();
    void Read();
    const Book &ThickerBook(const Book &);
};

int main(void) {
    Book web_book("HTML과 CSS", 350);
    Book html_book("HTML 레퍼런스", 200);

    // cout << web_book.current_page_ << endl;  // 접근 불가. 컴파일 오류 발생

    cout << web_book.ThickerBook(html_book).title_ << endl;

    return 0;
}

Book::Book(const string &title, int total_page) {
    title_ = title;
    total_page_ = total_page;
    current_page_ = 0;
    set_percent();
}

void Book::set_percent() {
    percent_ = (double)current_page_ / total_page_ * 100;
}

//    ↓ 반환 타입(const Book &)           ↓ 매개변수(const Book &)
const Book &Book::ThickerBook(const Book &comp_book) {
//                 ↑ 멤버 함수(Book 클래스의 ThickerBook())
// const Book &을 전달 받아 const Book &을 반환하는 Book 클래스의 ThickerBook() 멤버 함수
    if (comp_book.total_page_ > this->total_page_) {
        return comp_book;
    } else {
        return *this;
    }
}

// HTML과 CSS


  • this 포인터는 암시적으로도 사용될 수 있지만, 가급적 ->를 통해 명시적으로 사용하는 것이 좋다.


3) this 포인터의 특징

  • C++에서 this 포인터는 다음과 같은 특징을 가진다.


1] this 포인터는 클래스, 구조체 또는 열거체 타입의 비정적 멤버 함수에서만 사용할 수 있다.

2] 정적 멤버 함수는 this 포인터를 가지지 않는다.

3] this 포인터는 언제나 포인터 상수(Constant Pointer)이며, 따라서 값을 재할당할 수 없다.


References