[TIL] 23-12-26

Lev·2023년 12월 26일
0

📓TIL Archive

목록 보기
11/33

클래스 (이어서)

레퍼런스

#include <iostream>

class Test {

	// 레퍼런스는 무조건 초기화해야 함
	// 즉, int&를 사용했다면 int가 하나 필요하다
	int& Ref;	// 이런 사용은 이상한 짓
	int* Ptr = nullptr;

public:
	Test(int& _Ref)
		: Ref(_Ref)	// 여기서 반드시 초기화 해야 함
	{
		// 따라서, 만약 console game 코드의 Player 클래스에서 bool& IsFire 였다면
		// 초기화를 위해 반드시 Bullet 클래스가 Player 클래스보다 먼저 선언되어야 한다
	}

};

int main()
{
	{
		int Value = 20;
		Test NewTest = Test(Value);
	}
	{
		int Value0 = 0;
		int& Ref = Value0;

		int Value1 = 2;
		Ref = Value1;

		Ref = 50;	// Value0 = 50, Value1 = 2
		// 레퍼런스는 한번 초기화되면 참조하고 있는 대상을 바꿀 수 없다
	}
}

메모리

#include <iostream>

class A
{

};

class B
{
public:
	void Function()
	{

	}
};

class C
{
public:
	bool Test;	// 1
};

class D
{
public:
	bool Test0;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// -----
	int Test1;	// 4
};

class E
{
public:
	bool Test0;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// -----
	int Test1;	// 4
	// -----
	bool Test2;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
};

class F
{
public:
	bool Test0;	// 1
	bool Test1;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// -----
	int Test2;	// 4
};

class G
{
public:
	bool Test0;	// 1
	bool Test1;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// -----
	__int64 Test2;	// 8
};

class Test0
{
public:
	bool Test;	// 1
};

class Test1
{
public:
	__int64 Test;	// 8
};

class H
{
public:
	Test0 Value0;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
    // -----
	Test1 Value1;	// 8
};

class I
{
public:
	bool Test0;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	int Test1;	// 4
	// [0][][][][1][1][1][1] => Test0 바로 뒤에 Tes1이 붙어 있는 것이 아니라, 이런 식으로 되어있다
	// -----
	__int64 Test2;	// 8
};

class J
{
public:
	bool Test0;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	int Test1;	// 4
	// -----
	bool Test2;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// Temp;	// 1
	// -----
	__int64 Test3;	// 8

int main()
{
	printf_s("A Size = %lld", sizeof(A));	// 1byte
	// 논리적으로는 0byte가 맞지만, 인간의 편의를 위해 1byte로 정의
	{
		A NewA[100];	// 크기가 0byte인 것이 100개...?!
		A* NewA;

		NewA + 2;	// 아무리 건너뛰어도 0byte 이동...?!
		// 만약 0byte를 허용하게 되면, 포인터와 배열 문법이 무너질 수 있기 때문
	}

	printf_s("B Size = %lld", sizeof(B));	// 1byte
	// 함수의 크기는 클래스의 크기에 영향을 주지 않는다

	printf_s("C Size = %lld", sizeof(C));	// 1byte
	// 비어 있는 클래스 크기 + 변수 크기 ...가 아니다
	// 선언하기 전까진 가상 메모리를 사용하기 때문

	printf_s("D Size = %lld", sizeof(D));	// 8byte
	
	printf_s("E Size = %lld", sizeof(E));	// 12byte

	printf_s("F Size = %lld", sizeof(F));	// 8byte
	{
		F NewF = F();
		__int64 Address0 = reinterpret_cast<__int64>(&NewF.Test0);	// 100번지
		__int64 Address1 = reinterpret_cast<__int64>(&NewF.Test1);	// 101번지
		__int64 Address2 = reinterpret_cast<__int64>(&NewF.Test2);	// 104번지
	}

	printf_s("G Size = %lld", sizeof(G));	// 16byte

	printf_s("Test0 Size = %lld", sizeof(Test0));	// 1byte
	printf_s("Test1 Size = %lld", sizeof(Test1));	// 8byte
	printf_s("H Size = %lld", sizeof(H));	// 16byte

	printf_s("I Size = %lld", sizeof(I));	// 16byte
	{
		I NewI = I();
		__int64 Address0 = reinterpret_cast<__int64>(&NewI.Test0);	// 100번지
		__int64 Address1 = reinterpret_cast<__int64>(&NewI.Test1);	// 104번지
		__int64 Address2 = reinterpret_cast<__int64>(&NewI.Test2);	// 108번지
	}

	printf_s("J Size = %lld", sizeof(J));	// 24byte
}

바이트패딩 규칙

