Home [C++ 프로그래밍] 7강 - 클래스와 객체
Post
Cancel

[C++ 프로그래밍] 7강 - 클래스와 객체

💡해당 게시글은 방송통신대학교 이병래 교수님의 'C++ 프로그래밍' 강의를 개인 공부 목적으로 메모하였습니다.



학습 개요


  • 클래스 선언, 객체의 정의 및 활용 방법을 프로그램 예를 통해 복습
  • 스택을 표현하는 클래스를 활용하는 예를 학습함
  • 스택은 Last-In-First-Out 특성을 갖는 자료 구조임
  • 복소수를 표현하는 클래스를 활용하는 예를 학습함
  • 클래스를 선언하는 형식, 생성자의 활용, 여러 가지 멤버의 선언 방법 등을 다시 한번 익혀 봄
  • C++11 이후 사용할 수 있는 생성자의 활용법을 심화 학습함



학습 목표


  • 스택을 클래스로 선언하여 활용할 수 있음
  • 복소수를 표현하는 클래스를 선언하여 활용할 수 있음
  • 소속 클래스의 다른 생성자를 이용하여 다른 생성자를 정의할 수 있음
  • 초기화 리스트 생성자를 정의할 수 있음



주요 용어


  • 스택(stack)
    • pushpop연산을 이용하여 데이터를 저장하고 인출할 수 있는 자료 구조로서, 나중에 저장된 데이터가 먼저 인출될 수 있는 특성(LIFO: Last In, First Out)을 갖음
  • 위임 생성자(delegating constructor)
    • 클래스의 다른 생성자를 이용하여 선언되는 생성자로서, 생성자를 작성하는 코드의 중복을 줄일 수 있음
  • 초기화 리스트 생성자(initializer-list constructor)
    • 첫 번째 매개 변수가 std::initializer_list<Type>인 생성자



강의록


스택 클래스 - CharStack

스택의 개념

  • 스택(stack)
    • 데이터를 저장하는 자료 구조의 하나
    • 스택의 기본 연산
      • push
        • 데이터를 저장하는 연산
      • pop
        • 마지막으로 저장한 데이터를 인출하는 연산
    • LIFO(Last In, First Out)
  • 스택의 동작
    • push('a'), push('b'), push('c')에 의해 스택에 데이터가 추가 됨

      image.png

      image.png

      image.png

    • pop()에 의해 스택에 데이터 인출 됨

      image.png

    • c → b → a 순서로 인출 됨

  • 스택 구현을 위한 구조 설계

    image.png

예제 - CharStack 클래스

  • CharStack 클래스
    • 문자를 최대 20개까지 저장할 수 있는 스택 객체를 만들 수 있는 CharStack 클래스를 선언하라.
    • CharStack 객체는 문자 데이터를 저장(push)하거나 인출(pop)할 수 있으며, 스택이 비어있는지, 가득 차 있는 지를 검사할 수 있다.

    image.png

    • 행위

      멤버함수비고
      CharStack()생성자
      bool chkEmpty()스택이 비어 있는지 검사함
      bool chkFull()스택이 가득 차 있는지 검사함
      bool push(char)스택에 데이터를 저장함
      char pop()스택에서 데이터를 꺼냄
    • 속성

      데이터 멤버비고
      int top가장 위에 있는 데이터 위치를 가리킴
      char buf[20]데이터 저장 공간

예제: CharStack 클래스 - CharStack.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class CharStack {
  enum { size = 20 }; // 스택의 크기
  int top; // 마지막 데이터를 가리키는 포인터
  char buf[size]; // 스택의 저장 공간
public:
  CharStack() : top{ size } {} // 생성자
  
  bool chkEmpty() const { // 스택에 데이터가 없으면 true
    return top == size;
  }
  
  bool chkFull() const { // 스택이 가득 차 있으면 true
    return !top;
  }
  
  bool push(char ch); // 스택에 데이터를 넣음
  
  char pop(); // 스택에서 데이터를 꺼냄
};

