#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
}
바이트패딩 규칙
전 멤버변수 중 가장 큰 바이트 크기를 가진 기본자료형을 찾는다.
그 외엔 어떤 크기의 변수가 있어도 순서대로 가장 큰 크기의 변수 크기로 메모리를 할당하고, 다음 변수를 남은 공간에 할당할 수 있다면 남은 공간에 넣어 정렬한다.
단, 1byte 자료형을 제외하곤 정렬한 메모리 크기는 1, 2, 4, 8 단위로 떨어져야 한다.
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() // 구현
{
}
// [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
Warning
-실행…은 되지만 조금 문제가 있다
-추후를 위해 없애주면 좋다error LNK2019
-문법이 틀렸다는 뜻error C3861 : 식별자를 찾을 수 없습니다.
-함수의 선언조차 존재하지 않는다
-헤더를 확실히 include 해주었나?
-대소문자를 확실히 구별해주었나?함수에서 참조되는 확인할 수 없는 외부 기호
-선언만 있고 구현이 없다
-정말 구현해주었나?
-헤더나 라이브러리가 제대로 지정되었나?error C2011 : 형식 재정의
-이름이 같은 변수, 함수, 또는 클래스가 두 개 이상 있다
-하나만 남게 삭제하자
-#pragma once 가 헤더에 제대로 있나?error C1083 : 포함 파일을 열 수 없습니다.
-include 하려는데 경로에 해당 파일이 없다
📢기억해두면 좋은 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 ⇒ 새 항목 추가
- 프로젝트 생성 후에 소스, 헤더, 리소스 파일 필터는 삭제해도 된다
- 외부 종속성은 삭제할 수 없으니 건들지 말자