[C++ 기초] 접근 지정자, Struct, 클래스 초기화, 생성자, 오버로딩, 연산자 오버로딩

라멘커비·2023년 12월 21일
0

CPP 입문

목록 보기
8/25

클래스 개요 복습

  • 나는 Monster()를 정의하거나 만든적이 없는데 사용가능하다.
    → 컴파일러가 만들어 준 것이다.
    나도 모르는 어떠한 함수나 어떤 내용들을 컴파일러가 (없으면) 만들어버린다.
    • int가 할 수 있는데 내 클래스가 못한다? → 내가 안 만든 것이다.
      int가 할 수 없는데 내 클래스는 한다? → 그건 내가 만든 거다.
      원한다면 int와 완전히 동일한 클래스도 만들 수 있다.
class Monster {
 
};

int main()
{
    // 선생님 기준 int도 class다.
    // int가 할 수 있는데 대 클래스가 못한다? -> 내가 안 만든 것이다.
    // int가 할 수 없는데 내 클래스는 한다. -> 그건 내가 만들어준거다.

    // 내가 원한다면 int와 완전히 동일한 클래스도 만들 수 있다.

    // 즉 그냥 내부에 뭐가 구현되어 있냐 안되어있냐 차이
    Monster NewMonster = Monster();

    return 0;
}
  • 일반적인 멤버들은 사용하기 위해서 무조건 객체가 필요하다.
    클래스는 객체 자체가 아니고 설계도, 개념의 느낌이다. 사용하려면 객체를 무조건 생성하고 사용해야 한다. 생성자는 멤버함수의 규칙에서 예외이다.
class Player {
public:
	// 멤버 함수
	void Damage() {

	}
	// 멤버 변수
	int Hp;

	// 일반적인 멤버들은 사용을 위해서는 무조건 객체가 필요하다.
};
int main() {
	// 객체 만들기
	Player NewPlayer = Player();
    
	// 객체  멤버접근연산자      함수
	NewPlayer      .          Damage();
    NewPlayer.Hp = 20;
    
	return 0;
}

접근 지정자

클래스에는 접근 지정자(접근 제한자)라고 불리는 문법이 있다.
public, protected, private가 있다.
클래스는 디폴트 접근 지정자가 private이다. 명시하지 않으면 private으로 지정된다.

외부(전역+지역)에서는 public 지정자 아래 있는 변수나 함수들만 이용할 수 있다.
private이나 protected는 클래스의 같은 내부에서만 사용할 수 있다.

변수는 보통 private나 protected로 해두고 Get, Set 함수를 만들어서 접근한다. 클래스의 멤버변수를 변경할 수 있는 함수가 제한되기 때문에 디버깅에 용이하다.

다른 클래스의 내부는 외부이다.
ex) Monster 클래스에게 Player 클래스의 내부는 외부지역이다.

public

  • 접근 가능한 영역
외부자식멤버
OOO

protected

  • 접근 가능한 영역
외부자식멤버
XOO

private

클래스 내부는 기본이 private이다.

  • 접근 가능한 영역
외부자식멤버
XXO

예시

#include <iostream>

// 전역

class Monster {
    // 클래스의 내부 : 멤버

public:
    int publicint;
    void publicFunction() {

    }

protected:
    int protectedint;
    void protectedFunction() {

    }
private:
    int privateint;
    void privateFunction() {

    }
};

int main()
{
    // 지역(외부)
    Monster NewMonster = Monster();
    return 0;
}
  • publicint와 publicFunction만 접근 가능한 모습이다.
    객체가 값형이거나 레퍼런스형일때 .을 누르면 객체를 사용이 가능하다.

클래스의 기본 특성은 캡슐화이다. 객체지향의 4대 특성으로 추상화, 상속, 다형성, 캡슐화가 있다.
그 중에 추상화는 간단하게만 말하면 추상적인 인간의 생각을 문법으로 표현한다는 특징이다. 캡슐화는 객체가 모든 것을 외부에 공개할 필요는 없다는 특징이다. (상속과 다형성은 나중에)