  1. 전 멤버변수 중 가장 큰 바이트 크기를 가진 기본자료형을 찾는다.

  2. 그 외엔 어떤 크기의 변수가 있어도 순서대로 가장 큰 크기의 변수 크기로 메모리를 할당하고, 다음 변수를 남은 공간에 할당할 수 있다면 남은 공간에 넣어 정렬한다.

  3. 단, 1byte 자료형을 제외하곤 정렬한 메모리 크기는 1, 2, 4, 8 단위로 떨어져야 한다.

this


class Player
{
public:
	void Damage(int _Damage)	// (1)
	{
		Hp -= _Damage;
	}
	
	// 자신을 호출한 객체의 포인터를 첫번째 인자로 받는다(__thiscall)
	void __thiscall Damage(Player* const _this, int _Damage)	// (2)
	{
		this->Hp -= _Damage;
        // 클래스를 포인터로 사용할 때에는 . 이 아니라 -> 를 사용한다
		// 평소 this가 생략되어 있음을 인지하고 있어야 한다
		// 자기 클래스 안에서는 모든걸 public으로 사용할 수 있다
	}

private:
	int Att = 10;
	int Hp = 100;
};

// (1), (2), (3)은 동일한 역할을 하는 함수이다
void Damage(Player* const _this, int _Damage)	// (3)
{
	// 클래스를 포인터로받을 때에는 꼭 방어코드 작성하기
	if (nullptr == _this)
	{
		return;
	}

	// 자료형* const 형이기 때문에 참조 대상을 바꿀 수 없다
	// _this = nullptr; => 불가능

	/*
	~막간지식~
	int const Value = 20;
	const int Value = 20;
	const int const Value = 20;
	세개 다 같은 의미이다(...)
	*/

	_this->Hp -= _Damage;
}

int main()
{
	Player NewPlayer0 = Player();
	Player NewPlayer1 = Player();

	NewPlayer0.Damage(10);	// NewPlayer0.Damage(&NewPlayer0, 10);
	NewPlayer1.Damage(20);	// NewPlayer1.Damage(&NewPlayer1, 20);
}

헤더

선언과 구현

// [선언 Declaration]

void FightDamage(int* _Hp, int _Att);	// 선언
// 일단 호출하면 뒤에 구현이 있을 것이라는 공수표(...)
// 반드시 뒤에 구현이 따라와야 한다
// 선언은 몇개나 깔아둬도 상관 없다
// 링커가 선언과 구현을 이어준다 (전처리기 => 컴파일러 => 어셈블러 => 링커)

class Monster
{
public:
	int Hp;

	void /*Monster::*/Damage(/*Monster* this, */int _Att)
	{
		FightDamage(&Hp, _Att);
	}
};

class Player
{
public:
	int Hp;

	void /*Player::*/Damage(/*Player* this, */int _Att)
	{
		FightDamage(&Hp, _Att);
	}

