class A {
};
class B {
public:
void Function(){
}
};
int main() {
printf_s("A Size = %lld\n", sizeof(A)); // 1 바이트
printf_s("B Size = %lld\n", sizeof(B)); // 1 바이트
return 0;
}
A NewA[100];
A* NewA;
NewA + 2;
바이트 패딩은 메모리 정렬법이다.
class C {
public:
bool Test;
};
class D {
public:
bool Test;
int Test0;
};
class E {
public:
bool Test0;
int Test1;
bool Test2;
};
class F {
public:
bool Test0;
bool Test1;
int Test2;
};
class G {
public:
bool Test0;
bool Test1;
__int64 Test2;
};
class H {
public:
G Test0; //G 크기 16
F Test1; //F 크기 8
};
class Test0 {
bool Test;
};
class Test1 {
__int64 Test;
};
class I {
public:
Test0 Test0;
Test1 Test1;
};
class J {
public:
bool Test0;
int Test1;
__int64 Test2;
};
int main() {
printf_s("C Size = %lld\n", sizeof(C)); // 1 바이트, 비어있는 클래스의 크기가 1이라고 거기에 더해지는 것이 아니다.
printf_s("D Size = %lld\n", sizeof(D)); // 8 바이트, bool 4(1바이트인데 3바이트 패딩), int 4로 측정된 것
printf_s("E Size = %lld\n", sizeof(E)); // 12 바이트
printf_s("F Size = %lld\n", sizeof(F)); // 8 바이트
printf_s("G Size = %lld\n", sizeof(G)); // 16 바이트
printf_s("H Size = %lld\n", sizeof(H)); // 24 바이트
printf_s("I Size = %lld\n", sizeof(I)); // 16 바이트
printf_s("J Size = %lld\n", sizeof(J)); // 16 바이트
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번지
return 0;
}
class E {
public:
bool Test0;
int Test1;
bool Test2;
};
bool 1바이트
패딩 1
패딩 1
패딩 1
int 4
바
이
트
bool 1바이트
패딩 1
패딩 1
패딩 1
class F {
public:
bool Test0;
bool Test1;
int Test2;
};
bool 1바이트
bool 1바이트
패딩 1
패딩 1
int 4
바
이
트
->
사용class Player {
public:
int Att = 10;
int Hp = 100;
void Damage(int _Damage) {
Hp -= _Damage;
}
};
// 위아래 두 Damage함수는 문법적으로는 차이가 있지만 결과적으로 차이가 없다.
void Damage(Player* _this, int _Damage) {
// 방어코드
if (nullptr == _this) {
return;
}
// 클래스 포인터로 쓸때는 -> 사용
_this->Hp = _Damage;
}
__thiscall
: 함수 호출 규약 중 하나. 멤버함수에 자동으로 붙음. 이거때문에 첫번째 인자로 this 포인터 들어간다. 우리가 신경쓸필요 Xclass Player {
public:
int Att = 10;
int Hp = 100;
void __thiscall Damage(Player* const _this, int _Damage) {
_this->Hp -= _Damage;
//this->Hp -= _Damage;
//포인터 뒤 const이기 때문에 자기자신의 주소값을 바꿀 수 없다.
}
};
const 자료형 const * const
다 가능
//세 개 다 동일한 의미가 됨..
int const Value0 = 20;
const int Value1 = 20; // <- 쌤은 이것만 사용
const int const Value2 = 20;
int Test = 0;
// 위치 180번지
// 크기 4
// 형태 int
// 값 0
const int* const Value1 = &Test;
// 위치 200번지
// 크기 8
// 형태 const int* const
// 값 180번지
//180번지의 값을 100으로 바꿔라
*Value1 = 200; // 안됨 (const int)* const 괄호부분때문에 안됨
//200번지의 값을 0번지로 바꿔라
Value1 = nullptr; // 안됨 const int(* const) 괄호부분때문에 안됨
const int* Ptr
: 값을 못바꿈int* const Ptr
: 주소를 못바꿈전처리기 -> 컴파일러 -> 어셈블러 -> 링커
선언과 구현을 분리하면 inline이 되지 않는다. 보통 Get함수나 수학관련 클래스처럼 간단한 함수는 선언과 구현을 분리하지 않는다.
선언의 의미 : 이런 함수가 있을 것이니 믿고 호출해도 된다. 일단 호출해두면 나중에 구현과 연결될 것이라는 의미. (함수뿐만 아니라 다른것도됨) 선언은 많아도 상관없다.
선언자체가 없으면 "식별자를 찾을 수 없습니다" 라는 에러가 발생한다. (헤더 include했는지 확인도 필수)
구현 : 선언된 함수의 내용이 적힌 곳. 링커는 이 구현부를 선언부와 연결. 선언이 있는데 구현이 없으면 "함수에서 참조되는 확인할 수 없는 외부 기호" 에러가 발생한다.
#include <iostream>
// 전처리기 -> 컴파일러 -> 어셈블러 -> 링커
// 링커의 역할 : 선언과 구현을 이어줌.!
// 선언
// 선언의 의미 : 이런 함수가 있을 것이니 믿고 호출해도 된다. 일단 호출해두면 나중에 구현과 연결될 것이라는 의미. (함수뿐만 아니라 다른것도됨)
// 선언은 많아도 상관없다.
void FightDamage(int* _Hp, int _Att);
void FightDamage(int* _Hp, int _Att);
void FightDamage(int* _Hp, int _Att);
class Monster {
public:
int Hp;
void Damage(int _Att) {
FightDamage(&Hp, _Att);
}
};
class Player {
public:
int Hp;
void Damage(int _Att) {
FightDamage(&Hp, _Att);
}
};
// 구현
// 링커는 이 구현부를 선언부와 연결.
// 선언이 있는데 구현이 없으면 에러
void FightDamage(int* _Hp, int _Att) {
*_Hp -= _Att;
}
int main() {
Player NewPlayer = Player();
Monster NewMonster = Monster();
NewPlayer.Damage(10);
return 0;
}
::
포함, 클래스이름::함수이름
)으로 구현해줘야 한다.class Player {
public:
int Hp;
void Damage(int _Att) {
FightDamage(&Hp, _Att);
}
// 선언
void TestPlayerRender();
};
// 구현
void Player::TestPlayerRender() {
}
헤더에는 #pragma once를 꼭 넣어야 한다.
#pragma once
: 헤더 중복 방지 전처리기. #include로 같은 헤더를 몇번 반복해도 한 번만 해준다. 중복 방지 안 하면 같은 클래스가 여러개 정의되는 것이다.
int2같은 근본적인 클래스는 선언과 구현을 분리하지 않는다. (모두가 나를 사용하지만 나는 모두를 사용하지 않는 느낌의 클래스)
(내일 더 설명해야 함)
예시로 Player 클래스의 선언과 구현 분리 코드.
// 헤더에는 아래의 #pragma once를 꼭 넣어야 합니다.
#pragma once
#include "Math.h"
#include <conio.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;
};
#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;
}
int a = 0;
}
void Player::SetBulletFire(bool* _IsFire)
{
if (nullptr == _IsFire)
{
return;
}
IsFire = _IsFire;
}
//int& Ref; 에러남
//레퍼런스는 무조건 초기화를 피할 수 없다.
//즉 int&를 사용했다면 int가 하나 필요함.
int Value = 0;
int& Ref = Value;
class Test {
public:
// 리터럴 초기화는 멍청한 코드.
//int Value = 0;
//int& Ref = Value;
};
class Test {
public:
int& Ref; // 이것도 위험함
Test(int& _Ref)
:Ref(_Ref) {
}
};
int main() {
int Value = 20;
Test NewTest = Test(Value);
return 0;
}
int Value0 = 0;
int& Ref = Value0;
int Value1 = 2;
Ref = Value1;
Ref = 50; // Value0이 50으로 바뀜!