Struct

struct Monster {
	int Hp;
	int Att;
};

1. C++에서 struct와 class의 차이

struct는 디폴트 접근 지정자가 public이다.
그 이외의 차이점이 없다. 내부 변수를 private으로 지정하면 class와의 차이점이 아예 없다.
앞으로 배우게될 모든 문법이 struct에도 그냥 적용된다.

2. C의 struct와 C++의 struct와 class의 차이

c에서의 struct는 데이터의 집합이다. 함수도 가질 수 없다.
접근 지정자가 존재하지 않는다.
이름 생략이 불가능해서 struct Monster로 부른다. typedef로 이름을 변경해서 사용한다.

클래스 초기화

클래스의 멤버변수를 초기화하는 방법이 많다. 3가지 있다.

1. 최신 C++의 방식인 리터럴 초기화

보기에 가장 직관적.

class Monster {
private:
	int Hp = 20; // 클래스 안에 있으면 멤버변수라고 부른다.
};

2. 이니셜라이저 리스트 초기화 방식

public인 변수는 배열 초기화하는 것처럼 초기화할 수 있다.
모든 변수가 public일때만 가능하다.

class Player {
public:
	int Hp;
};
int main() {
	Player NewPlayer = { 10 }; 
	return 0;
}

3. 생성자

생성자는 멤버함수의 규칙에서 예외가 된다. 리턴값도 없다.
1. 리턴값이 없다. → 자기 자신을 포함하고 있는 그 객체를 만들어서 리턴하니까.
2. 생성자는 객체 없이도 호출이 가능하다.
3. 무조건 클래스의 이름과 동일해야 한다. → 사용 편의성을 위한 규칙
4. 초기화를 위한 멤버이니셜라이저 문법이라는 것을 유일하게 사용할 수 있다.

  • 멤버 이니셜라이저 문법
    class NPC {
    public:
    	NPC() : Hp(10){ // 멤버이니셜 라이저 문법 => 생성자() : 멤버이름()
      
    	}
    private:
    	int Hp;
    };
    // 두 개일 경우
    class NPC {
    public:
    	NPC() : Hp(100), Att(10) { // 멤버이니셜 라이저 문법 => 생성자() : 멤버이름()
      
    	}
    private:
    	int Hp;
    	int Att;
    };
  • 대입과 초기화의 차이를 확실히 이해
  1. 생성자를 아무것도 만들지 않았다면 컴파일러가 기본 생성자를 만들어 버린다.
    어떠한 생성자라도 만들었다면 컴파일러가 디폴트 생성자 만들지 않는다.

오버로딩

이름이 같은데 인자가 다른 함수를 여러 개 만들 수 있는 규칙을 오버로딩이라고 한다.
이 오버로딩 규칙은 모든 규격의 함수에 사용이 가능하다. 생성자에도 오버로딩이 가능하다.

인자가 다르면 같은 이름의 함수를 여러 개 만들 수 있다.
정확히는 이름이 같은 게 아니다. 이름에 인자가 포함된다고 생각하면 된다.
아래 두 함수는 각각
void Test(void)
void Test(int) 함수이다.

void Test() {

}
void Test(int _Value) {

}
  • 여러 형태의 생성자와 초기화 방식
class Monster {
public:
// 다 생성자
	Monster() {

	}
	Monster(int _Value) {

	}
	Monster(int _Value0, int _Value1, int _Value2) {

	}
};

int main() {
	Monster NewMonster0 = Monster();
	Monster NewMonster1 = Monster(10);

	Monster NewMonster2 = 10;

	Monster NewMonster3 = { 10, 10, 10 };
	
	return 0;
}

연산자 오버로딩(연산자 겹지정)