	void TestPlayerRender();	// 선언
};


int main()
{
	Player NewPlayer;
	Monster NewMonster;

	NewPlayer.Damage(10);
	NewMonster.Damage(20);

	NewPlayer.TestPlayerRender();
}

// [구현 Realization]

void FightDamage(int* _Hp, int _Att)	// 구현
{
	*_Hp -= _Att;
}

// 클래스 함수의 선언과 구현 분리는 클래스 바깥쪽에서 가능
// 이런 경우엔 구현에 꼭 풀네임을 붙여주어야 한다!
void Player::TestPlayerRender()	// 구현
{

}

Console Game (헤더파일 분리 ver.)

// [ConsoleGame.cpp]
#include <iostream>
#include <conio.h>
#include "ConsoleScreen.h"
#include "Galaga.h"
#include "Bullet.h"
#include "Player.h"

// #pragma once => 헤더 중복 방지 전처리기
// #include란 치환과 같은 의미이므로, 중복 방지를 꼭 걸어두는 것이 좋다

// 헤더에는 선언만 놓고, cpp파일에서 구현한다
// 구현 부분에서는 꼭 함수의 풀네임을 사용해주어야 한다
// 선언과 구현이 분리되면 inline이 불가능해지므로 간단한 함수(주로 Get)는 헤더파일에 구현하기도 한다

int main()
{
	ConsoleScreen NewScreen = ConsoleScreen('*');
	Galaga NewGalaga = Galaga();

	Bullet NewBullet = Bullet({ 0,0 }, '^');
	Player NewPlayer = Player({ ScreenXHalf, ScreenYHalf }, '@');

	bool& Ref = NewBullet.GetIsFireRef();
	NewPlayer.SetBulletFire(&Ref);

	while (true)
	{
		NewScreen.ClearScreen();
		NewGalaga.GalagaWallDraw(NewScreen);

		int2 Index = NewPlayer.GetPos();
		char Ch = NewPlayer.GetRenderChar();

		NewScreen.SetPixel(Index, Ch);

		if (true == NewBullet.GetIsFireRef())
		{
			NewScreen.SetPixel(NewBullet.GetPos(), NewBullet.GetRenderChar());
		}

		NewScreen.PrintScreen();
		NewPlayer.Update();
	}
}
// [Math.h]
#pragma once

// 수학 연산자를 정의하고 있는 클래스라 선언과 구현을 분리하지 않는다
// Math.h는 다른 어떤 클래스도 사용하지 않지만, 다른 클래스는 Math.h를 사용하기 때문

class int2
{
public:
	int X = 0;
	int Y = 0;
	void operator=(const int2& _Other)
	{
		X = _Other.X;
		Y = _Other.Y;
	}

	int2 operator+(const int2& _Other)
	{
		return { X + _Other.X, Y + _Other.Y };
	}

	void operator+=(const int2& _Other)
	{
		X += _Other.X;
		Y += _Other.Y;
	}
};

const int2 Left = { -1, 0 };
const int2 Right = { 1, 0 };
const int2 Up = { 0, -1 };
const int2 Down = { 0, 1 };
// [ConsoleScreen.h]
#pragma once
#include <iostream>
#include "Math.h"

const int ScreenX = 20;
const int ScreenY = 12;
const int ScreenXHalf = ScreenX / 2;
const int ScreenYHalf = ScreenY / 2;

class ConsoleScreen
{
public:
	ConsoleScreen(char _BaseChar);

	void PrintScreen();

	void ClearScreen();

	void SetPixel(const int2& _Position, char _Char);

