Hyun2and

인프런 C++ 프로그래밍 입문 : 객체지향 본문

공부엔 끝이없다/인프런 C++

인프런 C++ 프로그래밍 입문 : 객체지향

혀니앤 2023. 2. 20. 22:02
728x90

[인프런] C++ 프로그래밍 입문 을 보고 정리하는 글

객체지향의 시작

  • C++ 은 C 기반으로 다른 기능들을 탑재하다보니 절차와 객체 사이 어딘가에 있다

Class

  • 클래스를 정의하는 것 자체로는 메모리를 차지하지 않는다
  • C++ 에서 class는 struct 와 은닉설정만 다르다
  • 클래스 안에 함수를 정의해도 되긴하지만, 밖에 빼서 정의할 때에는 :: 로 클래스명을 정의한다
  • 데이터와 동작을 함께 정의한다
  • instantiate : 객체를 만든다
  • 클래스 함수 내부에서 매개변수인지, 멤버변수인지 표시하기 위해 규칙이 있는 경우가 많다
    • m_, m, _(언더바) 등
    • 현 프로젝트에서는 클래스 멤버 변수는 크게 정의가 없고, 함수에 들어가는 변수는 In___ , 함수에서 포인터 값으로 전달해줄 변수는 out____ 으로 정의하고 있다

생성자와 소멸자

  • 생성자 : 클래스가 생성될 때 호출.
    • 여러개가 존재할 수 있다. 오버로딩해서 정의한다
  • 소멸자 : 클래스가 소멸될 때 호출. 단 하나만 가진다

 

생성자

  • 기본 생성자 : 인자가 없는 생성자
  • 복사 생성자 : 자기 자신의 클래스 참조 타입을 인자로 받는다. 다른 값을 복사해서 만들겠다
Knight(const Knight& knight){ ... } // 기존의 클래스 인스턴스와 동일한 값을 가지는 인스턴스를 만들고 싶다 !
  • 암시적 생성자 implicit : 생성자를 정의하지 않으면 컴파일러가 기본 생성자를 자동으로 만든다
  • 사용할 수 있는 적절한 기본 생성자가 없습니다 → 인자에 맞는 생성자를 만들어줘야한다
  • 특이한 경우
둘의 결과는 다르다 Knight k3 = k1; //복사 생성자를 호출함 Knight k4; // 기본 생성자를 호출함 k4 = k1
  • 타입 변환 생성자
    • 인자를 1개만 받아서, 해당 인자의 타입으로 맞춰주는 과정
    • 암시적인 경우 컴파일러가 알아서 타입을 바꿔준다
  • explicit 키워드 : 명시적인 용도로만 사용할 것이다

 


상속성

  • 공통적인 속성을 최대한 재활용하기 위한 방법
  • 부모의 내용을 상속받는다
  • 메모리는 부모의 내용도 포함해서 메모리에 잡힌다
  • 똑같은 이름의 함수로 부모의 요소를 재정의할 수 있다 (거의 쓰지는 않는다)

생성자, 소멸자

  • 생성자는 각 클래스마다 호출되는 것
  • 생성자와 소멸자는 상속이 되지 않는다. 각각 따로 존재
  • 자식 클래스를 생성하면 자동으로 부모의 생성자가 호출된다
    • 부모 생성자 먼저, 자식 소멸자 먼저
    • 정확히는 자식 클래스의 생성자를 호출하려는 선처리 단계에서 부모 클래스의 생성자를 호출하는 것이다
    • 자식 클래스의 소멸자 후처리 단계에서 부모 클래스의 소멸자를 호출하는 것이다
  • 부모의 생성자도 지정해서 호출할 수 있다
Knight(int stemina) : Player(100){ }

 

  • 보통 최상위 구조를 정의하고, 상속구조를 만든다
GameObject 
~Creature 
~ Player, 
Monster 
~ Projcetile 
- Env 
~ Item 
~ Weapon

 

 


은닉성

  • 안전성을 위해 감추는 것
  • 캡슐화, Data Hiding, Encapsulation
  • 왜 숨기나?
    • 건들면 위험해지는 경우
    • 다른 경우로 접근하길 원하는 경우
  • 예시
class Car { 
public : //일반적인 사용자가 사용하게 될 함수 
	void MoveHandle() { } 
	void PushPedal() { } //깊은 작업에서만 사용하게 될 함수 
	void DisassembleCar() { } 
	void RunEngine() { } 
} 

int main()
{ 
	Car car; 
    return 0; 
}

 


접근지정자

  • C++ 에서는 class에 한 번만 작성해주면 된다class Car{ public : ~~ }
  • public : 누구에게나 공개됨
  • protected : 나, 나의 자식들에게만 허용한다
  • private : 클래스 내부에서만 사용할 것