보통 수학관련 자료형을 만들 때 많이 사용한다.
연산자 겹지정은 클래스의 함수를 만들 때 연산자 형식으로 사용할 수 있게 만드는 함수다.

  • operator=
    const Player&로 레퍼런스이기 때문에 전체 객체를 복사하기보다 8바이트만 사용해서 값을 가져올 수 있다. nullptr이나 비어있는 값이 인자로 들어오지 않는다.
    const이기 때문에 값이 바뀌어서 나오지도 않는다.
class Player {
public:
	void operator=(const Player& _Other) {
		Hp = _Other.Hp;
		Att = _Other.Att;
		Def = _Other.Def;
		Cri = _Other.Cri;
		Exp = _Other.Exp;
	}
	int Hp;
	int Att;
	int Def;
	int Cri;
	int Exp;
	// ....
};


int main() {
	Player NewPlayer0 = { 1, 1, 1, 1, 1 };
	Player NewPlayer1 = { 2, 2, 2, 2, 2 };

	NewPlayer0 = NewPlayer1;
	// NewPlayer0.operator=(NewPlayer1); 와 같다.
	return 0;
}
  • operator-, operator+
class MyInt {
public:
	MyInt() {

	}
	MyInt(int _Value) : Value(_Value) {

	}
	MyInt operator-(MyInt _Other) {
		return Value - _Other.Value;
	}
	MyInt operator+(MyInt _Other) {
		return Value + _Other.Value;
	}
private:
	int Value = 0;
};

int main() {
	MyInt Left = 20;
	MyInt Right = 20;
	MyInt Result = 0;
	Result = Left - Right; // => Result = Left.operator-(Right); 와 같음
	Result = Left + Right; // => Result = Left.operator+(Right); 와 같음
	return 0;
}

Shift + delete : 한 줄 삭제

TextRpg 클래스로 바꾸기

저번 시간에 만든 TextRpg에 클래스를 적용시킨 코드이다. Player와 Monster클래스의 내용이 거의 반복된다. 나중에 상속을 배우고 나서 개선된다.

#include <iostream>
#include <conio.h>

class Player
{
public:
	Player()
	{
	}

