Post

C++: 클래스(class) & 객체 지향 프로그래밍(Object Oriented Programming)

C++: 클래스(class) & 객체 지향 프로그래밍(Object Oriented Programming)
  • 1-5 클래스(Class)

    • C++에서 도입된 객체 지향 프로그래밍의 객체(Object)를 구현하기 위한 구조로, 필요한 데이터와 기능을 서로 관련된 덩어리/집합인 객체로 묶고 세부적인 구현을 외부로부터 감추기 위해 사용.
      • 사용자가 알아야 할 내용이 감소함으로서 보다 쉽고 간편한 사용 유도.
      • 권한에 따른 제한적인 접근을 구현함으로서 의도되지 않은 동작 예방
      • C 언어에서의 구조체와 유사하지만, 멤버 함수 가능, 상속, 접근제어등의 기능에 있어서 차이가 있음. (C++의 구조체는 기능적으로 클래스와 거의 동일하며 상속, 접근제어등도 가능, 기본 접근 지정자만 public으로 차이있음.)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        
          //class 이름{}
          class A{
          public:
          int a;	//멤버 변수
          void printNum(){	//멤버 함수(struct에서 안되던 개념)
              cout << a << endl;
          }
          };
          int main(){
          //class를 자료형처럼 사용
          //클래스이름 오브젝트이
          A temp;
          temp.a = 10;	//A내부 변수 수정
          temp.printNum();	//10 출력
          }
        
    • 접근제어(Access Control)

      • 키워드를 통해 멤버 함수와 변수에 대한 접근 권한을 설정할 수 있음.
      • public: 외부에서도 자유롭게 접근 가능.
      • private: 외부에서 접근 불가능. 별도의 지정자가 없는 멤버들의 경우 기본적으로 private으로 인식.
      • protected: 클래스의 자식 클래스에서만 접근가능. (상속에 대해 알아야 이해 가능)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        
          class A{
          public:	//외부 접근 가능
          int a;
          void SayHello(){
          cout << "Hello" << endl;
          }
          private:	//외부 접근 불가
          int b;
          void SayBye(){
          cout << "Bye" << endl;
          }
          };
          int main(){
          A temp;
          //public 변수 접근
          temp.a = 10;	
          //private 변수 접근(inaccesible 오류 발생!)
          //temp.b = 100;
        			
          //public 함수 호출
          temp.SayHello();
          //private 함수 호출 (inaccesible 오류 발생!)
          //temp.SayBye();
          }
        
    • 접근자(Getter)/설정자(Setter)

      • 위 코드에서처럼 변수에 직접 접근하여 값을 획득하고 수정하는 행위는 예기치 않은 동작을 야기할 가능성 존재.
      • 맴버 변수들은 private로 설정하고 이에 값을 획득하는 함수인 접근자(Getter), 값을 설정하는 함수인 설정자(Setter)를 별도로 사용하여 안정적인 프로그램 구현 가능
        • Getter로 얻은 변수값을 수정해도 맴버 변수에 영향 없음. (참조로도 반환할 수 있지만 데이터 보호를 위해 필요시에만 사용.)
        • Setter로 맴버 변수가 올바른 값으로 수정되도록 설계 가능.
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          
            class A{
            public:
            int GetNum(){	//Num 값을 반환
                return Num;
            }
            void SetNum(int Val){	//Num 값을 설정
                if(0 < Val && Val < 100){
                    Num = Val;
                }
            }
            private:
            int Num;	//class 외부에서 직접 접근 불가능
            };
            int main(){
            A temp;
            temp.SetNum(10);
            cout << temp.GetNum() << endl; // 10출력
            temp.SetNum(1000);	//100초과, 수정 안됨.
            cout << temp.GetNum() << endl; // 10 출력.
            }
          
    • 생성자(Constructor)

      • 클래스가 생성되어 오브젝트화될 때 자동으로 호출되어 처음 수행되야될 동작들을 보장하는 맴버 함수. 클래스 이름과 동일한 함수 명으로 선언함.
      • 여러개의 매개변수에 따라 여러 종류의 생성자를 만들어 놓을 수 있음. 같은 이름의 함수를 매개변수의 종류에 따라 여러 형태로 구현해놓는 것을 오버로딩(Overloading)이라고 함.
        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
        
          class A{
          public:
              //클래스 이름(매개변수){ 생성자 내용;}
              A(){	//매개변수 없는 생성자
                  Num = -1;
              }
              A(int Val){	//정수를 받는 생성자
                  Num = Val;
              }
              A(float Val){	//실수를 받는 생성자, 정수로 형변환 후 저장
                  Num = (int)Val;
              }
              int GetNum(){
                  return Num;
              }
          private:
              int Num;
          };
          int main(){
              A a;
              cout << a.GetNum() << endl; // -1출력
              A b(3);
              cout << b.GetNum() << endl; // 3 출력
              A c(1000.315f);
              cout << c.GetNum() << endl; // 1000 출력
              //A d("qqq");	//오류! 해당되는 생성자 없음!
          }
        
    • 소멸자(Destructor)

      • 객체가 소멸될 때 호출되는 함수. 생성자의 반대 역할. 객체의 소멸을 알리거나 소멸시 수행되야할 동작들을 보장.
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        
          class A{
          public:
              A(){
                  cout << "Class Constructed!" << endl;
              }
              ~A(){
                  cout << "Class Destructed!" << endl;
              }
        
              int GetNum(){
                  return Num;
              }
          private:
              int Num;
          };
          int main(){
              A a;	//class Constructed! 출력
              //함수 종료, a 객체 메모리 해제되며 소멸자 호출
              //class Destructed! 출력
              return 0; 
          }
        
    • 헤더파일 활용

      • 위 예시들에선 맴버 함수들도 클래스 내부에서 구현했지만, 기본적으로는 선언과 구현은 분리해야 함. 선언부들은 헤더파일에 저장하며, 소스파일은 그 헤더파일을 불러와 구현하거나 사용하는 구조로 분리.
        • 선언부만 분리시킴으로서 여러 클래스와 파일들을 프로젝트에서 다룰 때 구현부의 중복 포함을 방지.
        • 선언부만 독립적으로 여러 파일에서 불러올수 있게 함으로서 재사용성 향상.
        • 선언부와 구현부의 독립성을 통한 모듈화 기여.
    • 간단한 성적 관리 프로그램.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
        //Student.h
        #ifndef STUDENT_H	//STUDENT가 정의되어있지 않으면 실행
        #define STUDENT_H	//STUDENT 정의
        class Student{
        public:
            //입력이 없으면 0으로 초기화
            Student(int History = 0, int Math = 0, int Science = 0){
                this->History = History;
                this->Math = Math;
                this->Science = Science;
            }
            //구현 없이 선언만 마무리
            float GetAverage();
            int GetHighest();
            int GetTotal();
        private:
            int History;
            int Math;
            int Science;
        };
        #endif //STUDENT_H ifdef 구문 종료
      		
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        //Student.cpp
        #include "Student.h"
        #include <algorithm>
        float Student::GetAverage(){
            return static_cast<float>(this->History + this->Math + this->Science)/3;
        }
        int Student::GetHighest(){
            return std::max(std::max(History, Math), Science);
        }
        int Student::GetTotal(){
            return this->History + this->Math + this->Science;
        }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        //main.cpp
        #include <iostream>	//표준 라이브러리등 시스템 경로에서 검색할 때 <> 사용.
        #include "Student.h"	//프로젝트 내의 파일을 검색할 때 ""사용.
        using namespace std;
      		
        int main(){
                Student Kim(30, 20, 10);
                cout << Kim.GetAverage() << endl; //20
                cout << Kim.GetHighest() << endl;	//30
                cout << Kim.GetTotal() << endl;	//60
                return;
        }
      
    • 배터리 사용 시뮬레이션
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      
        //Battery.h
        #ifndef BATTERY_H
        #define BATTERY_H
        class Battery{
        public:
            //100으로 초기화
            Battery(){
                Power = 100;
            }
            int GetRemainings();
            void Consume();
            void Charge();
        private:
            int Power;
        };
        #endif //BATTERY_H
      		
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
        //Battery.cpp
        #include "Battery.h"
        #include <algorithm>
        int Battery::GetRemainings(){
            return this->Power;
        }
        void Battery::Consume(){	//7만큼 감소, 0보다 내려갈 수 없음
            this->Power = std::max(0, this->Power - 7);
        }
        void Battery::Charge(){	//5만큼 증가, 100보다 올라갈 수 없음
            this->Power = std::min(100, this->Power + 5);
        }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
        //main.cpp
        #include <iostream>	
        #include "Battery.h"
        using namespace std;
      		
        int main(){
                Battery Samsung;
                cout << Samsung.GetRemainings()<< endl; //100
                Samsung.Charge();
                cout << Samsung.GetRemainings()<< endl; //100
                Samsung.Consume();
                cout << Samsung.GetRemainings()<< endl; //93
                return;
        }
      
    • 분수(Fraction) 구현 클래스
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
       //Fraction.h
       #ifndef FRACTION_H
       #define FRACTION_H
       class Fraction{
       public:
           //100으로 초기화
           Fraction(int Num = 0, int Denom = 1){
               Numerator = Num;
               Denominator = Denom;
           }
           //구현 없이 선언만 마무리
           void Display();
           void Simplify();
       private:
           int Numerator;
           int Denominator;
       };
       #endif //FRACTION_H
      		
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      
       //Fraction.cpp
       #include "Fraction.h"
       #include <algorithm>
       #include <iostream>
       using namespace std;
       void Fraction::Display(){
           cout<< Numerator << "/" << Denominator << endl;
       }
       void Fraction::Simplify(){
           //둘중 작은 수부터 내려가며 먼저 나오는 공약수로 약분
           //빠른 성능을 원하면 std::gcd도 사용 가능
           for (int i = std::min(Numerator, Denominator); i >= 1; i--){
               if(Numerator % i == 0 && Denominator % i == 0){
                   Numerator /= i;
                   Denominator /= i;
                   return;
               }
           }
       }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
       //main.cpp
       #include <iostream>	
       #include "Fraction.h"
      		
      		
       int main(){
               Fraction A;
               Fraction B(6,10);
               A.Display(); // 0/1
               B.Display(); // 6/10
               B.Simplify();
               B.Display();	//3/5
               B.Simplify();
               B.Display();	//3/5
       }
      
  • 1-6 객체 지향 프로그래밍(Object Oriented Programming)

    • C언어로 대중화된 절차 지향 프로그래밍의 한계점인 유지보수성, 재사용성을 보완하기위해 제안된 프로그래밍 패러다임중 하나로, Java, C++, Python같은 고급 프로그래밍 언어의 보다 추상화되고 일상어에 가까운 개념을 제시.
    • 기존 C언어는 기계어(Machine Language)-어셈블리어(Assembly Language) -C언어(C Language)로 이어지는 언어의 고급화, 추상화를 통해 프로그래밍을 보다 쉽고 가독성있게 만들었지만, 함수, 구조체 구조로는 극복하지 못하는 코드 재사용성과 복잡함에 대한 관리 한계가 있었음.
    • 객체 지향 프로그래밍은 보다 나은 설계와 유지보수성을 위해 객체들의 상호작용을 중심으로 프로그래밍하는 방법을 제시하며, 다음 4가지 핵심 원리, 객체 지향의 4 기둥(4 Pillars of OOP) 를 포함.
      • 캡슐화(Encapsulation)
        • 데이터와 함수를 클래스 단위로 묶어 외 접근을 제한.
        • 위에서 배운 Getter, Setter내용과 일치하며, 변수를 private으로 두고 필요한 함수들만 public으로 열어두어 간접적인 접근만 가능하게 함.
      • 상속(Inheritance)
        • 기존 클래스의 개념을 다른 클래스가 이어받을 수 있음.
        • 물려주는 클래스가 부모 클래스(Parent Class), 물려받는 클래스가 자식 클래스(Child Class)
        • 부모 클래스에 정의된 개념은 자식클래스에서 사용 가능하며, 자식 클래스는 추가적인 맴버 변수나 함수 정의가 가능.
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          
            #include <iostream>
            using namespace std;
            class Transportation{	//부모 클래스
            public:
                void Drive() {
                    cout << "Going to the City Hall" << endl;
                }
            };
            class Taxi : public Transportation{//자식 클래스
          
            };
            int main() {
                Transportation* A = new Taxi();	//상속받은 자식 클래스 객체를 부모클래스 포인터로 가리킴(업캐스팅)
                A->Drive(); //시청으로 출발
            }
          
      • 추상화(Abstraction)
        • 복잡한 내부 구현을 숨김으로서 필요한 기능만 드러나는 단순한 개념으로 변환
        • 캡슐화와 유사하지만 데이터 보호와 구조의 단순화중 어느 부분에 초점을 맞추느냐에 따라 다음.
        • 추상 클래스와 인터페이스를 이용해 구현.
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          
            #include <iostream>
            using namespace std;
            class Transportation{	//부모 추상 클래스
                public:
                    virtual void Drive() = 0;	//순수 함수, 구현은 하위 클래스에서!
            };
            class Taxi1 : public Transportation{	//추상화된 함수 구현 1
                public:
                    void Drive() override{
                        cout << "Going to the City Hall" << endl;
                    }
            };
            class Taxi2 : public Transportation{
                public:
                    void Drive() override{
                        cout << "Going to the Supermarket" << endl;
                    }
            };
            int main(){
                Taxi1 A;
                Taxi2 B;
                A.Drive(); //시청으로 출발
                B.Drive(); //슈퍼마켓으로 출발
            }
          
      • 다형성(Polymorphism)
        • 변수, 함수등의 요소들이 상황에 따라 다양하게 해석되는 것에 대한 허용.
        • 오버로딩(Overloading)
          • 같은 이름의 함수가 매개변수에 따라 다양한 형태로 구현하는 기능
          • 오버로딩으로 다양한 입력을 받는 생성자 코드 예시와 동일
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            
              #include <iostream>
              using namespace std;
              class Transportation{
              public:
                  Transportation(int val = 0){	//int 생성자, 입력 있으면 val, 없으면 0
                      Price = val ;
                  }
                  Transportation(float val){	//float 생성자
                      Price  = (int) val;
                  }
                  void ShowPrice(){
                      cout << "Price: " << Price << endl;
                  }
              private:
                  int Price;
              };
              int main(){
              Transportation A;
              Transportation B(30);
              Transportation C(5.689f);
              A.ShowPrice(); //0 출력
              B.ShowPrice(); //30 출력
              C.ShowPrice(); //5 출력
              }
            
        • 오버라이딩(Overriding)
          • 자식 클래스에서 부모 클래스의 함수의 내용을 새로운 내용으로 덮어쓰는 행위
          • 부모에서 virtual 키워드를 사용함으로서 오버라이딩에 대한 허용을 표시.
          • 자식에선 overriding 키워드를 사용해 함수를 오버라이딩 할 것이라 선언.
          • 부모 클래스에서 함수를 호출하더라도, 자식 오브젝트에 구현된 오버라이딩 함수가 있으면 자식의 함수를 호출
            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>
              using namespace std;
              class Transportation{
              public:
                  Transportation(){
                      Price = 10;
                  }
                  virtual void ShowPrice(){
                      cout << "Price: " << Price << endl;
                  }
              protected:
                  int Price;
              };
              class Taxi : public Transportation{
              public:
                  void ShowPrice() override{
                      cout << "Taxi Price: " << Price << endl;
                  }
              };
              int main(){
              Transportation* A = new Transportation();
              Transportation* B = new Taxi();
              A->ShowPrice(); //Price: 10 출력
              B->ShowPrice(); //Taxi Price: 10 출력, Transportation* 이여도 자식 함수 호출
              delete A;
              delete B;
              }
            
    • 클래스를 이용한 게임 직업 예시
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      
        //Adventure.h
        class Adventure{//추상화 클래스
            public:
                virtual void useSkill() = 0; //순수 가상 함수
        };
        class Warrior: public Adventure{
            public:
                void useSkill() override;
        };
        class Mage: public Adventure{
            public:
                void useSkill() override;
        };
        class Archer: public Adventure{
            public:
                void useSkill() override;
        };
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      
        //Adventure.cpp
        #include <iostream>
        #include <Adventure.h>
        using namespace std;
        //각자 다른 스킬 텍스트 출력
        void Warrior::useSkill(){
            cout << "Warrior uses Slash!" << endl;
        }
        void Mage::useSkill(){
            cout << "Warrior casts Fairball!" << endl;
        }
        void Archer::useSkill(){
            cout << "Warrior shoots an arrow!" << endl;
        }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        //main.cpp
        #include <iostream>
        #include <Adventure.h>
        int main(){
            Adventure* A = new Warrior();
            Adventure* B = new Mage();
            Adventure* C = new Archer();
            A->useSkill();
            B->useSkill();
            C->useSkill();
        }
      
This post is licensed under CC BY 4.0 by the author.