예제: CharStack 클래스 - CharStack.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include "CharStack.h"
using namespace std;

bool CharStack::push(char ch)
{
  if (chkFull()) {
    cout << "스택이 가득 차 있습니다." << endl;
    return false;
  }
  
  buf[--top] = ch; // 스택에 공간이 있으면 저장
  return true;
}

char CharStack::pop()
{
  if (chkEmpty()) {
    cout << "스택에 데이터가 없습니다." << endl;
    return 0;
  }
  return buf[top++]; // top 위치의 데이터 반환
}
  • push('b')

    image.png

  • pop()→ b 반환

    image.png

예제: CharStack 클래스 - CSMain.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include "CharStack.h"
using namespace std;

int main()
{
  CharStack chStack; // 20개의 문자를 저장할 수 있는 스택
  char str[20];

  cout << "영어 단어를 입력하시오 : ";
  cin >> str;

  char* pt = str; // 포인터로 문자열 시작 위치를 가리킴
  while (*pt)     // 문자열의 끝이 아니면 반복
  {
    chStack.push(*(pt++)); // 스택에 문자를 넣음
  }

  cout << "역순 단어 출력 : ";
  while (!chStack.chkEmpty()) // 스택이 비어 있지 않으면 반복
  {
    cout << chStack.pop(); // 스택에서 인출한 문자를 출력
  }
  cout << endl;
  return 0;
}

image.png

복소수 클래스 - Complex1

복소수

  • 복소수(Complex Number)
    • 실수 부(real part)와 허수 부(imaginary part)로 구성될 수 있는 수
    • 복소수의 표현
      • a + jb
      • a
        • 실수 부의 값
      • b
        • 허수 부의 값
      • j
        • 허수 단위로서, j^2 = -1임
    • 켤레 복소수 (complex conjugate)
      • 허수 부의 부호가 반대인 복소수
      • a + jb의 켤레 복소수는 a - jb임

    image.png

  • 복소수 연산
    • 덧셈(뺄셈)
      • 실수 부의 합(차)과 허수 부의 합(차)을 각각 구함
      • (a + jb) + (d + je) = (a + d) + j(b + e)
      • (a + jb) - (d + je) = (a - d) + j(b - e)
    • 곱셈
      • (a + jb)(d + je) = (ad - be) + j(ae + bd)
    • 나눗셈
      • a + jb / d + je = ad + be / d^2 + e^2 + j (bd - ae) / (d^2 + e^2)

예제: Complex1 클래스

  • Complex1 클래스
    • 복소수를 표현하는 클래스를 선언하라.
    • 복소수의 사칙 연산 및 켤레 복소수를 구하는 멤버 함수를 포함하며, 실수 부의 값이 a, 허수 부의 값이 b일 때 (a + jb) 형태로 출력할 수 있도록 한다.
    • 행위

      멤버 함수비고
      Complex1(double r, double i)생성자
      Complex1 conj()켤레 복소수 반환
      Complex1 add(const Complex1& c)덧셈
      Complex1 sub(const Complex1& c)뺄셈
      Complex1 mul(const Complex1& c)곱셈
      Complex1 div(const Complex1& c)나눗셈
      void display()복소수의 값 출력
    • 속성

      데이터 멤버비고
      double rPart실수 부의 값
      double iPart허수 부의 값

예제: Complex1 클래스 - Complex1.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Complex1 {
    double rPart, iPart; // 실수 부 및 허수 부
    
public:
    // 생성자
    Complex1(double r=0, double i=0) : rPart(r), iPart(i) {}
    Complex1 conj() const {
        return Complex1(rPart, -iPart); // Complex1 클래스의 임시 객체를 생성 -> 문장 실행 후 소멸
    }

    Complex1 add(const Complex1& c) const {
        return Complex1(rPart + c.rPart, iPart + c.iPart);  // Complex1 클래스의 임시 객체를 생성 -> 문장 실행 후 소멸
    }

    Complex1 sub(const Complex1& c) const {
        return Complex1(rPart - c.rPart, iPart - c.iPart);  // Complex1 클래스의 임시 객체를 생성 -> 문장 실행 후 소멸
    }
    
    Complex1 mul(const Complex1& c) const;
    Complex1 div(const Complex1& c) const;
    void display() const; // 복소수 값을 출력
};