	void StatusRender()
	{
		int Size = printf_s("%s ", Name);
		for (int i = 0; i < 50 - Size; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
		printf_s("공격력 %d\n", Att);
		printf_s("체력 %d\n", Hp);

		for (int i = 0; i < 50; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
	}

	void DamageLogic(int _Att)
	{
		Hp -= _Att;
	}

	int GetAtt()
	{
		return Att;
	}

	void DamageRender()
	{
		printf_s("%s 가 %d의 공격력으로 공격했습니다.\n", Name, Att);
	}

protected:

private:
	char Name[100] = "Fighter";
	int Hp = 100;
	int Att = 10;
};

class Monster
{
public:
	Monster()
	{
	}

	void StatusRender()
	{
		int Size = printf_s("%s ", Name);
		for (int i = 0; i < 50 - Size; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
		printf_s("공격력 %d\n", Att);
		printf_s("체력 %d\n", Hp);

		for (int i = 0; i < 50; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
	}

	void DamageLogic(int _Att)
	{
		Hp -= _Att;
	}

	int GetAtt()
	{
		return Att;
	}

	void DamageRender()
	{
		printf_s("%s 가 %d의 공격력으로 공격했습니다.\n", Name, Att);
	}

protected:

private:
	char Name[100] = "Orc";
	int Hp = 50;
	int Att = 5;
};


int main()
{
	Player NewPlayer = Player();
	Monster NewMonster = Monster();

	while (true)
	{
		NewPlayer.StatusRender();
		NewMonster.StatusRender();
		{
			int Input = _getch();
		}

		system("cls");
		NewMonster.DamageLogic(NewPlayer.GetAtt());
		NewPlayer.StatusRender();
		NewMonster.StatusRender();
		NewPlayer.DamageRender();

		{
			int Input = _getch();
		}

		system("cls");
		NewPlayer.DamageLogic(NewMonster.GetAtt());
		NewPlayer.StatusRender();
		NewMonster.StatusRender();
		NewPlayer.DamageRender();
		NewMonster.DamageRender();

		// 아무키나 눌릴때까지 기다려 줍니다.
		{
			int Input = _getch();
		}

		// 콘솔창에 명령을 요청한다.
		system("cls");
	}
}

과제

Player와 Monster 중에 누군가 죽었다면 게임이 끝나도록 만들기.
Player와 Monster에 bool을 리턴하는 IsDeath()라는 함수를 만들어서 Hp가 0 이하가 되면 true를 리턴해서 종료되게 하기.

#include <iostream>
#include <conio.h>

class Player
{
public:
	Player()
	{
	}
	void StatusRender()
	{
		int Size = printf_s("%s ", Name);
		for (int i = 0; i < 50 - Size; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
		printf_s("공격력 %d\n", Att);
		printf_s("체력 %d\n", Hp);

		for (int i = 0; i < 50; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
	}

	void DamageLogic(int _Att)
	{
		Hp -= _Att;
	}

	int GetAtt()
	{
		return Att;
	}

	void DamageRender()
	{
		printf_s("%s 가 %d의 공격력으로 공격했습니다.\n", Name, Att);
	}

	bool IsDeath() {
		/*if (Hp <= 0) {
			return true;
		}
		else {
			return false;
		}*/
        // 같은 의미
		return Hp <= 0;
	}

private:
	char Name[100] = "Fighter";
	int Hp = 100;
	int Att = 10;
};

class Monster
{
public:
	Monster()
	{
	}

	void StatusRender()
	{
		int Size = printf_s("%s ", Name);
		for (int i = 0; i < 50 - Size; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
		printf_s("공격력 %d\n", Att);
		printf_s("체력 %d\n", Hp);

		for (int i = 0; i < 50; i++)
		{
			printf_s("-");
		}

		printf_s("\n");
	}

	void DamageLogic(int _Att)
	{
		Hp -= _Att;
	}

	int GetAtt()
	{
		return Att;
	}

	void DamageRender()
	{
		printf_s("%s 가 %d의 공격력으로 공격했습니다.\n", Name, Att);
	}

	bool IsDeath() {
		return Hp <= 0;
	}

private:
	char Name[100] = "Orc";
	int Hp = 50;
	int Att = 5;
};


int main()
{
	Player NewPlayer = Player();
	Monster NewMonster = Monster();

	while (true)
	{
		NewPlayer.StatusRender();
		NewMonster.StatusRender();
		{
			int Input = _getch();
		}

		system("cls");
		NewMonster.DamageLogic(NewPlayer.GetAtt());
		NewPlayer.StatusRender();
		NewMonster.StatusRender();
		NewPlayer.DamageRender();
        
		if (NewMonster.IsDeath())
		{
			printf_s("게임 종료\n");
			break;
		}

		{
			int Input = _getch();
		}

		system("cls");
		NewPlayer.DamageLogic(NewMonster.GetAtt());
		NewPlayer.StatusRender();
		NewMonster.StatusRender();
		NewPlayer.DamageRender();
		NewMonster.DamageRender();

		{
			int Input = _getch();
		}

		// 누군가 죽었다면 게임이 끝나야합니다.
		// Player와 몬스터에 bool을 리턴하는 IsDeath()를 만들어서
		// 누구든 Hp가 0이하가 되면 true를 리턴해서
		// 종료가 되게 하세요.
		if (NewPlayer.IsDeath())
		{
			printf_s("게임 종료\n");
			break;
		}

		// 콘솔창에 명령을 요청한다.
		system("cls");
	}
}
  • Orc가 죽자 바로 끝나는 모습.
profile
일단 시작해보자

0개의 댓글

관련 채용 정보