Skip to main content

[C++ Primer Plus] Chapter 10. 객체와 클래스

C++ 기초 플러스 책을 읽고 공부한 노트입니다.




클래스 #

  • 추상화란 어떤 정보를 사용자 인터페이스로 표현하는 것이다. 따라서 클래스는 추상화 인터페이스를 구현하는 사용자 정의 데이터형이라고 볼 수 있다. public으로 정의된 인터페이스를 통해서만 접근이 가능하다.
  • 클래스는 데이터와 메서드를 하나로 결합한다.
// cpp 파일에서 클래스의 정의를 제공한다. 
// = 캡슐화(세부적인 구현들을 추상화와 분리한다)

class Stock // 사용자 정의 데이터형인 class. 
{
private: // 정보 은닉. 데이터 무결성을 보호한다. = 캡슐화
    char company[30];
    int shares;
    double shareVal;
    double totalVal;
    void SetTotal() {  totalVal = shares * shareVal;  }
  
public: // 설계를 추상화하여 인터페이스로 제공한다
    void Acquire (const char * co, int n, double pr);
    void Buy (int num, double price);
    void Sell (int num, double price);
    void Update (double price);
    void Show ();
};

  • 구조체와 클래스의 차이점
    • 구조체는 디폴트 접근 제어가 public이지만, 클래스는 private이다.

  • 인라인 메서드
    • 클래스 선언 안에 정의를 가지는 모든 함수는 자동으로 인라인 함수가 된다.
    • 원한다면, 선언 외부에 함수를 정의하고 inline 키워드를 넣어서 인라인 함수로 만들 수 있다.
class Stock
{
private:
    void SetTotal();
    //…
};

inline void Stock::SetTotal()
{
    //…
}

  • 객체는 각기 자체의 데이터를 갖지만 동일한 클래스 메서드 집합을 공유한다.
Stock s1; // 각각의 데이터 공간을 가진다. 
Stock s2;

s2.Show(); // 하나의 멤버 함수를 공유한다. 
s2.Show();



클래스의 생성자 #

  • 클래스의 데이터들은 private 접근제어를 가지고 있기 때문에 프로그램이 직접 데이터 멤버에 접근할 수 없다. 따라서 struct와 같은 방법으로 초기화할 수가 없다.
struct Student 
{
    char* name;
    int age;
};

Student s = { "John", 12 };
Stock ss = { "Sukie", 200, 50.25 }  // (X) 컴파일 에러. ss객체의 private 데이터에 접근 불가. 

  • 그렇기 때문에 C++은 객체를 생성할 때 자동으로 초기화하는 특별한 멤버함수인 ‘클래스 생성자’를 제공한다. 프로그램이 객체를 선언할 때 자동으로 생성자가 호출된다.
  • 생성자는 리턴값을 가질 수 없으며, 명시적, 암시적으로 호출할 수 있다.
class Stock
{
public:
    Stock(const string& co, int n, double pr);
    //…
};

Stock s1 = Stock("Sukie", 200, 50.25); // 명시적 호출
Stock s2 ("Sukie", 200, 50.25); // 암시적 호출
Stock* s3 = new Stock("Sukie", 200, 50.25); // 동적 객체

// C++11에서 허용 (초기화 리스트를 사용)
Stock s4 = Stock{"Sukie", 200, 50.25}; 
Stock s5 {"Sukie", 200, 50.25};
Stock* s6 = new Stock{"Sukie", 200, 50.25};

  • 디폴트 생성자는 명시적인 초기화값을 제공하지 않을 때 사용하는 생성자이다. C++는 사용자가 생성자를 제공하지 않을 때 자동으로 디폴트 생성자를 제공한다.
  • 만약, 다른 생성자를 사용자가 만들었다면 디폴트 생성자를 제공하지 않는다.
Stock s1 = Stock(); // 디폴트 생성자를 사용한다. 만약 다른 생성자가 있다면 에러가 발생한다. 
Stock s2; 

  • 디폴트 생성자 정의 방법
    1. 생성자의 모든 매개변수에 디폴트 값을 제공한다.
    2. 함수 오버로딩을 사용하여 매개변수가 없는 또 하나의 생성자를 정의한다.
    • 그러나 두 가지 방법을 동시에 사용하는 것은 안 된다.
class Stock
{
public:
    Stock(const string& co = "Error", int n = 0, double pr = 0.0);  // 방법1
    Stock();  // 방법2
    //...
};



클래스의 파괴자 #

  • 객체의 수명이 끝나는 시점에서 프로그램은 파괴자를 자동으로 호출한다. 파괴자는 클래스 이름 앞에 틸데(~)가 붙으며 매개변수와 리턴값을 가질 수 없다.
  • 사용자가 명시적으로 파괴자를 호출하면 안 된다. 파괴자는 컴파일러에 의해 자동으로 불려지는 것이다. 예를 들어, 정적 기억 공간에 클래스 객체를 생성한다면, 프로그램이 종료될 때 자동으로 파괴자가 호출된다.
  • 파괴자는 반드시 있어야 하며, 사용자가 파괴자를 제공하지 않으면 컴파일러가 디폴트 파괴자를 선언한다.
  • 이미 존재하는 객체에 생성자를 사용하면, 생성자가 새로운 임시객체를 생성하고, 새로 생성된 임시 객체의 내용이 복사된다. 그리고 임시객체의 파괴자가 호출된다.