예제: Complex1 클래스 - Complex1.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include "Complex1.h"
using namespace std;

Complex1 Complex1::mul(const Complex1& c) const
{
    double r = rPart * c.rPart - iPart * c.iPart;
    double i = rPart * c.iPart + iPart * c.rPart;
    return Complex1(r, i);
}

Complex1 Complex1::div(const Complex1& c) const
{
    double d = c.rPart * c.rPart + c.iPart * c.iPart;
    Complex1 c1 = mul(c.conj());
    return Complex1(c1.rPart/d, c1.iPart/d);
}

void Complex1::display() const
{
    cout << "(" << rPart;
    if (iPart > 0)
        cout << "+" << iPart << "j";
    else if (iPart < 0)
        cout << "-" << -iPart << "j";
    cout << ")";
}

복소수 클래스 - C1Main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include "Complex1.h"
using namespace std;

int main()
{
    Complex1 c1(1, 2);
    Complex1 c2(2, 3);
    Complex1 c3 = c1.add(c2);
    c1.display();
    cout << " + ";
    c2.display();
    cout << " = ";
    c3.display();
    cout << endl;

    c3 = c1.mul(10.0); // 묵시적 형 변환 -> c3 = c1.mul(Complex1(10.0, 0.0));
    c1.display();
    cout << " * 10 = ";
    c3.display();
    cout << endl;

    return 0;
}

// (1+2j) + (2+j3) = (3+j5)
// (1+j2) * 10 = (10+j20)

image.png

심화 학습

생성자 처리의 위임 (C++11 이후)

  • 위임 생성자 (delegating constructor)의 선언
    • 멤버 초기화 리스트에 클래스의 다른 생성자를 사용하여 새로운 생성자를 선언할 수 있음
      • 위임 생성자
        • 클래스의 다른 생성자를 이용하여 선언되는 생성자
      • 타겟 생성자
        • 위임의 대상이 되는 생성자
    • 생성자를 작성하는 코드의 중복을 줄일 수 있음
  • VecF 클래스

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      class VecF {
        int n;
        float *arr;
      public:
        VecF(int d, const float* a=nullptr) : n{ d } {
          arr = new float[d];
          if (a) memcpy(arr, a, sizeof(float) * n);
        }
        VecF(const VecF& fv) : n{ fv.n } {
          arr = new float[n];
          memcpy(arr, fv.arr, sizeof(float)*n);
        }
      };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      class VecF {
        int n;
        float *arr;
      public:
        VecF(int d, const float* a=nullptr) : n{ d } { // 타겟 생성자
          arr = new float[n];
          if (a) memcpy(arr, a, sizeof(float) * n);
        }
          
        VecF(const VecF& fv) : VecF(fv.n, fv.arr) {} // 위임 생성자
      };
    

초기화 리스트 생성자

  • 초기화 리스트 생성자(initializer-list constructor)
    • 첫 번째 매개 변수가 std::initializer_list<Type>인 생성자

      1
      
        std::initializer_list<Type>
      
      • 지정된 자료형의 값들을 { }안에 나열한 리스트
      • 헤더 파일
        • #include <initializer_list>
      멤버 함수용도
      begin()첫 번째 요소에 대한 포인터를 반환함
      end()마지막 요소의 다음 위치에 대한 포인터를 반환함
      size()initializer_list의 원소 수를 반환함
      1
      
        initializer_list<int> ilst{ 1, 2, 3 };
      
  • 초기화 리스트 생성자의 활용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
      class VecF {
        int n;
        float *arr;
      public:
        VecF(int d, const float* a=nullptr) : n{ d } {
          arr = new float[d];
          if (a) memcpy(arr, a, sizeof(float) * n);
        }
          
        VecF(initializer_list<float> lst)
          : n{ static_cast<int>(lst.size()) } {
          arr = new float[n];
          copy(lst.begin(), lst.end(), arr);
        }
        
      };
        
      int main()
      {
        float a[4] = {1.0f, 2.0f, 3.0f, 4.0f};
        VecF v1(4, a);
        VecF v2{2.0f, 4.0f, 6.0f, 8.0f}; // 초기화 리스트 생성자 호출
      }
    



