본문 바로가기
C++

[C++] C++ 객체지향 언어에 대해 + 예시코드 (Class, Object, Abstract, Inheritance, Encapsulation, Polymorphism)

by 유노brain 2023. 10. 24.
반응형

절차지향 언어인 C언어와는 다르게 C++은 자바와 같은 객체지향(OOP)의 개념을 가집니다.

객체지향의 언어는 좋은 점을 많이 가지고 있는데요.

모델링이 용이하고, 재사용 가능하고, 유지보수 등에 있어서 여러 장점을 가집니다.

이제 C++의 핵심 객체지향 언어에 대해 알아보겠습니다.

 

OOP 개념들

객체지향(OOP)은 다음 6가지 개념들을 가집니다.

- Class and Object (클래스와 객체)
- Abstraction (추상화)
- Inheritance (상속)
- Encapsulation (캡슐화)
- Polymorphism (다형성)

 

 

 

1. Class (클래스)

클래스는 객체(Object)의 설계도라고 보시면 됩니다.

어떠한 물건을 만드는데에 있어서 설계도가 필요하듯이 클래스에서는 객체에 대한 설계도를 만들어 줍니다.

즉 이 객체가 자동차인지 또는 동물인지 정의해 줍니다.

 

추가적으로 객체의 속성과 행동에 대해서도 정의해 줍니다.

동물이라면 동물의 속성(손, 발), 행동(달리기, 짖기)을 가지게 됩니다.

자동차라면 자동차의 속성(문, 브랜드), 행동(속력, 문 열기)을 가지게 됩니다.

 

이런식으로 기본적으로 클래스는 객체에 대한 틀, 설계도를 줍니다.

 

 

2. Object (객체)

객체(Object)는 클래스의 설계도를 가지고 객체 즉 물건을 만든 것을 뜻합니다.

이것을 클래스의 인스턴스(instance)라고 합니다.

 

객체의 경우 상태와 동작으로 구성이 되어있습니다.

예를 들어 자동차 객체를 만들었다고 가정해 보겠습니다.

이 자동차의 상태 즉 브랜드는 "현대"이고 색상은 "검정" 그리고 출시년도는 "2020" 이렇게 나타낼 수 있습니다.

자동차의 동작으로는 "악셀을 밟으면" 속도가 올라고 문을 "열고" , "닫는" 동작을 수행할 수 있습니다.

 

추가적으로 좀 더 설명을 드리자면

객체를 생성하기 위해서는 "생성자"가 필요합니다.

생성자의 역할은 객체의 상태나 행동의 매개변수를 받아 객체의 초기상태를 나타내는 것을 뜻합니다.

 

 

Class, Object 예제 코드

아래는 Class와 Object로 작성한 예시코드입니다.

예시코드
#include <iostream>
#include <string>

class Car{
   public :
    // 생성자 name, speed, color 인자를 받는다.
    // 객체를 초기화해주는 역할을 가진다.
   Car(std::string name, int speed, std::string color): name_(name),
         speed_(speed), color_(color) {};
   private:
   std::string name_;
   int speed_;
   std::string color_;
};

int main() {
   Car my_car("hyundai", 150, "yellow");
   return 0;
}

먼저 코드에 대해 설명을 해드리겠습니다.

 

public, private

public : 누구나 멤버변수에 접근이 가능합니다.
private : 아래에 정의된 멤버변수의 경우 클래스 내부에서만 접근이 가능합니다.

 

생성자

Car(std::string name, int speed, std::string color): name_(name),
         speed_(speed), color_(color) {};

위의 코드는 생성자입니다.

객체를 초기화하는 역할을 하죠.

이 생성자는 name, speed, color 세 개의 매개변수를 받아 클래스 맴버변수 name_, speed_, color_를 초기화합니다.

이것은 main클래스에서 각각 "hyundai", 180, "yellow"가 할당됩니다.

 

 

3. Abstraction (추상화)

추상화의 객체 내부의 세부 사항을 숨기고 필요한 정보만 나타내 줍니다.

대체로 이 클래스는 사용할 것 같은데 아직 구현할 필요가 없을 때 추상화를 사용하기도 합니다.

추상화를 사용하면 위의 설명대로 필요한 정보만을 보여주기 때문에 나중에 사용할 때 이 추상화에 있는 정보를 필요한 클래스로 가져와서 override를 통해 재정의를 통해 사용할 수 있습니다.

 

다음은 추상화와 관련된 예제 코드입니다.

예시코드
#include <iostream>

// 추상 클래스: 동물(Animal)
class Animal {
public:
    // 순수 가상 함수(추상 메서드): 소리를 내는 동작
    virtual void makeSound() const = 0;
};

// 구체적인 동물 클래스: 개(Dog)
class Dog : public Animal {
public:
    // 소리를 내는 동작을 구체적으로 구현
    void makeSound() const override {
        std::cout << "멍멍!" << std::endl;
    }
};

int main(){
   Animal* myDog = new Dog();
   myDog ->makeSound();

   return 0;
}

먼저 추상 클래스 Animal(동물)을 만들었습니다.

클래스 안을 보면 어떠한 세부사항 없이 소리를 내는 동작을 나타내는 메소드 makeSound()만 존재합니다.

이제 아래 구체적인 동물 클래스인 Dog(개)를 통해 makeSound() 메소드를 제정의해 "멍멍!"이라는 소리가 나오도록 작성하겠습니다.

위의 코드를 실행하면 "멍멍!"이 출력되는 것을 알 수 있습니다.

 

 

4. Encapsulation (캡슐화)

