객체지향프로그래밍의 시작 -2

·2022년 5월 20일
0

cpp_study

목록 보기
5/25

const, const, const!

생성자의 초기화 리스트(initializer list)

#include <iostream>

class Marine {
  int hp; // 마린 체력
  int coord_x, coord_y; // 마린 위치
  int damage; // 공격력
  bool is_dead;
public:
  Marine(); // 기본 생성자
  Marine(int x, int y); // x, y 좌표에 마린 생성
  int attack(); // 데미지를 리턴한다.
  void be_attacked(int damage_earn); // 입는 데미지
  void move(int x, int y); // 새로운 위치
  void show_status(); // 상태를 보여준다.
};

// 아래에 집중!!
Marine::Marine() : hp(50), coord_x(0), coord_y(0), damage(5), is_dead(false) {}

Marine::Marine(int x, int y)
: coord_x(x), coord_y(y), hp(50), damage(5), is_dead(false) {}

void Marine::move(int x, int y) {
  coord_x = x;
  coord_y = y;
}

int Marine::attack() { return damage; }

void Marine::be_attacked(int damage_earn) {
  hp -= damage_earn;
  if (hp <= 0) is_dead = true;
}

void Marine::show_status() {
  std::cout << " *** Marine *** " << std::endl;
  std::cout << " Location : ( " << coord_x << " , " << coord_y << " ) "
  << std::endl;
  std::cout << " HP : " << hp << std::endl;
}

int main() {
  Marine marine1(2, 3);
  Marine marine2(3, 5);
  marine1.show_status();
  marine2.show_status();
}

초기화 리스트란?

  • 기존 생성자보다 훨씬 줄어든 코드 길이로 같은 기능을 동작할 수 있음.
  • (생성자 이름): var1(arg1), var2(arg2) {}
  • 위 경우에서 var1 이름 == argv1 이름 이어도 정상적으로 동작함.
    -> 이유: coord_x를 동일 이름이라고 둘 때(var1 == argv1 == coord_x) 바깥 coord_x는 무조건 멤버 변수, 괄호 안 coord_x는 원칙상 인자로 받은 coord_x를 우선적으로 지칭

초기화 리스트 장점

생성과 초기화를 동시에 하게 됨
클래스였다면 복사 생성자 호출과 같은 효과

초기화 리스트 미사용 시: 생성을 먼저 하고 그 다음에 대입,
클래스였다면 디폴트 생성자가 호출된 뒤 대입이 수행

즉, 비용이 절감됨. 좀 더 자세히 얘기해보자.

class Foo
{
public:
  Foo(std::string& str);
  
private:
  std::string name;
}

Foo::Foo(std::string& str)
{
  name = str;  // 초기화가 아니라 대입!!
}

위 코드를 보면 데이터 멤버 객체에 대해 초기화를 수행하고 대입까지 해버리는, 즉 작업을 2번 수행하는 꼴이 됨.

멤버 초기화 리스트를 사용하면 위와 같이 작업을 2번 수행하는 게 아닌 1번만 수행해도 됨.

class Foo
{
public:
  Foo(std::string& str);
  
private:
  std::string name;
}

Foo::Foo(std::string& str) : 
  name(str) // 초기화만으로 str 값을 name 안에 넣게 될 수 있습니다.
{}

그런데 이때 기본 생성자를 호출해 초기화하고 싶으면 아래와 같이 쓰면 됨.

Foo::Foo(std::string& str) : 
  name() // 기본 생성자 호출
{}

초기화리스트로 상수 초기화가 가능할까?

결론부터 말하면 가능하다.
아래 코드를 보면 default_damage가 constant(상수)로 선언된 것을 볼 수 있다.
그런데 생성자를 보면 int default_damage로 받도록 되어있다.
이때 default_damage는

const int default_damage = (인자로 받은 default_damage);

와 같이 초기화된 것과 마찬가지다.

#include <iostream>

class Marine {
  int hp; // 마린 체력
  int coord_x, coord_y; // 마린 위치
  bool is_dead;
  const int default_damage; // 기본 공격력
public:
  Marine(); // 기본 생성자
  Marine(int x, int y); // x, y 좌표에 마린 생성
  Marine(int x, int y, int default_damage);
  int attack(); // 데미지를 리턴한다.
  void be_attacked(int damage_earn); // 입는 데미지
  void move(int x, int y); // 새로운 위치
  void show_status(); // 상태를 보여준다.
};

Marine::Marine()
: hp(50), coord_x(0), coord_y(0), default_damage(5), is_dead(false) {}

Marine::Marine(int x, int y)
: coord_x(x), coord_y(y), hp(50), default_damage(5), is_dead(false) {}

Marine::Marine(int x, int y, int default_damage)
: coord_x(x),
coord_y(y),
hp(50),
default_damage(default_damage),
is_dead(false) {}

static 변수(생성된 총 Marine 수 세기)

static 멤버
전역 변수 같지만 클래스 하나에만 종속되는 변수
원래 클래스 멤버는 선언만 하고 초기화할 수 없음.
예외로 const static은 클래스 코드 내부에 선언과 초기화를 동시에 가능

// static 멤버 변수의 사용

class Marine {
  static int total_marine_num;
  int hp; // 마린 체력
  int coord_x, coord_y; // 마린 위치
  bool is_dead;
  const int default_damage; // 기본 공격력
public:
  Marine(); // 기본 생성자
  Marine(int x, int y); // x, y 좌표에 마린 생성
  Marine(int x, int y, int default_damage);
  int attack(); // 데미지를 리턴한다.
  void be_attacked(int damage_earn); // 입는 데미지
  void move(int x, int y); // 새로운 위치
  void show_status(); // 상태를 보여준다.
  ~Marine() { total_marine_num--; }
};