연습 문제


  1. 다음 중 스택에서 데이터를 인출하는 순서에 대한 올바른 설명은?

    a. Last In, First Out

    • 스택은 나중에 저장된 데이터가 먼저 인출되는 구조를 가지고 있음
    • First In, First Out과 같이 저장된 순서에 따라 인출하는 자료구조는 큐(queue)임
  2. 생성자를 직접 호출하는 형식의 문장 사용에 대한 올바른 설명은?

    a. 이름이 없는 임시 객체가 생성된다.

    • 생성자를 직접 호출하는 명령은 임시 객체를 만들며, 그 문장을 수행한 후 제거 됨
  3. 위 지문과 같이 선언된 ClassA의 객체 obj1과 obj2에 대한 다음 문장에 대한 올바른 설명은?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
     obj1 = obj2.add(10);
        
     class ClassA {
       int x, y;
     public:
       ClassA(int a=0, int b=0) : x(a), y(b) {}
       ClassA add(const ClassA& objA) {
         x += objA.x;
         y += objA.y;
         return *this;
       }
     };
    

    a. 10은 생성자(a는 10, b는 디폴트 값인 0이 사용됨)를 이용하여 ClassA의 객체로 자동 형변환이 되어 add()의 형식 매개변수로 전달된다.

    • int 값을 인수로 전달 받는 add()는 선언되지 않았지만, 생성자를 이용하여 int 값이 ClassA의 객체로 자동 형 변환이 이루어질 수 있으므로 명령은 올바로 실행 됨
    • 그 결과 obj2의 x에는 10이, y에는 0이 더해지며, 변화된 obj2가 obj1에 대입 됨



정리 하기


  • 스택은 pushpop연산을 이용하여 데이터를 저장하고 인출할 수 있는 자료 구조로서, 나중에 저장된 데이터가 먼저 인출될 수 있는 특성(LIFO: Last In, First Out)을 갖음
  • 배열과 top을 이용하여 스택을 구현할 수 있음
    • push는 top을 1 감소 시킨 후 배열의 top 위치에 값을 저장하고, pop은 top 위치의 값을 반환하며 top을 1 증가 시킴
  • 실수 부와 허수 부의 값을 저장하는 데이터 멤버와 사칙 연산 등 필요한 연산을 멤버 함수로 정의하여 복소수 클래스를 구현할 수 있음
  • 생성자를 직접 호출하는 형식의 연산은 해당 클래스의 임시 객체를 만듬
  • 어떤 클래스의 객체가 필요한 위치에 그 클래스의 생성자의 형식 매개변수에 해당되는 값을 사용하면 묵시적 형 변환을 통해 그 클래스의 객체로 변환
  • 위임 생성자는 클래스에 선언된 다른 생성자를 이용하여 선언되는 생성자로서, 코드 중복을 줄일 수 있음
  • std::initialize_list는 지정된 자료형의 값을 임의 개수 나열한 리스트를 만드는 데 사용되며, 첫 번째 매개변수가 std::initializer_list<Type>인 생성자를 초기화 리스트 생성자(initializer-list constructor)라고 함

[C++ 프로그래밍] 6강 - 클래스와 객체

[C++ 프로그래밍] 8강 - 연산자 다중 정의