캡슐화는 허가받지 않는 접근으로부터 내부 상태를 보호해 줍니다.

즉 외부에서 알 수 없도록 상태를 숨깁니다.

클래스가 제공하는 인터페이스만을 통해 접근이 가능합니다.

클래스 멤버와 메소드를 접근하는데에 있어서 접근 지정자('public', 'private', 'protected')에 따릅니다.

 

예시코드
#include <iostream>
#include <string>

class Car{
   // 외부에서 접근이 가능하다.
   public :
   Car(std::string name, int speed, std::string color): name_(name),
         speed_(speed), color_(color) {};

   int speed_up(int km){
         speed_ += km;
         return speed_;
   }
   // 외부에서의 접근을 막는다.
   private:
   std::string name_;
   int speed_;
   std::string color_;
};

int main() {
   Car my_car("hyundai", 150, "yellow");
   std::cout<<my_car.speed_up(20)<<std::endl;
   return 0;
}

위의 코드에서는 캡슐화를 통해 외부에서의 접근을 막는 예시입니다.

private를 통해 클래스 내의 인터페이스를 통해서만 접근이 가능해집니다.

 

5. Inheritance (상속)

상속의 경우 다른 클래스에서 정의된 것을 자신의 클래스에서 사용할 수 있는 것을 말합니다.

상속에서는 자식 클래스와 부모 클래스로 나누어지는데요.

부모 클래스는 자식 클래스에게 자신의 상태나 행동을 물려줍니다.

즉 자식 클래스에서는 부모클래스의 매개변수, 메소드 등을 사용할 수 있습니다.

(만약 부모클래스에서 private로 정의되어 있다면 자식클래스에서 받을 수 없습니다.)

 

하지만 반대로 부모클래스에서는 자식 클래스에 있는 정보를 사용하지 못합니다.

 

다음은 예시코드입니다.

예시코드
#include <iostream>
#include <string>

class Car{
 
   public :
   Car(std::string name, int speed, std::string color): name_(name),
         speed_(speed), color_(color) {}

   int speed_up(int km){
         speed_ += km;
         return speed_;
   }
   
   private:
   std::string name_;
   int speed_;
   std::string color_;
};

// 상속을 받을 때는 아래와 같이 상속을 받는다.
class Bus : public Car{
   public:
    Bus(std::string name, int speed, std::string color, int money):Car(name,speed,color), money_(money){}

   int Charge(int bill) {
      money_ += bill;
      return money_;
   }
   
   private:
   int money_;
};

int main() {
   Car my_car("hyundai", 150, "yellow");
   std::cout<<my_car.speed_up(20)<<std::endl;
   Bus my_bus("BMW",120,"red",2000);
   std::cout<<my_bus.speed_up(10)<<std::endl;
   std::cout<<my_bus.Charge(1500)<<std::endl;
 
   return 0;
}

 

상속을 받을 때는 클래스 옆에 지정자로 부모 클래스를 받으면 됩니다.

상속을 받게 되면 Bus 클래스에서 따로 name, speed, color를 작성하지 않아도 부모 클래스에 의해 정보를 받는 것을 알 수 있습니다.

 

 

6. Polymorphism (다형성)

다형성은 여러 객체를 동일한 하나의 인터페이스를 통해 처리하거나 다양한 방식으로 동작할 수 있게 합니다.

클래스의 포인터로 다양한 유형의 객체를 가리킬 수 있습니다.

 

다형성은 Static polymorphism(정적 다형성)과 Dynamic polymorphism(동적 다형성)으로 나뉩니다.

 

Static polymorphism(정적 다형성)

정적 다형성은 compile-time polymorphism이라고 불립니다.

정적 다형성은 컴파일 시에  결정되며 overloading, overriding 관련 있습니다.

같은 이름의 함수가 다른 매개변수 형식을 가질 때 사용됩니다.

 

Dynamic polymorphism(동적 다형성)

동적 다형성은 runtime polymorphism이라고 불립니다.

런타임에 결정이 되며 Virtual function과 관련이 있습니다.

객체의 실제 타입을 기반으로 매서드를 호출합니다.

 

아래는 예시코드입니다.

예시코드
#include <iostream>
#include <string>

class Car{
 
   public :
   Car(std::string name, int speed, std::string color): name_(name),
         speed_(speed), color_(color) {}

   int speed_up(int km){
         speed_ += km;
         return speed_;
   }
   
   private:
   std::string name_;
   int speed_;
   std::string color_;
};

// 상속을 받을 때는 아래와 같이 상속을 받는다.
class Bus : public Car{
   public:
    Bus(std::string name, int speed, std::string color, int money):Car(name,speed,color), money_(money){}

   int Charge(int bill) {
      money_ += bill;
      return money_;
   }
   
   private:
   int money_;
};

int main() {
   Car my_car("hyundai", 150, "yellow");
   std::cout<<my_car.speed_up(20)<<std::endl;
   Bus my_bus("BMW",120,"red",2000);
   std::cout<<my_bus.speed_up(10)<<std::endl;
   std::cout<<my_bus.Charge(1500)<<std::endl;

   Car* car_ptr = &my_bus;
   std::cout << car_ptr->speed_up(150) << std::endl;
   std::cout << my_bus.Charge(1500) << std::endl;
 
   return 0;
}

아래를 보면 Car* car_ptr로 Car의 포인터로 my_bus의 주소를 받는 것을 확인할 수 있습니다.

이렇게 다형성을 통해서도 코드를 작성할 수 있습니다.

반응형

댓글