// 아래와 같이 쓰지 않을 수 없음.
int Marine::total_marine_num = 0;

static 함수의 정의

  • 클래스 전체에 딱 1개만 존재하는 함수
  • {class}::{static_함수}로 호출(어떤 객체에도 속하지 않기 때문)
  • void Marine::show_total_marine(){ ... }으로 클래스 외부에서 함수 내용 작성함
    - 클래스 내부에서는 static void show_total_marine(){}으로 선언

질문: static 메소드는 외부에서 굳이 static 표시를 안함?

this

  • this 포인터는 객체의 주소를 저장한 포인터 변수
  • 아래 코드에서 보면 1번2번에서 찍은 Dummy 클래스 내부의 변수 this와 dummy의 주소값을 가진 포인터 dummyPointer 는 같은 주소값을 가진 것을 볼 수 있다.
// Example program
#include <iostream>
#include <string>

using namespace std;

class Dummy{
public:
    Dummy();
    ~Dummy();
    void showThisAddress(){
    	// ** 1번 **
        cout << "this: " << this << endl;
    }
    
};

Dummy::Dummy(){}
Dummy::~Dummy(){}

int main()
{
  Dummy dummy;
  dummy.showThisAddress();
  
  Dummy* dummyPtr;
  dummyPtr = &dummy;
  // ** 2번 **
  cout << "dummyPointer: " << dummyPtr << endl;
  
  return 0;
}

참고: this의 의미와 사용법

레퍼런스를 리턴하는 함수

레퍼런스를 리턴하는 함수 acces_x를 통해
-> private 변수에 해당하는 x를 클래스 외부에서 변경할 수 있는 권한이 주어진다.

// 레퍼런스를 리턴하는 함수 
#include <iostream>

class A { 
  int x;
public:
  A(int c) : x(c) {}
  int& access_x() { return x; }
  int get_x() { return x; }
  void show_x() { std::cout << x << std::endl; }
};

int main() { 
  A a(5);
  a.show_x();	// 5
  
  int& c = a.access_x(); 
  c = 4;
  a.show_x();	// 4
  
  int d = a.access_x(); 
  d = 3;
  a.show_x();	// 4
  
  // 아래는 오류
  // int& e = a.get_x(); 
  // e = 2;
  // a.show_x();	
  
  int f = a.get_x(); 
  f = 1;
  a.show_x();	// 4
}

오류 나는 부분

위 코드 "아래는 오류"부분에서는 컴파일 오류가 발생하는데, 그 이유는

  • a.get_x()로 타입이 반환되며
  • 임시 객체(int 변수)가 생성되어 반환된 타입을 저장함
  • 그런데 임시 객체는 레퍼런스를 가질 수 없음
  • 따라서 오류 발생!

const 함수

상수 멤버 함수

  • 아래와 같이 쓰는 게 특징
    int Marine::attack() const { return default_damage; }
  • 변수들의 값을 바꾸지 않고 읽기만 함
  • 객체들의 읽기만이 수행
  • 상수 함수 내에서 호출할 수 있는 함수: 다른 상수 함수

그러나 많은 경우 getter를 씀(멤버 변수: private, 게터: public)


// 상수 멤버 함수
#include <iostream>
class Marine {
  static int total_marine_num;
  const static int i = 0;
  int hp; // 마린 체력
  int coord_x, coord_y; // 마린 위치
  bool is_dead;
  const int default_damage; // 기본 공격력
public:
  Marine(); // 기본 생성자
  Marine(int x, int y); // x, y 좌표에 마린 생성
  Marine(int x, int y, int default_damage);
  int attack() const; // 데미지를 리턴한다.
  Marine& be_attacked(int damage_earn); // 입는 데미지
  void move(int x, int y); // 새로운 위치
  void show_status(); // 상태를 보여준다.
  static void show_total_marine();
  ~Marine() { total_marine_num--; }
};

int Marine::total_marine_num = 0;

void Marine::show_total_marine() {
  std::cout << "전체 마린 수 : " << total_marine_num << std::endl;
}

Marine::Marine()
: hp(50), coord_x(0), coord_y(0), default_damage(5), is_dead(false) {
total_marine_num++;
}

Marine::Marine(int x, int y)
: coord_x(x),
coord_y(y),
hp(50),
default_damage(5),
is_dead(false) {
total_marine_num++;
}

Marine::Marine(int x, int y, int default_damage)
: coord_x(x),
coord_y(y),
hp(50),
default_damage(default_damage),
is_dead(false) {
  total_marine_num++;
}

void Marine::move(int x, int y) {
  coord_x = x;
  coord_y = y;
}

int Marine::attack() const { return default_damage; }

Marine& Marine::be_attacked(int damage_earn) {
  hp -= damage_earn;
  if (hp <= 0) is_dead = true;
  return *this;
}

void Marine::show_status() {
  std::cout << " *** Marine *** " << std::endl;
  std::cout << " Location : ( " << coord_x << " , " << coord_y << " ) "
  << std::endl;
  std::cout << " HP : " << hp << std::endl;
  std::cout << " 현재 총 마린 수 : " << total_marine_num << std::endl;
}

int main() {
  Marine marine1(2, 3, 5);
  marine1.show_status();
  Marine marine2(3, 5, 10);
  marine2.show_status();
  std::cout << std::endl << "마린 1 이 마린 2 를 두 번 공격! " << std::endl;
  marine2.be_attacked(marine1.attack()).be_attacked(marine1.attack());
  marine1.show_status();
  marine2.show_status();
}
profile
이것저것 개발하는 것 좋아하지만 서버 개발이 제일 좋더라구요..

0개의 댓글