	void SetPixel(int _X, int _Y, char _Char);

protected:

private:
	char Arr[ScreenY][ScreenX] = { 0, };
	char BaseCh = ' ';
};
// [ConsoleScreen.cpp]
#include "ConsoleScreen.h"

ConsoleScreen::ConsoleScreen(char _BaseChar)
{
	BaseCh = _BaseChar;
}

void ConsoleScreen::PrintScreen()
{
	for (int y = 0; y < ScreenY; y++)
	{
		char* Ptr = Arr[y];
		printf_s(Ptr);
		printf_s("\n");
	}
}

void ConsoleScreen::ClearScreen()
{
	system("cls");

	for (int y = 0; y < ScreenY; y++)
	{
		for (int x = 0; x < ScreenX - 1; x++)
		{
			Arr[y][x] = BaseCh;
		}
	}
}

void ConsoleScreen::SetPixel(const int2& _Position, char _Char)
{
	SetPixel(_Position.X, _Position.Y, _Char);
}

void ConsoleScreen::SetPixel(int _X, int _Y, char _Char)
{
	Arr[_Y][_X] = _Char;
}
// [Galaga.h]
#pragma once
#include "Math.h"
#include "ConsoleScreen.h"

class Galaga
{
public:
	void GalagaWallDraw(ConsoleScreen& _Screen);
};
// [Galaga.cpp]
#include "Galaga.h"

void Galaga::GalagaWallDraw(ConsoleScreen& _Screen)
{
	{
		int2 Start0 = { 0, 0 };
		int2 Start1 = { 0, ScreenY - 1 };
		for (int i = 0; i < ScreenX - 1; i++)
		{
			_Screen.SetPixel(Start0, '#');
			_Screen.SetPixel(Start1, '#');
			Start0 += Right;
			Start1 += Right;
		}
	}
	{
		int2 Start0 = { 0, 0 };
		int2 Start1 = { ScreenX - 2, 0 };
		for (int i = 0; i < ScreenY; i++)
		{
			_Screen.SetPixel(Start0, '#');
			_Screen.SetPixel(Start1, '#');
			Start0 += Down;
			Start1 += Down;
		}
	}
}
// [Bullet.h]
#pragma once
#include "Math.h"

class Bullet
{
public:
	Bullet(const int2& _StartPos, char _RenderChar);

	bool& GetIsFireRef()
	{
		return IsFire;
	}

	inline int2 GetPos()
	{
		return Pos;
	}

	inline char GetRenderChar()
	{
		return RenderChar;
	}

private:
	bool IsFire = false;
	int2 Pos = { 0, 0 };
	char RenderChar = '@';
};
// [Bullet.cpp]
#include "Bullet.h"

Bullet::Bullet(const int2& _StartPos, char _RenderChar)
	: Pos(_StartPos), RenderChar(_RenderChar)
{

}
// [Player.h]
#pragma once
#include <conio.h>
#include "Math.h"
#include "ConsoleScreen.h"

class Player
{
public:
	Player();
	
	Player(const int2& _StartPos, char _RenderChar);

	inline int2 GetPos()
	{
		return Pos;
	}

	inline char GetRenderChar()
	{
		return RenderChar;
	}

	void Update();

	void SetBulletFire(bool* _IsFire);

private:
	int2 Pos = { 0, 0 };
	char RenderChar = '@';
	bool* IsFire = nullptr;
};
// [Player.cpp]
#include "Player.h"

Player::Player()
{
}

Player::Player(const int2& _StartPos, char _RenderChar)
	: Pos(_StartPos), RenderChar(_RenderChar)
{
}

void Player::Update()
{
	int Value = _getch();

	switch (Value)
	{
	case 'a':
	case 'A':
	{
		if ((Pos + Left).X != 0)
		{
			Pos += Left;
		}
		break;
	}
	case 'd':
	case 'D':
	{
		if ((Pos + Right).X != (ScreenX - 2))
		{
			Pos += Right;
		}
		break;
	}
	case 'w':
	case 'W':
	{
		if ((Pos + Up).Y != 0)
		{
			Pos += Up;
		}
		break;
	}
	case 's':
	case 'S':
	{
		if ((Pos + Down).Y != (ScreenY - 1))
		{
			Pos += Down;
		}
		break;
	}
	case 'q':
	case 'Q':
	{
		if (nullptr != IsFire)
		{
			*IsFire = true;
		}
		// IsFire = true;
	}
	default:
		break;
	}
}

void Player::SetBulletFire(bool* _IsFire)
{
	if (nullptr == _IsFire)
	{
		return;
	}

	IsFire = _IsFire;
}

코드 분리 완료!


📢자주 나오는 ERROR

  1. Warning
    -실행…은 되지만 조금 문제가 있다
    -추후를 위해 없애주면 좋다

