[C++] 4.3 클래스 - const, static

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

전에 하던 그 생성자가 아니라, C++스러운 생성자를 만들어보자.

#include <iostream>

class Marine{
	int hp;
	int coord_x, coord_y;
	int damage;
	bool is_dead;

public : 
	Marine();
	Marine(int x, int 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();
}

익숙하지 않은 생성자 친구가 눈에 보인다.

Marine::Marine() :
		hp(50), coord_x(0), coord_y(0), 
		damage(5), is_dead(false){}
  • 함수 {본체} 에는 아무것도 없고, 앞에 이상한게 덕지덕지 붙어있다.
Marine::Marine() {
  hp = 50;
  coord_x = coord_y = 0;
  damage = 5;
  is_dead = false;
}
  • 원래는 이렇게 생긴 친구다.

생성자 리스트

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

 생성자 이름 : var1(args), var2(args){}
  • 이렇게 생성자에서 멤버변수 초기화를 하는 이 친구가 생성자 리스트이다.
  • 리스트 초기화가 좋은 이유는
    • 변수를 초기화 할 때 다이렉트로 한번에 초기화 하기 때문에, 효율적이고 확실하다.
    • 다만 멤버변수 전부다 함께 초기화 시켜줘야함.

static

static 멤버 변수

C에서 쓰던 그 static 맞다.
클래스에서도 사용가능하고, C랑 똑같이 공유가 되고, main문 종료전까지 계속 살아있다.

class Marine{
	static int total_marine_num;

	int hp;
	int coord_x, coord_y;
	int damage;
	bool is_dead;

	const int default_damage;

public : 
	Marine();
	Marine(int x, int y);
	Marine::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_num = 0;

Marine::Marine() :
		hp(50), coord_x(0), coord_y(0), 
		damage(5), is_dead(false), default_damage(5){total_marine_num++;}
...
  • 해당 클래스의 static 멤버변수이다.
  • 이 클래스를 사용하는 모든 클래스들이 공유하는 변수이다.
  • static 멤버변수는 그 어느객체의 것도 아니기때문에, 이렇게 변수를 선언해도 된다.
int Marine::total_marine_num = 0;

이 static 멤버변수를 이렇게 사용한다.

Marine::Marine() :
		hp(50), coord_x(0), coord_y(0), 
		damage(5), is_dead(false), default_damage(5){total_marine_num++;}
  • 생성할 땐 이 static변수를 +1 시켰고
	~Marine() {total_marine_num--;}
  • 소멸될땐 -1 시켰다.
  • 이런식으로 사용하면 된다.

static 멤버 함수

class Marine{
	static int total_marine_num;

	int hp;
	int coord_x, coord_y;
	int damage;
	bool is_dead;

	const int default_damage;

public : 
	Marine();
	Marine(int x, int y);
	Marine::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();

	static void show_total_marine();
	~Marine() {total_marine_num--;}
};

...

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

...

// main()
  Marine::show_total_marine();
  • static 멤버변수를 선언하였다.
  • static 함수는 어떤 객체에 종속되는 것이 아니라, 클래스 자체에 종속된다
    • (객체).(멤버 함수) 이렇게 부르는게 아니라
      클래스::static 함수() 이렇게 불러야한다.

this

this 는 객체 자신을 가리키는 키워드이다.

class Marine{
	static int total_marine_num;
	const static int i =0;

	int hp;
	int coord_x, coord_y;
	int damage;
	bool is_dead;

	const int default_damage;

public : 
	Marine();
	Marine(int x, int y);
	Marine::Marine(int x, int y, int default_damage);
	
	int attack();
	Marine& be_attacked(int damge_earn);
	void move(int x, int y);

	void show_status();
	static void show_total_marine();
	~Marine() {total_marine_num--;}
};

...

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

  return *this;
}

...

// main()
  marine2.be_attacked(marine1.attack()).be_attacked(marine1.attack());

이런식으로 쓰인다.

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

  return *this;
}

이친구의 변수 hp를 this->hp라고 변경해도, 위의 함수와 의미는 같다.

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

  return *this;
}
  • 객체 내에서 내꺼다! 표시하는게 this 라는 표현이며, 멤버변수, 멤버함수 둘다 적용가능하다.
  • 당연히 클래스꺼인 static 함수,변수는 객체꺼 표시인 this를 표시 못한다.

근데 함수 리턴값이 Marine& 인데, 이 레퍼런스(별명, alias)를 리턴한다는 게 무슨뜻일까?

레퍼런스를 리턴하는 함수

