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++에서 클래스는 다음과 같이 정의한다.
- 다음은
Book
이라는 이름의 클래스를 정의하는 그림이다.
- 접근 제어 지시자는 OOP의 특징 중 하나인 정보 은닉을 위한 키워드이다.
public
영역은 모든 객체에서 접근할 수 있지만,private
영역은 해당 객체 내의 멤버 변수나 멤버 함수만이 접근할 수 있다.
- 이렇게 선언된 클래스의 정의를 가지고 다음과 같이
Book
객체를 선언할 수 있다.
1) 멤버 함수의 정의
- 클래스에서 멤버 함수를 정의하는 방법은 일반 함수의 정의와 크게 다르지 않다.
- C++에서는 멤버 함수를 클래스의 선언 안이나 밖에서 모두 정의할 수 있도록 허용한다.
- 클래스의 선언 밖에서 멤버 함수를 정의할 때에는
::
(범위 지정 연산자)를 사용하여 해당 함수가 어느 클래스에 속하는지를 명시해야 한다.
- 다음의 코드는 클래스 선언 밖에서
Book
클래스의Move()
멤버 함수를 정의하는 것이다.
- 만약 멤버 함수가 클래스의 선언 안에서 정의되면, 이 함수는 인라인 함수로 처리되어 위와 같이
::
를 사용하여 소속 클래스를 명시할 필요가 없게 된다. - 또한, 클래스 선언 밖에서 정의된 멤버 함수도
inline
키워드를 사용하여 인라인 함수로 처리할 수 있다. - 이처럼 하나의 클래스에서 생성된 인스턴스는 각각 독립된 메모리 공간에 저장된 자신만의 멤버 변수를 가지지만, 멤버 함수는 모든 인스턴스가 공유하게 된다.
- 참고로 멤버 함수는 클래스의 선언 안이나 밖에서 모두 정의할 수 있지만, 클래스가 일단 선언된 후에 멤버 함수를 추가할 수는 없다.
2) 멤버 함수의 호출
- C++에서 위와 같이 정의한 멤버 함수를 호출하는 방법은 구조체의 경우와 같다.
- 멤버 함수는
.
(멤버 참조 연산자)를 사용하여 호출할 수 있다.
- 멤버 함수를 호출하는 방법은 다음과 같다.
4. 접근 제어
1) 정보 은닉
- C++에서 구조체의 모든 멤버는 외부에서 언제나 접근할 수 있다.
- 하지만 클래스는 OOP의 기본 규칙 중 하나인 정보 은닉에 대해서도 생각해야만 한다.
- 정보 은닉이란 사용자가 굳이 알 필요가 없는 정보는 사용자로부터 숨겨야 한다는 개념이다.
- 그렇게 함으로써 사용자는 언제나 최소한의 정보만으로 프로그램을 손쉽게 사용할 수 있게 된다.
2) 접근 제어
- C++에서는 이러한 정보 은닉을 위해 접근 제어라는 기능을 제공하고 있다.
- 접근 제어란 접근 제어 지시자를 사용해 클래스 외부에서의 직접적인 접근을 허용하지 않는 멤버를 설정할 수 있도록 하여, 정보 은닉을 구체화하는 것을 의미한다.
- C++에서는 다음과 같은 세 가지의 접근 제어 지시자를 제공한다.
1] public
2] private
3] protected
- 클래스의 기본 접근 제어 권한은
private
이며, 구조체 및 공용체는public
이다.
(1) public
접근 제어 지시자
public
접근 제어 지시자를 사용하여 선언된 클래스 멤버는 외부로 공개되며, 해당 객체를 사용하는 프로그램 어디에서나 직접 접근할 수 있다.- 따라서
public
멤버 함수는 해당 객체의private
멤버와 프로그램 사이의 인터페이스(Interface) 역할을 하게 된다. - 프로그램은 이러한
public
멤버 함수를 통해 해당 객체의private
멤버에도 접근할 수 있다.
- 다음 그림은 클래스의
public
멤버에 접근할 수 있는 영역을 나타낸다.
- 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
멤버에 접근할 수 있는 영역을 나타낸다.
- 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
멤버에 접근할 수 있는 영역을 나타낸다.
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()
멤버 함수는 다음과 같이 호출될 것이다.
- 이때
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)이며, 따라서 값을 재할당할 수 없다.