  2. error LNK2019
    -문법이 틀렸다는 뜻

  3. error C3861 : 식별자를 찾을 수 없습니다.
    -함수의 선언조차 존재하지 않는다
    -헤더를 확실히 include 해주었나?
    -대소문자를 확실히 구별해주었나?

  4. 함수에서 참조되는 확인할 수 없는 외부 기호
    -선언만 있고 구현이 없다
    -정말 구현해주었나?
    -헤더나 라이브러리가 제대로 지정되었나?

  5. error C2011 : 형식 재정의
    -이름이 같은 변수, 함수, 또는 클래스가 두 개 이상 있다
    -하나만 남게 삭제하자
    -#pragma once 가 헤더에 제대로 있나?

  6. error C1083 : 포함 파일을 열 수 없습니다.
    -include 하려는데 경로에 해당 파일이 없다

  • 에러는 위에서부터 아래로 읽어야 한다
    • 위에서 발생한 에러 때문에 아래에도 에러가 발생할 수 있기 때문
  • 에러는 출력창에서 확인하자
    • 상단 메뉴 → 보기 → 출력
  • 최소 2번 이상 확인하기
    • 대소문자 확실히 보자
    • 헤더는 있는지 확인하자
  • 일부터 틀린값을 넣어 에러를 잘 체크하는지 확인하기
    • 디펜스 코드를 잘 넣었는지 확인하자
  • 자주자주 커밋하기
    • 몰아 커밋하려다 많은 부분을 날릴 수 있다
  • 안되면 취소하기
    • 여러 코드를 친 다음에, ‘이걸 좀 수정하면 되지 않을까’와 같은 희망코딩은 하지 말자
    • 다 지우고 다시 시작하자
  • 최후의 보루로 실행파일 전부 삭제하고 컴퓨터 재부팅 하기
    • VS도 때론 완벽하지 않다

📢기억해두면 좋은 VS단축키 & 정보

  • Shift + Del ⇒ 한 줄 삭제
  • Shift + 위/아래 ⇒ 한 줄 드래그
  • Shift + Home ⇒ 한 줄 안에서 커서 앞쪽으로 드래그
  • Shift + End ⇒ 한 줄 안에서 커서 뒤쪽으로 드래그
  • Shift + Ctrl + 좌/우 ⇒ 단어 단위로 드래그
  • Shift + Alt + 위/아래 ⇒ 커서 n층으로 만들기
  • Shift + Alt + 위/아래 → Ctrl + 좌/우 ⇒ n층 단어 단위로 드래그
  • Alt + 드래그 ⇒ 지정 사각형만큼 드래그
  • 범위지정 + Alt + 위/아래 ⇒ 윗줄 또는 아랫줄로 이동
  • Ctrl + 좌/우 ⇒ 단어 단위로 이동
  • Ctrl + A ⇒ 문서 코드 전체 선택
  • 변수명 위에 커서 + Ctrl + R + R ⇒ 동일한 개념을 가진 변수명을 동시에 변경
    • 항상 제대로 작동하는 것은 아니다
    • 변수가 많을수록 엄청 오래 걸린다
  • 범위지정 + Ctrl + K+ C ⇒ 주석
  • Ctrl + K + F ⇒ 자동 줄맞춤
  • 파일명 위에 커서 + Ctrl + Shift + G ⇒ 파일 열기
  • 이름 위에 커서 + F12 ⇒ 정의로 이동
  • 프로젝트 선택 + Ctrl + Shift + A ⇒ 새 항목 추가
    • 프로젝트 생성 후에 소스, 헤더, 리소스 파일 필터는 삭제해도 된다
    • 외부 종속성은 삭제할 수 없으니 건들지 말자
profile
⋆꙳⊹⋰ 𓇼⋆ 𝑻𝑰𝑳 𝑨𝑹𝑪𝑯𝑰𝑽𝑬 ⸝·⸝⋆꙳⊹⋰

0개의 댓글