#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();

	int& c = a.access_x();
	c=4;
	a.show_x();

	int d = a.access_x();
	d=3;
	a.show_x();

	// 오류코드
	// int& e = a.get_x();
	// e = 2;
	// a.show_x();

	int f = a.get_x();
	f = 1;
	a.show_x();

}
  • 결과

여기서 int를 리턴하는 'get_x()' 함수와, int의 레퍼런스를 리턴하는 'access_x()'함수가 사용됨.
아으 헷갈려.. 어케 돌아가는지 차근차근 보자

int& c = a.access_x();
c = 4;
a.show_x();
  • c는 지금 x의 레퍼런스를 받았다. 즉 c는 x의 별명이 된것.
  • c를 4로 바꿨으니, x값도 변경되었다.
  • 레퍼런스를 리턴하는 함수는 그 함수 부분을 원래의 그 멤버변수로 치환했다고 생각해도 상관없음.
    int& c = x; // int x의 새 별명은 c

그렇담 int 형에 레퍼런스를 넣었는데 컴파일 에러가 안나오는 걸까

int d = a.access_x();
d=3;
a.show_x();
  • int d 에 그냥 x의 레퍼런스 관련 값이 들어갔음.
  • d는 그냥 int 형 변수로써 값을 받아온거 이기 때문에, 컴파일 에러가 안나옴.
  • 그렇기 때문에, d=3; 을 넣었을 때, 객체에 x값이 변하지 않는게 당연하다.

에러가 나온 코드를 확인해보자.

int& e = a.get_x();
e = 2;
a.show_x();
  • x의 값을 리턴받은 레퍼런스변수 e는 아무짝에 쓸모없는 친구가 되어버렸다.
  • 그래서 에러가 난다. (애초에 타입도 안맞고, 사라지는 값을 별명으로 붙이는거도 말도안됨)
    int& e = x(지역변수로 복사된 x);

마지막으로 확인해볼 친구는 int형에 객체에서 int형 값을 받아온 경우를 보자.

int f = a.get_x();
f = 1;
a.show_x();
  • f에 x값을 받아오는 것을 확인 할 수 있다.
  • 하지만 x의 '값'을 받아온거지 x를 받아온 것이 아니기 때문에, 당연히 f=1을 해도 값이 객체의 맴버값이 바뀌진 않는다.

이제 다시 Marine예제로 가보자.

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

  return *this;
}
  • be_attacked에서 &Marine 타입으로 *this를 리턴받았다.
  • 여태 자신의 멤버변수를 리턴할 때 (*this).멤버변수this->멤버변수 처럼 써준 것이었다.
  • *this는 객체 자신을 의미한다.

그렇담 이 부분에서 어떤일이 벌어질까

marine2.be_attacked(marine1.attack()).be_attacked(marine1.attack());
  • marine2.be_attacked(marine1.attack()) 이 실행되고, .be_attacked(marine1.attack()) 이 실행되는 것으로 확인할 수 있다.
    • marine2.be_attacked(marine1.attack())이 실행될 때,
      marine2에서 marine1의 공격을 받아낸 후, Marine&타입으로 marine2의 자신을 리턴 받고,
      다시 marine2.be_attacked(marine1.attack()) 으로 공격을 또 받아낸 후 자신을 리턴받는다.

Marine& 타입이 아니라, Marine 타입으로 *this를 리턴한다면 어떻게 될까?

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

  return *this;
}
  • 컴파일도 되고 동작도 된다.

그렇담 이 부분에서 어떤일이 벌어질까

marine2.be_attacked(marine1.attack()).be_attacked(marine1.attack());
  • 순서는 똑같이 두번 attack이 실행된다.
  • 그런데 레퍼런스 &Marine의 *this를 가져온게 아니라 그냥 Marine의 *this를 가져와서 동작한다
    • be_atttacked에서 임시객체의 Marine의 *this를 리턴한다.
    • '임시객체 Marine을 계속 새로 불러서' attack을 하는 것이기 때문에, 암만 attack()을 해도 한번밖에 실행이 안되는 것을 확인할 수 있다.

아니 그럼 위에 &Marine 의 *this 는 어떻게 두번 attack() 이 들어간거야?

  • 레퍼런스(별명)로 리턴을 해버려서, 임시객체가 계속 새로 생성이 되는것이 아니라,
    레퍼런스끼리 꼬리 물기를 해버려서 attack()이 이어지게 된다.

const 함수

  • 변수의 값을 바꾸지 않고 읽기만하는 상수함수로써 선언 가능하다.
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);  
  Marine(int x, int y, int default_damage);

  int attack() const;   // <- 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::attack() const { return default_damage; }
  • (기존 함수 정의) const; 이렇게 사용한다.

따라하자 씹어먹는 C++

좋은 웹페이지 즐겨찾기