Stock s = Stock("Sukie", 200, 50.25); 
s = Stock("Nifty", 100, 20.5); // 임시 객체를 생성하고, s 객체에 복사한 후, 임시 객체를 파괴한다. 



const 멤버 함수 #

  • 어떤 멤버함수가 호출 객체를 변경하지 않는다고 약속하기 위해 const를 함수 괄호 뒤에 넣는다.
class Stock
{
public:
    void Show() const;
    //...
};



this 포인터 #

  • this 포인터는 멤버 함수를 호출하는 데 사용된 객체를 지시한다. 일반적으로, 모든 클래스의 멤버 함수들은 this를 가지며, this는 그 해당 객체의 주소로 설정된다. 따라서 *this는 해당 객체 자체를 의미한다.

class Stock
{
public:
    //…
	
    // 매개변수로 전달된 Stock 객체와 비교해서 totalVal이 높은 객체를 리턴한다
    const Stock& TopValue(const Stock& s) const
    {
        if (s.totalVal > totalVal)  // totalVal은 this->totalVal의 약식 표기이다. 
            return s;  
        else                        
            return *this  // 이 멤버함수를 호출한 객체 s1을 리턴한다
    }
};

Stock s1 = Stock("Sukie", 200, 50.25); 
Stock s2 = Stock("Nifty", 100, 20.5); 
s1.TopValue(s2); // this 포인터를 s1 객체의 주소로 설정하고, TopValue에서 그 포인터를 사용할 수 있게 한다. 



객체 배열 #

  • 표준 데이터형의 배열을 선언하는 것과 동일한 방법으로 선언한다.
Stock myStocks1 [4]; // 배열 각 원소 마다 디폴트 생성자가 호출된다. 
Stock myStocks2 [4] = {
    Stock("NanoSmart", 12, 20), 
    Stock("Boffo", 200, 2.0),
    Stock("Monolithic", 130, 3.27),
    Stock("Fleep", 60, 6.5)
};



클래스 사용 범위 #

  • C++ 클래스는 전역 사용 범위와 지역 사용 범위와 다르게 클래스 사용 범위라는 새로운 종류의 사용 범위를 가지고 있다. 클래스 사용 범위를 갖는 것들은 클래스 안에는 알려지지만 클래스 바깥에는 알려지지 않는다.

  • 그렇다면 클래스 사용 범위를 가지는 기호 상수는 어떻게 만들 수 있을까?

class Stock
{
private:
    const int Months = 12;
    double costs[Months];   // (X)
    //…
};
  • 클래스를 선언하는 것은 객체가 어떻게 생겼는지 서술하는 것이지, 그 객체를 생성하는 것은 아니다. 따라서 값을 저장할 기억 공간은 객체가 생성될 때까지 마련되지 않는다. 그렇기 때문에 위의 예제는 실패한다.

  • 이러한 문제의 해결 방안은 두 가지가 있다.
  1. 클래스 안에 열거체를 선언한다. 단, 각 객체는 그 안에 열거체를 담지 않는다. 컴파일 할 때 클래스 사용 범위에 있는 코드에서 기호이름 Months가 발견되면, 컴파일러는 그것을 단순히 12로 대체할 뿐이다.
  2. static 키워드를 사용하여 정적 클래스 멤버를 선언한다. 그래서 해당 클래스의 모든 객체들이 하나의 상수를 공유한다.
class Stock
{
private:
    enum { Months = 12 }; // 방법 1
    static const int Months = 12; // 방법 2
	
    double costs[Months];  // (O)
    //…
};



범위가 정해진 열거 (C++11) #

  • 전통적인 열거 방식은 다음과 같은 경우 충돌한다는 문제점을 안고 있다.
enum Egg { Small, Medium, Large };
enum Shirts { Small, Medium, Large };

  • 따라서 class 키워드를 사용해서 열거자에게 클래스 사용 범위를 갖게 함으로써 해결할 수 있다. 이 경우 열거자를 사용하려면 이름을 사용해야 한다. 그러나 이 경우 int형으로의 암시적 변환이 이루어지지 않는다.
enum class Egg { Small, Medium, Large }; // class 키워드 사용
enum class Shirts { Small, Medium, Large };

Egg e = Egg::Large;    // 사용하려면 열거자 이름을 사용해야 함
Shirts s = Shirts::Large;

int number1 = e;       // (X) int형으로의 암시적 변환 불가능
int number2 = int(e);  // (O) 명시적 변환은 가능

  • 범위가 정해진 열거의 내재적 형태는 int형이다. 이것을 선택적으로 바꿀 수도 있다.
enum class : short Pizza { Small, Medium, Large }; // : short 는 기본형이 short형임을 의미한다.