캡슐화

  • 연관된 데이터와 함수를 논리적으로 묶어놓은 것
  • get, set 함수를 만들어줘야 한다
  • 외부에서 모르는 사람이 쓰더라도 사양과 기능에 부합하도록 노출시켜야 한다

상속접근지정자

  • 다음 세대에게 부모의 자산을 어떻게 물려줄 것인지
  • 반드시 자식에게 부모의 모든 것을 동일하게 물려줄 필요는 없다
  • public 상속을 보통 많이쓴다
  • 상속 종류
    • public : 부모의 상속을 그대로 열어주겠다
    • protected : 부모의 public 요소들을 protected로.
    • private : public , protected 까지도 전부 private로 만든다

 


 

다형성 (Ploymorphism)

  • 겉은 비슷하지만 내부가 다르게 작동한다
  • 오버로딩 : 같은 이름의 함수를 재사용한다
  • 오버라이딩 : 부모 클래스의 함수를 자식 클래스에서 재정의한다

오버로딩

  • 같은 이름을 가지지만 매개변수가 다른 함수를 정의한다
void Move() { } void Move(int a) { }

 

 

오버라이딩

  • 부모의 요소를 자식 클래스에서 재정의한다
class Player(){ void Move(){ } } 
class Knight : Player { void Move(){ } }
  • 애매한 상황들이 생긴다
void MovePlayer(Player* player){ } 
void MoveKnight(Knight* knight){ } 
int main(){ 
	Player p; 
	MovePlayer(&p); 
	MoveKnight(&p); // Err. Player라고해서 Knight라고 할 수 있나? 
}

 

바인딩

  • 정적 바인딩 : 어떤 함수를 호출할지 컴파일 시점에 결정한다
  • 동적 바인딩 : 어떤 함수를 호출할지 실행 시점에 결정한다
  • 일반 함수들은 정적 바인딩을 쓰기 때문에, 동적 바인딩을 하기 위해서는 가상함수를 사용해야 함
class Player(){ 
	void Move(){ } 
	virtual void MoveVirtual() { } 
} 

class Knight : Player { 
	void Move() { } 
	virtual void MoveVirtual() { } 
} 

int main() { 
	Player* p = new Knight(); p->Move(); // 부모의 함수가 호출된다 
    p->MoveVirtual(); //자식의 함수가 호출된다 
}

 


 

초기화 리스트

  • 멤버 변수들을 초기화하는 부분
  • 왜 초기화를 해야할까?
    • 버그 예방에 중요함
    • 포인터 등 주소값이 들어가 있을 경우
class Knight { }
  • stack 메모리 특성상 이전의 메모리를 재사용할 수 없다
  • 클래스 정의 시 멤버들의 값이 쓰레기값으로 들어가 있다
  • 초기화 방법
    • 생성자 내
    • 초기화 리스트
    • C++ 11 문법
int _hp = 200; // 기존의 C++에서는 불가능했다

 

초기화 리스트

  • 상속 관계에서 원하는 부모 생성자를 호출하는 경우
  • 일반변수는 생성자에서 초기화 하는 것과 차이 없음
  • 멤버 타입이 클래스인 경우 사용됨
  • 관계가 상속(is a) 관계인지, 포함(has a) 관계인지class Knight : public Player { public: Knight() : Player(1), _hp(100) //선처리 영역 에서 Inventory의 기본 생성자가 호출된다 { } public: Inventory _inventory; }
  • 포함관계인 클래스 Inventory 의 생명주기는 Knight를 따른다. Knight의 선처리 과정에서 함께 생성된다
  • 직접 다른 생성자를 쓰고자 할 때 생성자가 2번 호출되게 된다
  • 결론 : 생성자 리스트에 적어주면 중복생성을 막을 수 있다!
class Knight { public : Knight() : Player(1), _hp(100), _inventory(20) { } }
class Knight { 
public : 
    Knight() 
    { 
    _inventory = Inventory(20); // 기본 생성자가 아닌 다른 생성자로 호출하면서 기존의 생성자를 덮어씌우게 된다 
    } 
}

+ 무조건 생성자 리스트에만 넣게 되는 경우

  • 참조 타입이나, const 는 생성자 리스트에 넣었어야 했다

 


 

연산자 오버로딩

  • 연산자를 커스터마이징 한다
  • 함수 오버로딩과의 차이
    • 연산자는 피연산자의 개수, 타입이 고정되어 있다
  • operator 키워드를 통해 작성한다
  • a op b 인 경우, a 를 기준으로 실행된다
Position operator+(const Position& arg) { }

 

  • a가 클래스가 아니면 사용할 수 없다
  • 1 + pospos + 1 로 써야함

 

 

 

  •  
728x90
Comments