/*
[전처리문]
#include "파일명" => 파일 치환
// 주석 => 주석
#define 코드 => 코드 치환 (new!)
*/
// define 코드 치환
#define TEN 10
#define TENTEN 10; // ; 를 치환...?!
#define MYTEXT = "AAAAAAA" // = 를 치환...?!
#define MYTEXT2 MYTEXT // 이중 치환...?!
// define 매크로 함수
#define PLUS(Value) Value + Value
#define MUL(Value) Value * Value
// 요즘엔 잘 사용하지 않는다
// inline이 있기도 하고, 헷갈리기 때문이다
int main()
{
int Value0 = TEN; // int Value0 = 10;
int Value1 = TENTEN // int Value1 = 10;
char Arr0[100] MYTEXT; // char Arr0[100] = "AAAAAAA"
char Arr1[100] MYTEXT2; // char Arr1[100] = "AAAAAAA"
int Value2 = PLUS(20); // int Value2 = 20 + 20;
int Value3 = MUL(PLUS(20)); // int Value3 = 20 + 20 * 20 + 20
// 말그대로의 치환이라 알아서 괄호가 쳐진다거나 하지 않는다
}
#include <Windows.h> // 윈도우 헤더
// 플랫폼 헤더 => 다른 OS에서는 사용할 수 없다
// C++은 해줄 수 없고, 윈도우만이 해줄 수 있는 것
#include <assert.h>
int main()
{
MessageBoxA(nullptr, "X의 크기가 0입니다", "치명적 에러", MB_OK);
// 메세지 창을 띄워주는 함수
// 사용자가 OK를 누를 때까지 프로그램을 멈춰준다.
assert(false);
// 프로그램을 파괴할 수 있는 함수
// 해서는 안될 사용을 저질렀을 경우, 일부러 심각한 익셉션을 일으켜 프로그램을 중지시킨다.
}
/*
[호출스택]
F5 -> 중단점에 걸림 or 치명적인 오류 발생
이 때에는 반드시 호출스택 창을 확인하자!
- 보는 법
가장 상단이 가장 마지막에 실행된 함수
아래로 갈수록 이전에 실행된 함수
그 시점의 스택 메모리 상황이 기록되어 있다
호출 스택에서 에러/중단점으로 멈춰 있는 부분 이전에 실행된 함수의 상태를 확인할 수 있다
- 여는 법
상단 메뉴 -> 디버그 -> 창 -> 호출 스택
(F5를 누르지 않았을 때에는 열 수 없고, 반드시 디버깅이 시작되어야만 열 수 있다)
*/
// [ConsoleEngine/EngineDebug.h]
#pragma once
#define MsgBoxAssert(TEXT) MessageBoxA(nullptr, TEXT, "치명적 에러", MB_OK); assert(false);
// MessageBoxA(nullptr, "콘솔스크린을 생성할 수 없습니다", "치명적 에러", MB_OK);
// assert(false);
// 본래 이렇게 두 줄을 입력해주어야 하지만, #define 전처리문을 사용하여 간략화했다
// [Galaga/Galaga.cpp]
#include <ConsoleEngine/ConsoleScreen.h>
int main()
{
ConsoleScreen NewScreen = ConsoleScreen();
NewScreen.CreateScreen(2, 2);
}
// [ConsoleEngine/ConsoleScreen.h]
#pragma once
#include "EngineDebug.h"
// 디버깅은 최고 상위개념이나 마찬가지라 헤더파일의 헤더에 포함시켜도 된다
/*
정적 할당 => 프로그램이 실행되면 변경이 불가능하다
동적 할당 => 메모리 생성 지연을 지연시킬 수 있다
둘 중에 어떤게 더 좋다 나쁘다가 아니고, 각자의 역할이 있다.
*/
class ConsoleScreen
{
public:
void CreateScreen(int _ScreenX, int _ScreenY);
private:
int Value; // 클래스 생성 => 동시에 즉시 생성
int* Ptr; // new int(); => 언제 생성될지 프로그래머가 정할 수 있음
int ScreenX = -1;
int ScreenY = -1;
// -1로 초기화를 해두는 이유는, 초기화가 되지 않았다는 것을 표현하기 위함이다
char* ScreenData = nullptr;
/*
char Arr[10][10]; => 사실 char Arr[10 * 10]; 이나 마찬가지
2차원 배열이란, 인간의 편의성을 위해 만든 것이다.
램이 구조상 1차원이기 때문에 실제로 2차원인 것은 아니다.
*/
};
// [ConsoleEngine/ConsoleScreen.cpp]
#include "ConsoleScreen.h"
#include <iostream>
#include <Windows.h>
#include <assert.h>
void ConsoleScreen::CreateScreen(/*&NewScreen => this, */int _ScreenX, int _ScreenY)
{
if (_ScreenX <= 0)
{
MsgBoxAssert("스크린 X 크기가 0이기 때문에 콘솔스크린을 생성할 수 없습니다.");
}
if (_ScreenY <= 0)
{
MsgBoxAssert("스크린 Y 크기가 0이기 때문에 콘솔스크린을 생성할 수 없습니다.");
}
/*this->*/ScreenX = _ScreenX;
/*this->*/ScreenY = _ScreenY;
// (1) 스크린에 총 몇 칸이 필요한지 계산
int ArrayCount = (ScreenX + 1) * ScreenY + 1;
// 2차원 배열이지만, 결국 1차원 배열이나 마찬가지이기 때문이다
// ScreenX + 1 을 하는 이유는, 마지막에 줄바꿈기호를 넣기 위해서이다
// (2) 동적 할당으로 새로운 스크린 생성하고 모든 칸을 0으로 초기화
ScreenData = new char[ArrayCount] {0, };
for (int y = 0; y < ScreenY; y++)
{
// (3) 각 줄 (ScreenX - 1)번째 칸까지 *로 채우기
for (int x = 0; x < ScreenX; x++)
{
int Index = y * (ScreenX + 1) + x;
ScreenData[Index] = '*';
}
// (4) 각 줄 ScreenX번째 칸 \n으로 채우기
int ReturnIndex = y * (ScreenX + 1) + ScreenX;
ScreenData[ReturnIndex] = '\n';
}
// (5) 가장 마지막칸 0으로 채우기 ((2) 단계가 있기 때문에 선택적)
ScreenData[ArrayCount - 1] = 0;
printf_s(ScreenData);
/*
문자열이 출력되는 방법
"**************\n
**************\n
**************\n
**************\n
**************\n
**************\n
**************\n"0
e.g. ScreenX = 2, ScreenY = 2인 경우
실제 모습 출력되는 모습
[*][*][\n][*][*][\n][0] **
**
*/
}
// [ConsoleEngine/ConsoleScreen.h]
#pragma once
#include "EngineDebug.h"
class ConsoleScreen
{
public:
void CreateScreen(int _ScreenX, int _ScreenY);
private:
int ScreenX = -1;
int ScreenY = -1;
char** ScreenData = nullptr; // 이중포인터로 변경
};
// [ConsoleEngine/ConsoleScreen.cpp]
#include "ConsoleScreen.h"
#include <iostream>
#include <Windows.h>
#include <assert.h>
void ConsoleScreen::CreateScreen(/*&NewScreen => this, */int _ScreenX, int _ScreenY)
{
// 함수가 실행되었다는 것을 스택영역에 그릴 때,
// 맴버함수의 경우 실행되면 내부에 this가 존재한다는 것을 기억해야 한다
if (_ScreenX <= 0)
{
MsgBoxAssert("스크린 X 크기가 0이기 때문에 콘솔스크린을 생성할 수 없습니다.");
}
if (_ScreenY <= 0)
{
MsgBoxAssert("스크린 Y 크기가 0이기 때문에 콘솔스크린을 생성할 수 없습니다.");
}
/*this->*/ScreenX = _ScreenX;
/*this->*/ScreenY = _ScreenY;
// (1) 동적 할당으로 스크린 세로 줄 생성
ScreenData = new char* [ScreenY];
for (int y = 0; y < ScreenY; y++)
{
// (2) 동적 할당으로 각 줄별 스크린 가로 생성하고 모든 칸 0으로 초기화
ScreenData[y] = new char[ScreenX + 2] {0, };
// (3) 각 줄 (ScreenX - 1)번째 칸까지 *로 채우기
for (int x = 0; x < ScreenX; x++)
{
ScreenData[y][x] = '*';
}
// (4) 각 줄 ScreenX번째 칸 \n으로 채우기
ScreenData[y][ScreenX] = '\n';
}
for (int y = 0; y < ScreenY; y++)
{
printf_s(ScreenData[y]);
}
/*
문자열이 출력되는 방법
"**************\n"0
"**************\n"0
"**************\n"0
"**************\n"0
"**************\n"0
"**************\n"0
"**************\n"0
e.g. ScreenX = 2, ScreenY = 2인 경우
실제 모습 출력되는 모습
[*][*][\n][0] **
[*][*][\n][0] **
*/
}