<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>unreality</title>
        <link>https://velog.io/</link>
        <description>⋆꙳⊹⋰ 𓇼⋆ 𝑻𝑰𝑳 𝑨𝑹𝑪𝑯𝑰𝑽𝑬 ⸝·⸝⋆꙳⊹⋰</description>
        <lastBuildDate>Tue, 24 Dec 2024 06:09:19 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>unreality</title>
            <url>https://velog.velcdn.com/images/yoon-park/profile/d9420d59-5ef4-403b-9fc4-93df4eadb3ac/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. unreality. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yoon-park" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[블로그 이주합니다.]]></title>
            <link>https://velog.io/@yoon-park/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9D%B4%EC%A3%BC%ED%95%A9%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@yoon-park/%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9D%B4%EC%A3%BC%ED%95%A9%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Tue, 24 Dec 2024 06:09:19 GMT</pubDate>
            <description><![CDATA[<p>from.
<a href="https://velog.io/@yoon-park/posts">https://velog.io/@yoon-park/posts</a></p>
<p>to.
<a href="https://unreality.tistory.com/">https://unreality.tistory.com/</a></p>
<p>마크다운으로 작성 가능한 편리함을 누릴 수 있어서 좋았던 정든 벨로그를 떠나,
좀 더 다양한 양식으로 글을 작성할 수 있고 자유롭게 미디어를 첨부할 수 있는 티스토리로 가려 한다.
작성 중이던 글들부터 이주하고, 나머지 글들은 순차적으로 옮길 예정이다.</p>
<p>마크다운이 너무 그리워지면 돌아오게 될지도 모르지만,
일단은 새 둥지에서 좀 더 자주 공부한 내용을 올릴 수 있도록 힘내봐야지!❤️‍🔥</p>
<hr>
<p>24-12-24 작성</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] Unreal Learning Kit 가지고 놀기]]></title>
            <link>https://velog.io/@yoon-park/UE5-Unreal-Learning-Kit-%EA%B0%80%EC%A7%80%EA%B3%A0-%EB%86%80%EA%B8%B0</link>
            <guid>https://velog.io/@yoon-park/UE5-Unreal-Learning-Kit-%EA%B0%80%EC%A7%80%EA%B3%A0-%EB%86%80%EA%B8%B0</guid>
            <pubDate>Tue, 17 Dec 2024 12:22:45 GMT</pubDate>
            <description><![CDATA[<p>해당 글은 아래 페이지로 이전되었습니다.
<a href="https://unreality.tistory.com/entry/UE5-Unreal-Learning-Kit-%EA%B0%80%EC%A7%80%EA%B3%A0-%EB%86%80%EA%B8%B0">https://unreality.tistory.com/entry/UE5-Unreal-Learning-Kit-%EA%B0%80%EC%A7%80%EA%B3%A0-%EB%86%80%EA%B8%B0</a>
&nbsp;
&nbsp;</p>
<hr>
<p><strong>Unreal Learning Kit</strong>
<a href="https://www.fab.com/ko/listings/1a2b1d97-bc88-4c3f-98b8-22369b5c3170">https://www.fab.com/ko/listings/1a2b1d97-bc88-4c3f-98b8-22369b5c3170</a>
오늘은 위 라이브러리를 활용해보자.</p>
<h2 id="프로젝트-세팅">프로젝트 세팅</h2>
<p><strong>과정</strong>
→ 내 라이브러리에 추가
→ 에픽게임즈 런처의 팹 라이브러리 새로고침
→ 해당 라이브러리의 프로젝트 생성 (5.1까지밖에 지원하지 않으므로 버전이 안 맞아도 그대로 생성)
→ <code>C:\Users\1\Documents\Unreal Projects\UnrealLearningKit</code> 에서 <code>.uproject</code> 파일 찾기
→ 혹시 5.5 에디터로 실행하지 않을 수 있으므로, 우클릭 → 추가옵션 표시 → Switch Unreal Engine Version → 5.5 설정해주기
→ 프로젝트 실행!</p>
<p>&nbsp;
<strong>주의할 점</strong>
셰이더 에러가 날 수 있음. 아래와 같이 Project Settings에서 SM6를 체크해 해결!
<img src="https://velog.velcdn.com/images/yoon-park/post/824e14d6-b99d-4c3e-9770-cb88093eb358/image.png" alt=""> <img src="https://velog.velcdn.com/images/yoon-park/post/c42d852f-aaa7-4523-8fdd-eec8da510e1d/image.png" alt=""> <img src="https://velog.velcdn.com/images/yoon-park/post/6a145fa8-6769-4ba9-8d0b-bfbe0f181a4d/image.png" alt=""></p>
<p>&nbsp;</p>
<h2 id="활용해보기">활용해보기</h2>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/9b8177de-d065-42d3-a15b-b6c276ea3e84/image.png" alt=""> <img src="https://velog.velcdn.com/images/yoon-park/post/c685cdef-13fc-48ae-bd19-2bf980ec8d7d/image.png" alt=""> <img src="https://velog.velcdn.com/images/yoon-park/post/05f052ce-81ce-455f-b98b-a3ea637c7588/image.png" alt=""> <img src="https://velog.velcdn.com/images/yoon-park/post/43dedda2-30cc-4f42-aded-ea06d413aba2/image.png" alt=""></p>
<p><em>대략, 주인공이 얼어붙은 땅에 나타난 황금의 유적을 찾아간다는 설정(?)</em></p>
<h3 id="트랜스폼-머티리얼-변경">트랜스폼, 머티리얼 변경</h3>
<p>주어진 키트의 내용물로 새로운 환경을 창조하기 위해, 아래의 경우들과 같이 액터들의 위치, 회전, 스케일, 그리고 머티리얼을 변경하여 활용하였다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/ca284b9f-bf1c-460b-aa61-839ed2d32bfc/image.png" alt=""> 바위덩어리에서</th>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/ba87dc0e-b3d1-4359-8935-a00ad99f618e/image.png" alt=""> 얼음덩어리로!</th>
</tr>
</thead>
</table>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/7feb3c1c-c242-468f-a94e-48be7519767d/image.png" alt=""> 돌계단에서</th>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/7ff58339-0038-4f4d-a102-0782151bf645/image.png" alt=""> 황금계단으로!</th>
</tr>
</thead>
</table>
<p>&nbsp;
트랜스폼과 머티리얼 모두, 에디터의 <code>아웃라이너</code>에서 액터를 선택한 후 열리는 <code>디테일</code> 창에서 변경해줄 수 있었다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/22440e4a-48b1-4541-babb-54d5f9d26808/image.png" alt=""> 트랜스폼 (뷰포트 상에서 각각 W, E, R 키를 누른 후, 기즈모를 움직여서도 변경 가능)</th>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/8103c57a-b63b-4c39-be81-a8f050ecbe9f/image.png" alt=""> 머티리얼(인스턴스)</th>
</tr>
</thead>
</table>
<p>머티리얼은 내부에서 수치를 조정해서 전혀 새로운 것을 만들 수도 있었다. 하지만 오늘은 머티리얼 그래프 단까지 가서 노드를 만져보지는 않았고, <code>MI_Glass</code>라는 머티리얼 인스턴스의 수치를 조금 만져보며 어떻게 변화하는지 파악해보았다.</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/c5a30b33-481b-47b2-a488-aaa6f960a2a5/image.png" alt=""> 기본적인 색을 결정</th>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/fb36dbe7-80a8-4b25-992a-43ad77c377fb/image.png" alt=""> 발광여부와 어떤 색 또는 무늬를 따라 발광할지를 결정</th>
<th><img src="https://velog.velcdn.com/images/yoon-park/post/71475835-7346-4efd-9a68-f2fc33664e73/image.png" alt=""> 표면의 거칠기 정도를 결정</th>
</tr>
</thead>
</table>
<p>처음엔 Emissive 정도가 엄청 커서 어두운 곳에서도 조명처럼 빛을 강렬하게 발하길래, 특정 texture를 따라 발광하는 옵션을 제거해서 좀 더 얼음처럼 보이게 만들었다. Roughness는 0으로 설정하여 잘 반사되도록 만들었다.</p>
<h3 id="충돌">충돌</h3>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/0a8b779e-6a9d-4a19-b528-00a3d793a648/image.gif" alt=""></p>
<p>플레이어가 아이템과 충돌하면, &#39;아이템에 깃든 힘이 활성화 된다&#39;는 느낌으로 해당 아이템의 머티리얼이 플레이어에게 입혀지도록 해보았다!</p>
<p>먼저, 플레이어와 아이템의 충돌을 어디에서 판단하는지를 찾아야 했다. 충돌이 감지되면 아이템이 소멸되는 이벤트가 발생하는 것으로 보아, 플레이어 또는 아이템의 블루프린트에 상호간의 충돌을 검사하는 부분이 존재할 것이라고 생각했다.</p>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/f2819c22-32ea-4355-ae64-daf797cecb75/image.png" alt=""> <img src="https://velog.velcdn.com/images/yoon-park/post/e6f92999-a2ac-4f26-86e7-353484e7c81e/image.png" alt=""></p>
<p>찾아 본 결과, 아이템의 부모 클래스에서 플레이어와의 충돌을 판단하고 있었다! 아이템 액터가 소멸하기 직전에 플레이어의 메시를 불러와 머티리얼을 변경하도록 노드를 추가해주었고, 의도한 바대로 잘 작동했다. 하지만, 한 가지 의문이 들어 멘토선생님에게 질문을 드렸다.</p>
<p><strong>Q) 플레이어의 메시를 변경하는 일인데, 아이템 쪽에서 처리하는 것이 옳은 방법인가요?</strong>
결론부터 말하자면, 플레이어 쪽에서 특정 아이템을 지정하여 감지하고 이벤트까지 수행하게 하는 것 보다는, 아이템이 플레이어를 감지하여 일을 수행하는 방식이 더 편리하다고 한다. 물론 절대적으로 옳은 방법이랄 것도 없고, 다양한 방법을 사용해서 같은 결과를 얻을 수 있는 것도 사실이다. 하지만 플레이어 내부의 코드에는 플레이어의 동작에 관련된 것만 작성하는 편이 객체적으로도 옳은 방식이라고 한다.</p>
<hr>
<blockquote>
<p>💭</p>
<p><em>이런저런 블록들을 배치해서 나만의 작은 공간을 만드는 것이 꽤 즐거웠다. 예술적인 감각이 아주 뛰어나다거나, 에디터를 자유자재로 다루진 못해서 엄청나게 아름다운 공간을 만든다거나 하지는 못했지만, 샌드박스 게임을 하는 느낌으로 가볍게 즐길 수 있었다.</em></p>
</blockquote>
<p>24-12-17 작성</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] 시작하기]]></title>
            <link>https://velog.io/@yoon-park/UE5-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoon-park/UE5-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 16 Dec 2024 06:45:40 GMT</pubDate>
            <description><![CDATA[<p>해당 글은 아래 페이지로 이전되었습니다.
<a href="https://unreality.tistory.com/entry/UE5-%EC%84%A4%EC%B9%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1">https://unreality.tistory.com/entry/UE5-%EC%84%A4%EC%B9%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</a>
&nbsp;
&nbsp;</p>
<hr>
<blockquote>
<p>💡 <strong>&quot;게임 엔진이란 무엇인가?&quot;</strong></p>
</blockquote>
<p><strong>게임 엔진</strong>
게임에 필요한 장치들을 쉽게 개발할 수 있도록 도와준다. 게임에 필요한 복잡한 요소들을 다루는 다양한 도구를 제공해, 게임 개발의 진입장벽을 제법 낮춰주었다. 한마디로 만능 작업실!
    - 그래픽 렌더링
    - 물리 엔진
    - 사운드 처리
    - 네트워킹 (RPC, …)
    - AI (Behavior Tree, EQS, …)</p>
<blockquote>
</blockquote>
<p>그리고…
    - 사용자들의 커뮤니티 (UDN) → 이게 생각보다 중요함!
    - 공식문서, 튜토리얼
&nbsp;
&nbsp;</p>
<blockquote>
</blockquote>
<p><strong>자체 엔진 개발은 왜 어려울까?</strong>
상용 엔진이 제공해주는 위와 같은 요소들에 대한 깊은 지식이 필요하기 때문이다. 제작하는 데에도 절대적인 시간이 필요하고, 유지, 보수 및 업데이트에도 방대한 노력이 들어간다. …따라서 적지 않은 비용을 감수하더라도 검수가 완료된 상용 엔진을 사용하고자 하는 것!</p>
<blockquote>
</blockquote>
<p>상용 엔진을 활용하여 컨텐츠를 제작하는 공부에 먼저 매진해보자 ‘-’)9
&nbsp;
&nbsp;</p>
<blockquote>
</blockquote>
<p><strong>언리얼 엔진은…</strong>
    - 고도의 그래픽, 네트워킹 기술을 활용한 게임
    - 애니메이션
    - 디지털 트윈 (자동차, 건축 등)</p>
<blockquote>
</blockquote>
<p>등의 제작에 사용할 수 있다!</p>
<h2 id="설치">설치</h2>
<ul>
<li>24-12-16 기준 <code>Unreal Engine 5.5.1</code></li>
<li><code>디버깅을 위한 편집기 기호</code> 포함해서 다운하자</li>
</ul>
<p>&nbsp;</p>
<h2 id="프로젝트-생성-실행">프로젝트 생성, 실행</h2>
<p><em>(Unreal Learning Kit에 관한 내용을 다른 글로 이전함에 따라 다른 필기를 채워 넣어야 함 _ 24-12-17)</em></p>
<hr>
<blockquote>
</blockquote>
<p>💭</p>
<blockquote>
</blockquote>
<p><em>새로운 과정에 합류하게 되었다.
포트폴리오 하나 없던 내가 일 년간의 공부로 크게 발전한 것은 사실이지만, 지원서를 제출하며 여전히 부실하게 공부한 지식이 더 많음을 뼈저리게 느꼈다.
하지만 오히려 부족한 부분이 무엇인지 잘 알게 되었기에, 이번 과정에서는 그 부분들을 집중적으로 보완하는 시간을 가질 수 있을 것 같다.
기초부터 다시 배워나가는 과정을 소홀히 하지 않고, 새로이 공부할 시간을 얻은 것에 감사하며 임해야겠다.
같은 목표를 향해 나아가는 사람들과 함께 서로의 학구열을 자극하고, 또 응원하며 발전하는 시간을 보낼 수 있도록 노력하자!</em></p>
<blockquote>
</blockquote>
<p>24-12-16 작성</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[렌더링] 소프트웨어 렌더링]]></title>
            <link>https://velog.io/@yoon-park/%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@yoon-park/%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Wed, 20 Mar 2024 08:47:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>❗보충 필요</strong>
수학 - 내적, 외적 개념
뷰행렬 - 전치 내적 사용하는 이유, 카메라의 월드행렬의 역행렬 식
투영행렬 - 직교투영행렬과 원근투영행렬 차이
(블로그 글 백업해두기)</p>
</blockquote>
<p>💁‍♀️ <em>.oO(WinAPI 환경에서 3D 렌더링을 구현해보자!)</em></p>
<p><strong>렌더링이란?</strong>
: 모니터에 그림을 그리는 것</p>
<p>모니터에 그림을 그릴 픽셀을 선별하기 위해서는, 도형이 필요하다.
따라서 점(vertex)이 중요하다!
이때 점의 위치(XYZW)를 Fvector로 표현한다.
아래의 예시에서는 삼각형을 그려주는 <code>Polygon()</code>을 사용하고, 3개의 버텍스를 입력해준다.</p>
<h2 id="행렬-없이-직접-변환">행렬 없이 직접 변환</h2>
<p>먼저 카메라와 모니터를 고려하지 않고, 단순히 윈도우 창에 메쉬를 그리는 것부터 구현해보자!</p>
<h3 id="2차원에-그리기">2차원에 그리기</h3>
<pre><code class="language-cpp">FTransform Transform;
float Speed = 100.0f;
float RotSpeed = 360.0f;
FVector DirNormal;
FVector DirRNormal;

void USoftRenderingLevel::Render(float _DeltaTime)
{
    ULevel::Render(_DeltaTime);

    // 윈도우 창 백버퍼이미지의 핸들 가지고 오기
    std::shared_ptr&lt;UWindowImage&gt; Image = GEngine-&gt;MainWindow.GetBackBufferImage();
    HDC DC = Image-&gt;GetImageDC();

    // 키 입력에 따른 버텍스 위치 변환
    if (UEngineInput::IsPress(&#39;Q&#39;))    // 회전(반시계)
    {
        Transform.AddRotation(FVector(0.0f, 0.0f, -1.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;E&#39;))    // 회전(시계)
    {
        Transform.AddRotation(FVector(0.0f, 0.0f, 1.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;W&#39;))    // 이동(앞)
    {
        Transform.AddPosition(DirNormal * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;S&#39;))    // 이동(뒤)
    {
        Transform.AddPosition(-DirNormal * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;A&#39;))    // 이동(왼쪽)
    {
        Transform.AddPosition(-DirRNormal * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;D&#39;))    // 이동(오른쪽)
    {
        Transform.AddPosition(DirRNormal * Speed * _DeltaTime);
    }

    // 원점을 기준으로 그린 도형 =&gt; FBX 파일, 메쉬 파일
    std::vector&lt;std::vector&lt;FVector&gt;&gt; Mesh =
    {
        { {0.0, -50.0f}, {-50.0f, 50.0f}, {50.0f, 50.0f} },
        { { 0.0, -45.0f }, { 0.0, -55.0f }, { 0.0, -45.0f } },
    };

    // 변환된 위치의 메쉬
    std::vector&lt;std::vector&lt;FVector&gt;&gt; RenderingMesh = Mesh;

    // &lt;변환1. 회전&gt;
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            RenderingMesh[TriCount][VertexCount].RotationZToDeg(Transform.GetRotation().Z);
        }
    }

    // 회전한 위치를 기준으로 상하좌우 방향 결정
    DirNormal = RenderingMesh[0][0].Normalize2DReturn();
    DirRNormal= float4::VectorRotationZToDeg(DirNormal, 90.0f);
    /*
    사실 위 경우는 이등변삼각형이라 이 방법으로 한번 구현해봤을 뿐,
    다른 형태의 메쉬이고 3차원일 경우에는 다른 방법을 사용해야 한다.
    */

    // &lt;변환2. 이동&gt;
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            RenderingMesh[TriCount][VertexCount] += Transform.GetPosition();
        }
    }
    /*
    반드시 회전 이후에 이동이 수행되어야 한다.
    이동이 먼저 이뤄질 경우, 이동된 위치에서 원점을 기준으로 공전이 이루어지기 때문이다.
    */

    // 변환된 버텍스를 픽셀로 전환
    std::vector &lt; std::vector&lt;POINT&gt;&gt; WinApiPoints;
    WinApiPoints.resize(RenderingMesh.size());
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        WinApiPoints[TriCount].resize(RenderingMesh[TriCount].size());
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            WinApiPoints[TriCount][VertexCount] = RenderingMesh[TriCount][VertexCount].ConvertToWinApiPOINT();
        }
    }

    // 화면에 메쉬 그리기
    for (size_t TriCount = 0; TriCount &lt; WinApiPoints.size(); TriCount++)
    {
        POINT&amp; StartPoint = WinApiPoints[TriCount][0];
        Polygon(DC, &amp;StartPoint, 3);
    }
}</code></pre>
<h3 id="3차원에-그리기-1">3차원에 그리기 (1)</h3>
<p>변환 순서를 반드시 지켜야 한다!
<strong>크기 -&gt; 자전 -&gt; 이동 -&gt; 공전 -&gt; 부모</strong></p>
<pre><code class="language-cpp">FTransform Transform;
float Speed = 100.0f;
float RotSpeed = 360.0f;

void USoftRenderingLevel::BeginPlay()
{
    ULevel::BeginPlay();

    Transform.SetScale({ 100, 100, 100 });    // 크기 정보
}

void USoftRenderingLevel::Render(float _DeltaTime)
{
    ULevel::Render(_DeltaTime);

    std::shared_ptr&lt;UWindowImage&gt; Image = GEngine-&gt;MainWindow.GetBackBufferImage();
    HDC DC = Image-&gt;GetImageDC();

    // 원점을 기준으로 그리는 메쉬
    std::vector&lt;std::vector&lt;FVector&gt;&gt; Mesh;

    Mesh.resize(4);
    // 도형의 뒷면
    Mesh[0] = { {-0.5f, -0.5f, -0.5f }, {-0.5f, 0.5f, -0.5f}, {0.5f, 0.5f, -0.5f} };
    Mesh[1] = { {-0.5f, -0.5f, -0.5f}, {0.5f, -0.5f, -0.5f}, {0.5f, 0.5f, -0.5f} };
    // 도형의 앞면
    Mesh[2] = { FVector::VectorRotationYToDeg(Mesh[0][0], 180.0f), FVector::VectorRotationYToDeg(Mesh[0][1], 180.0f), FVector::VectorRotationYToDeg(Mesh[0][2], 180.0f) };
    Mesh[3] = { FVector::VectorRotationYToDeg(Mesh[1][0], 180.0f), FVector::VectorRotationYToDeg(Mesh[1][1], 180.0f), FVector::VectorRotationYToDeg(Mesh[1][2], 180.0f) };

    // 키입력에 따른 변환값
    if (UEngineInput::IsPress(VK_NUMPAD8))    // 회전(Z축)
    {
        Transform.AddRotation(FVector(0.0f, 0.0f, 1.0f) * RotSpeed* _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD7))    // 회전(Z축)
    {
        Transform.AddRotation(FVector(0.0f, 0.0f, -1.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD5))    // 회전(Y축)
    {
        Transform.AddRotation(FVector(0.0f, 1.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD4))    // 회전(Y축)
    {
        Transform.AddRotation(FVector(0.0f, -1.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD2))    // 회전(X축)
    {
        Transform.AddRotation(FVector(1.0f, 0.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD1))    // 회전(X축)
    {
        Transform.AddRotation(FVector(-1.0f, 0.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;W&#39;))    // 이동(위)
    {
        Transform.AddPosition(FVector::Up * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;S&#39;))    // 이동(아래)
    {
        Transform.AddPosition(FVector::Down * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;A&#39;))    // 이동(왼쪽)
    {
        Transform.AddPosition(FVector::Left * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;D&#39;))    // 이동(오른쪽)
    {
        Transform.AddPosition(FVector::Right * Speed * _DeltaTime);
    }

    // 변환될 메쉬
    std::vector&lt;std::vector&lt;FVector&gt;&gt; RenderingMesh = Mesh;

    FMatrix Scale;
    Scale.Scale(Transform.GetScale());

    // &lt;변환1. 크기&gt;
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            RenderingMesh[TriCount][VertexCount] *= Scale;
        }
    }

    // &lt;변환2. 회전&gt;
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            RenderingMesh[TriCount][VertexCount].RotationXToDeg(Transform.GetRotation().X);
            RenderingMesh[TriCount][VertexCount].RotationYToDeg(Transform.GetRotation().Y);
            RenderingMesh[TriCount][VertexCount].RotationZToDeg(Transform.GetRotation().Z);
        }
    }

    // &lt;변환3. 이동&gt;
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            RenderingMesh[TriCount][VertexCount] += Transform.GetPosition();
        }
    }

    // 변환된 버텍스를 화면상의 픽셀로 전환
    std::vector &lt; std::vector&lt;POINT&gt;&gt; WinApiPoints;
    WinApiPoints.resize(RenderingMesh.size());
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        WinApiPoints[TriCount].resize(RenderingMesh[TriCount].size());
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            WinApiPoints[TriCount][VertexCount] = RenderingMesh[TriCount][VertexCount].ConvertToWinApiPOINT();
        }
    }

    // 화면에 메쉬 그리기
    for (size_t TriCount = 0; TriCount &lt; WinApiPoints.size(); TriCount++)
    {
        POINT&amp; StartPoint = WinApiPoints[TriCount][0];
        Polygon(DC, &amp;StartPoint, 3);
    }
}</code></pre>
<h2 id="3차원-변환행렬-적용">3차원 변환행렬 적용</h2>
<p><strong><em>로컬 스페이스 → 월드 스페이스 → 뷰 스페이스 → 투영 스페이스 → 모니터</em></strong></p>
<pre><code>메쉬 * 월드행렬 * 뷰행렬 * 투영행렬 * 뷰포트 =&gt; 모니터에 출력!

- 로컬 스페이스 : 물체의 메쉬만 존재하는 상태
- 월드 스페이스 : 각각의 물체의 메쉬마다 월드정보를 적용하여 모두 같은 월드에 존재하는 상태
- 뷰 스페이스 : 플레이어의 뒤에 놓인 카메라가 세상의 원점에 위치하고 정면을 바라보도록 모든 물체를 정렬한 상태
- 투영 스페이스 : 카메라의 시야 범위 속 물체들을 3D 공간에서 2D 화면으로 투영한 상태
- 모니터 : 해상도에 따른 비율에 맞춰 메쉬가 그려짐</code></pre><p><em>크기 → 자전 → 이동 → 공전 → 부모</em>
위 순서로 메쉬를 변환해주다보면, 모든 버텍스에 같은 과정을 수행해주게 된다.
따라서 모든 벡터를 일일이 변환시키지 않고, 행렬을 이용하면 편하다.
연산하는 메소드를 호출하는 횟수도 줄기 때문에 상대적으로 효율적이다. 
이때 행렬은 차원이나 마찬가지이다.
공간을 정의하는 크기, 회전, 위치 등의 정보를 포함하고 있기 때문이다.
<strong>4 x 4 행렬에 월드의 공간 정보를 기록하여 버텍스의 위치 정보에 곱하자!</strong></p>
<h3 id="월드-행렬">월드 행렬</h3>
<p>크기, 회전, 이동에 관한 월드 정보를 가진 행렬이다.</p>
<pre><code class="language-cpp">/*
[크기 행렬]
크기X        0            0            0
0            크기Y        0            0
0            0            크기Z        0
0            0            0            1
*/

void Scale(float4 _Value)
{
    Identity();
    Arr2D[0][0] = _Value.X;
    Arr2D[1][1] = _Value.Y;
    Arr2D[2][2] = _Value.Z;
    Arr2D[3][3] = 1.0f;
}

/*
--------------------------------------------------------------------------------
[회전 행렬]
            X좌표값        Y좌표값        Z좌표값
X축 =&gt;        회전            회전            회전            0
Y축 =&gt;        회전            회전            회전            0
Z축 =&gt;        회전            회전            회전            0
            0            0            0            1

1. X축 회전
1        0        0        0
0        cos        sin        0
0        -sin    cos        0
0        0        0        1

2. Y축 회전
cos        0        -sin    0
0        1        0        0
sin        0        cos        0
0        0        0        1

3. Z축 회전
cos        sin        0        0
-sin    cos        0        0
0        0        1        0
0        0        0        1

- 코시시코를 기억하자!
- X축 회전이라면, X축을 기준으로는 아무 영향도 받지 않고, X좌표값도 변하지 않는다.
- 회전 정보는 크기 정보에 해당되는 범위도 포함하고 있다.
  회전 변환 시 눈에 보이는 크기도 함께 변환되기 때문이다.
  따라서 반드시 크기 -&gt; 회전 변환 순서를 지켜줘야 한다.
- 사실 이 변환식은 윈도우 좌표계에 해당하는 것이고, 다른 좌표계를 사용하면 식이 조금 달라진다.
*/

float4x4 RotationDeg(const float4 _Value)
{
    Identity();
    float4x4 X;
    X.RotationXDeg(_Value.X);
    float4x4 Y;
    Y.RotationYDeg(_Value.Y);
    float4x4 Z;
    Z.RotationZDeg(_Value.Z);
    return X * Y * Z;
}

void RotationXDeg(float _Angle)
{
    RotationXRad(_Angle * UEngineMath::DToR);
}

void RotationXRad(float _Angle)
{
    Identity();
    Arr2D[1][1] = cosf(_Angle);
    Arr2D[1][2] = sinf(_Angle);
    Arr2D[2][1] = -sinf(_Angle);
    Arr2D[2][2] = cosf(_Angle);
}

void RotationYDeg(float _Angle)
{
    RotationYRad(_Angle * UEngineMath::DToR);
}

void RotationYRad(float _Angle)
{
    Identity();
    Arr2D[0][0] = cosf(_Angle);
    Arr2D[0][2] = -sinf(_Angle);
    Arr2D[2][0] = sinf(_Angle);
    Arr2D[2][2] = cosf(_Angle);
}

void RotationZDeg(float _Angle)
{
    RotationZRad(_Angle * UEngineMath::DToR);
}

void RotationZRad(float _Angle)
{
    Identity();
    Arr2D[0][0] = cosf(_Angle);
    Arr2D[0][1] = sinf(_Angle);
    Arr2D[1][0] = -sinf(_Angle);
    Arr2D[1][1] = cosf(_Angle);
}

/*
--------------------------------------------------------------------------------
[이동 행렬]
1            0            0            0
0            1            0            0
0            0            1            0
이동X        이동Y        이동Z        1

- 버텍스의 W값이 0이 아니어야만 이동 값을 더해줄 수 있다.
- 보통은 1을 넣지만, W값을 바꾸어 위치 적용 %를 변경해줄 수 있다.
*/

void Position(float4 _Value)
{
    Identity();
    Arr2D[3][0] = _Value.X;
    Arr2D[3][1] = _Value.Y;
    Arr2D[3][2] = _Value.Z;
}</code></pre>
<h3 id="뷰-행렬">뷰 행렬</h3>
<p>월드에는 카메라가 실제로 존재하지 않기 때문에,
카메라를 제외한 모든 물체가 함께 움직여 마치 카메라가 움직이는 것처럼 보이게 한다.
이를 위해 카메라를 원점에 위치시키고 정면(Z축 양의 방향)을 바라보도록 월드를 정렬하는 과정은,
카메라를 제외한 나머지 모든 물체들을 카메라의 로컬 스페이스로 데려가는 것이라고 생각해도 무방하다.
따라서 모든 물체에 카메라의 월드행렬의 역행렬을 적용시켜야 한다.</p>
<pre><code class="language-cpp">/*
&lt;회전행렬을 구하는 방법&gt;
(1) 3축을 기준으로 회전한 각도를 이용하여 계산하는 방법
(2) 회전행렬의 두 벡터를 알고 있을 경우, 세 벡터가 서로 직교한다는 점을 이용하여 외적으로 계산하는 방법
-&gt; 아래에선 2번 방법을 사용한다.
*/

float4x4 View(const float4 _EyePos, const float4 _EyeDir, const float4 _EyeUp)
{
    /*
    _EyePos    =&gt; 위치
    _EyeDir    =&gt; 바라보는 방향
    _EyeUp    =&gt; 바라보는 방향과 수직인 벡터

    여기서 회전행렬의 두 벡터를 입력받기 때문에, 외적을 통해 나머지 한 벡터를 알아내어
    카메라의 회전벡터를 구할 수 있다.
    */

    float4x4 View;

    FVector Up = _EyeUp.Normalize3DReturn();
    FVector Forward = _EyeDir.Normalize3DReturn();
    FVector Right = FVector::Cross3D(Up, Forward);    // 외적
    Up.W = 0.0f;
    Forward.W = 0.0f;
    Right.W = 0.0f;

    // 회전 정보
    View.ArrVector[0] = Right;
    View.ArrVector[1] = Up;
    View.ArrVector[2] = Forward;
    View.Transpose();

    float4 NegEyePos = -_EyePos;
    NegEyePos.W = 1.0f;

    // 이동 정보
    View.ArrVector[3].X = float4::DotProduct3D(Right, NegEyePos);
    View.ArrVector[3].Y = float4::DotProduct3D(Up, NegEyePos);
    View.ArrVector[3].Z = float4::DotProduct3D(Forward, NegEyePos);

    *this = View;

    return View;
}</code></pre>
<h3 id="투영-행렬">투영 행렬</h3>
<pre><code class="language-cpp">// (1) 직교투영행렬
void OrthographicLH(float _Width, float _Height, float _Near, float _Far)
{
    // _Near : 가장 가까이 보이는 평면
    // _Far : 가장 멀리 보이는 평면

    Identity();

    float fRange = 1.0f / (_Far - _Near);

    // 크기 정보
    Arr2D[0][0] = 2.0f / _Width;
    Arr2D[1][1] = 2.0f / _Height;
    Arr2D[2][2] = fRange;

    // 이동 정보
    Arr2D[3][2] = -fRange * _Near;    // Near 앞쪽은 출력될 필요가 없기 때문

}

// (2) 원근투영행렬
/* TBC */</code></pre>
<h3 id="뷰포트">뷰포트</h3>
<pre><code class="language-cpp">void ViewPort(float _Width, float _Height, float _Left, float _Right, float _ZMin, float _ZMax)
{
    Identity();

    // 크기 정보 
    Arr2D[0][0] = _Width * 0.5f;
    Arr2D[1][1] = -_Height * 0.5f;
    Arr2D[2][2] = _ZMax != 0.0f ? 1.0f : _ZMin / _ZMax;

    // 이동 정보
    Arr2D[3][0] = Arr2D[0][0] + _Left;
    Arr2D[3][1] = -Arr2D[1][1] + _Right;
    Arr2D[3][2] = _ZMax != 0.0f ? 0.0f : _ZMin / _ZMax;
    Arr2D[3][3] = 1.0f;
}</code></pre>
<h3 id="3차원에-그리기-2">3차원에 그리기 (2)</h3>
<pre><code class="language-cpp">FTransform PlayerTransform;    // 플레이어에게 적용되는 월드행렬
FTransform CameraTransform;    // 카메라에게 적용되는 월드행렬

float Speed = 500.0f;
float RotSpeed = 360.0f;

void USoftRenderingLevel::BeginPlay()
{
    ULevel::BeginPlay();

    PlayerTransform.SetScale({100, 100, 100});
    CameraTransform.SetPosition({ 0, 0, -1000 });
}

void USoftRenderingLevel::Tick(float _DeltaTime)
{
    ULevel::Tick(_DeltaTime);

    PlayerControl(_DeltaTime);
    CameraControl(_DeltaTime);
}

void USoftRenderingLevel::PlayerControl(float _DeltaTime)
{
    // 키입력에 따른 플레이어 회전 변화
    if (UEngineInput::IsPress(VK_NUMPAD8))    // 회전(Z축)
    {
        PlayerTransform.AddRotation(FVector(0.0f, 0.0f, 1.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD7))    // 회전(Z축)
    {
        PlayerTransform.AddRotation(FVector(0.0f, 0.0f, -1.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD5))    // 회전(Y축)
    {
        PlayerTransform.AddRotation(FVector(0.0f, 1.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD4))    // 회전(Y축)
    {
        PlayerTransform.AddRotation(FVector(0.0f, -1.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD2))    // 회전(X축)
    {
        PlayerTransform.AddRotation(FVector(1.0f, 0.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_NUMPAD1))    // 회전(X축)
    {
        PlayerTransform.AddRotation(FVector(-1.0f, 0.0f, 0.0f) * RotSpeed * _DeltaTime);
    }

    FMatrix RotationMat, RotationXMat, RotationYMat, RotationZMat;
    RotationXMat.RotationXDeg(PlayerTransform.GetRotation().X);
    RotationYMat.RotationYDeg(PlayerTransform.GetRotation().Y);
    RotationZMat.RotationZDeg(PlayerTransform.GetRotation().Z);
    RotationMat = RotationXMat * RotationYMat * RotationZMat;

    // 키입력에 따른 플레이어 이동 변화
    if (UEngineInput::IsPress(&#39;W&#39;))    // 이동(위)
    {
        PlayerTransform.AddPosition(RotationMat.UpVector() * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;S&#39;))    // 이동(아래)
    {
        PlayerTransform.AddPosition(RotationMat.DownVector() * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;A&#39;))    // 이동(왼쪽)
    {
        PlayerTransform.AddPosition(RotationMat.LeftVector() * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;D&#39;))    // 이동(오른쪽)
    {
        PlayerTransform.AddPosition(RotationMat.RightVector() * Speed * _DeltaTime);
    }
}

void USoftRenderingLevel::CameraControl(float _DeltaTime)
{
    FMatrix RotationMat, RotationXMat, RotationYMat, RotationZMat;
    RotationXMat.RotationXDeg(CameraTransform.GetRotation().X);
    RotationYMat.RotationYDeg(CameraTransform.GetRotation().Y);
    RotationZMat.RotationZDeg(CameraTransform.GetRotation().Z);
    RotationMat = RotationXMat * RotationYMat * RotationZMat;

    // 키입력에 따른 카메라 이동 변화
    if (UEngineInput::IsPress(VK_LEFT))    // 이동(왼쪽)
    {
        CameraTransform.AddPosition(-RotationMat.RightVector() * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_RIGHT))    // 이동(오른쪽)
    {
        CameraTransform.AddPosition(RotationMat.RightVector() * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_UP))    // 이동(위)
    {
        CameraTransform.AddPosition(RotationMat.UpVector() * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_DOWN))    // 이동(아래)
    {
        CameraTransform.AddPosition(RotationMat.UpVector() * Speed * _DeltaTime);
    }

    if (UEngineInput::IsPress(&#39;O&#39;))    // 회전(Y축)
    {
        CameraTransform.AddRotation({0.0f, 360.0f * _DeltaTime, 0.0f});
    }

    if (UEngineInput::IsPress(&#39;P&#39;))    // 회전(Y축)
    {
        CameraTransform.AddRotation({ 0.0f, 360.0f * _DeltaTime, 0.0f });
    }
}

void USoftRenderingLevel::Render(float _DeltaTime)
{
    ULevel::Render(_DeltaTime);

    std::shared_ptr&lt;UWindowImage&gt; Image = GEngine-&gt;MainWindow.GetBackBufferImage();
    HDC DC = Image-&gt;GetImageDC();

    // 로컬 스페이스
    std::vector&lt;std::vector&lt;FVector&gt;&gt; Mesh;

    Mesh.resize(4);
    Mesh[0] = { {-0.5f, -0.5f, -0.5f }, {-0.5f, 0.5f, -0.5f}, {0.5f, 0.5f, -0.5f} };
    Mesh[1] = { {-0.5f, -0.5f, -0.5f}, {0.5f, -0.5f, -0.5f}, {0.5f, 0.5f, -0.5f} };
    Mesh[2] = { FVector::VectorRotationYToDeg(Mesh[0][0], 180.0f), FVector::VectorRotationYToDeg(Mesh[0][1], 180.0f), FVector::VectorRotationYToDeg(Mesh[0][2], 180.0f) };
    Mesh[3] = { FVector::VectorRotationYToDeg(Mesh[1][0], 180.0f), FVector::VectorRotationYToDeg(Mesh[1][1], 180.0f), FVector::VectorRotationYToDeg(Mesh[1][2], 180.0f) };

    std::vector&lt;std::vector&lt;FVector&gt;&gt; RenderingMesh = Mesh;

    // [월드 행렬]
    FMatrix World;
    {
        // 플레이어 크기 정보
        FMatrix ScaleMat;
        ScaleMat.Scale(PlayerTransform.GetScale());
        // 플레이어 회전(자전) 정보
        FMatrix RotationMat, RotationXMat, RotationYMat, RotationZMat;
        RotationXMat.RotationXDeg(PlayerTransform.GetRotation().X);
        RotationYMat.RotationYDeg(PlayerTransform.GetRotation().Y);
        RotationZMat.RotationZDeg(PlayerTransform.GetRotation().Z);
        RotationMat = RotationXMat * RotationYMat * RotationZMat;
        // 플레이어 이동 정보
        FMatrix PositionMat;
        PositionMat.Position(PlayerTransform.GetPosition());
        World = ScaleMat * RotationMat * PositionMat;    // 크기 * 자전 * 이동
    }

    // [뷰 행렬]
    FMatrix View;
    {
        // 카메라 회전(자전) 정보
        FMatrix CameraRotationMat, CameraRotationXMat, CameraRotationYMat, CameraRotationZMat;
        CameraRotationXMat.RotationXDeg(CameraTransform.GetRotation().X);
        CameraRotationYMat.RotationYDeg(CameraTransform.GetRotation().Y);
        CameraRotationZMat.RotationZDeg(CameraTransform.GetRotation().Z);
        CameraRotationMat = CameraRotationXMat * CameraRotationYMat * CameraRotationZMat;

        View.View(CameraTransform.GetPosition(), CameraRotationMat.ForwardVector(), CameraRotationMat.UpVector());
    }

    // [투영 행렬]
    FMatrix Projection;
    {
        Projection.OrthographicLH(1280.0f, 640.0f, 1.0f, 1000.0f);
    }

    // [뷰포트]
    FMatrix ViewPort;
    {
        ViewPort.ViewPort(1280.0f, 640.0f, 0.0f, 0.0f, 0.0f, 1.0f);
    }

    // 변환!
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            // &lt;변환1. (로컬 스페이스 * 월드 행렬) -&gt; 월드 스페이스&gt;
            RenderingMesh[TriCount][VertexCount] *= World;
            // &lt;변환2. (월드 스페이스 * 뷰 행렬) -&gt; 뷰 스페이스&gt;
            RenderingMesh[TriCount][VertexCount] *= View;
            // &lt;변환3. (뷰 스페이스 * 투영 행렬) -&gt; 투영 스페이스&gt;
            RenderingMesh[TriCount][VertexCount] *= Projection;
        }
    }

    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            // &lt;변환4. (투영 스페이스 * 뷰포트) -&gt; 변환 완료&gt;
            RenderingMesh[TriCount][VertexCount] *= ViewPort;
        }
    }

    // 변환 완료된 메쉬의 버텍스를 픽셀로 전환
    std::vector &lt; std::vector&lt;POINT&gt;&gt; WinApiPoints;
    WinApiPoints.resize(RenderingMesh.size());
    for (size_t TriCount = 0; TriCount &lt; RenderingMesh.size(); TriCount++)
    {
        WinApiPoints[TriCount].resize(RenderingMesh[TriCount].size());
        for (size_t VertexCount = 0; VertexCount &lt; RenderingMesh[TriCount].size(); VertexCount++)
        {
            WinApiPoints[TriCount][VertexCount] = RenderingMesh[TriCount][VertexCount].ConvertToWinApiPOINT();
        }
    }

    // 화면에 메쉬 그리기
    for (size_t TriCount = 0; TriCount &lt; WinApiPoints.size(); TriCount++)
    {
        POINT&amp; StartPoint = WinApiPoints[TriCount][0];
        Polygon(DC, &amp;StartPoint, 3);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[수학] 행렬]]></title>
            <link>https://velog.io/@yoon-park/%EC%88%98%ED%95%99-%ED%96%89%EB%A0%AC</link>
            <guid>https://velog.io/@yoon-park/%EC%88%98%ED%95%99-%ED%96%89%EB%A0%AC</guid>
            <pubDate>Wed, 20 Mar 2024 08:31:09 GMT</pubDate>
            <description><![CDATA[<h3 id="4-x-4-행렬">4 x 4 행렬</h3>
<pre><code class="language-cpp">class float4x4
{
public:
    union
    {
        struct
        {
            float v00; 
            float v01; 
            float v02;
            float v03;

            float v10; 
            float v11; 
            float v12;
            float v13;

            float v20; 
            float v21; 
            float v22;
            float v23;

            float v30; 
            float v31; 
            float v32;
            float v33;
        };

        float4 ArrVector[4];
        float Arr1D[16] = { };
        float Arr2D[4][4];
    };

    float4x4()
    {
        Identity();    // 무조건 항등행렬로 생성
    }

    float4x4&amp; operator=(const float4x4&amp; _Value)
    {
        memcpy_s(Arr1D, sizeof(float) * 16, _Value.Arr1D, sizeof(float) * 16);
        // _Value.Arr1D에 존재하는 내용을 (sizeof(float) * 16) 크기만큼,
        // Arr1D의 (sizeof(float) * 16) 크기에 복사하라는 뜻

        return *this;
    }

    float4x4 operator*(const float4x4&amp; _Value)
    {
        return ::operator*(*this, _Value);
    }

    // 3차원 변환행렬로써 사용할 경우...
    float4 LeftVector()        // X벡터
    {
        return -ArrVector[0].Normalize2DReturn();
    }

    float4 RightVector()    // X벡터
    {
        return ArrVector[0].Normalize2DReturn();
    }

    float4 UpVector()        // Y벡터
    {
        return ArrVector[1].Normalize2DReturn();
    }

    float4 DownVector()        // Y벡터
    {
        return -ArrVector[1].Normalize2DReturn();
    }

    float4 ForwardVector()    // Z벡터
    {
        return ArrVector[2].Normalize2DReturn();
    }

    float4 BackVector()        // Z벡터
    {
        return -ArrVector[2].Normalize2DReturn();
    }
};

using FMatrix = float4x4;</code></pre>
<h3 id="곱셈">곱셈</h3>
<p>행렬 1 ⇒ Y1 x X1
행렬 2 ⇒ Y2 x X2
(행렬 1 * 행렬 2) 연산을 수행할 경우…</p>
<ul>
<li>X1 = Y2 이어야 곱셈이 성립한다.</li>
<li>연산 결과는 Y1 x X2 의 크기를 갖는다.</li>
<li>교환법칙은 성립하지 않는다.</li>
</ul>
<pre><code class="language-cpp">/*
(1 x 4) * (4 x 4) 의 경우
a00 a01 a02 a03        x    b00 b01 b02 b03        =    ab00 ab01 ab02 ab03
                        b10 b11 b12 b13
                        b20 b21 b22 b23
                        b30 b31 b32 b33

ab00 = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30
ab01 = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31
ab02 = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32
ab03 = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33
*/

float4 float4::operator*(const float4x4&amp; _Other) const
{
    float4 Result;
    Result.X = (Arr2D[0][0] * _Other.Arr2D[0][0]) + (Arr2D[0][1] * _Other.Arr2D[1][0]) + (Arr2D[0][2] * _Other.Arr2D[2][0]) + (Arr2D[0][3] * _Other.Arr2D[3][0]);
    Result.Y = (Arr2D[0][0] * _Other.Arr2D[0][1]) + (Arr2D[0][1] * _Other.Arr2D[1][1]) + (Arr2D[0][2] * _Other.Arr2D[2][1]) + (Arr2D[0][3] * _Other.Arr2D[3][1]);
    Result.Z = (Arr2D[0][0] * _Other.Arr2D[0][2]) + (Arr2D[0][1] * _Other.Arr2D[1][2]) + (Arr2D[0][2] * _Other.Arr2D[2][2]) + (Arr2D[0][3] * _Other.Arr2D[3][2]);
    Result.W = (Arr2D[0][0] * _Other.Arr2D[0][3]) + (Arr2D[0][1] * _Other.Arr2D[1][3]) + (Arr2D[0][2] * _Other.Arr2D[2][3]) + (Arr2D[0][3] * _Other.Arr2D[3][3]);
    return Result;
}

float4&amp; float4::operator*=(const class float4x4&amp; _Other) 
{
    float4 Result;
    Result.X = (Arr2D[0][0] * _Other.Arr2D[0][0]) + (Arr2D[0][1] * _Other.Arr2D[1][0]) + (Arr2D[0][2] * _Other.Arr2D[2][0]) + (Arr2D[0][3] * _Other.Arr2D[3][0]);
    Result.Y = (Arr2D[0][0] * _Other.Arr2D[0][1]) + (Arr2D[0][1] * _Other.Arr2D[1][1]) + (Arr2D[0][2] * _Other.Arr2D[2][1]) + (Arr2D[0][3] * _Other.Arr2D[3][1]);
    Result.Z = (Arr2D[0][0] * _Other.Arr2D[0][2]) + (Arr2D[0][1] * _Other.Arr2D[1][2]) + (Arr2D[0][2] * _Other.Arr2D[2][2]) + (Arr2D[0][3] * _Other.Arr2D[3][2]);
    Result.W = (Arr2D[0][0] * _Other.Arr2D[0][3]) + (Arr2D[0][1] * _Other.Arr2D[1][3]) + (Arr2D[0][2] * _Other.Arr2D[2][3]) + (Arr2D[0][3] * _Other.Arr2D[3][3]);
    *this = Result;
    return *this;
}

/*
(4 x 4) * (4 x 4) 의 경우
a00 a01 a02 a03        x    b00 b01 b02 b03        =    ab00 ab01 ab02 ab03
a10 a11 a12 a13            b10 b11 b12 b13            ab10 ab11 ab12 ab13
a20 a21 a22 a23            b20 b21 b22 b23            ab20 ab21 ab22 ab23
a30 a31 a32 a33            b30 b31 b32 b33            ab30 ab31 ab32 ab33

ab00 = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30
ab01 = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31
ab02 = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32
ab03 = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33

ab10 = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30
ab11 = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31
ab12 = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32
ab13 = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33

ab20 = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30
ab21 = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31
ab22 = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32
ab23 = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33

ab30 = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30
ab31 = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31
ab32 = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32
ab33 = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33
*/

float4x4 operator*(const float4x4&amp; _Left, const float4x4&amp; _Right)
{
    float4x4 Result;
    const float4x4&amp; A = _Left;
    const float4x4&amp; B = _Right;

    Result.Arr2D[0][0] = (A.Arr2D[0][0] * B.Arr2D[0][0]) + (A.Arr2D[0][1] * B.Arr2D[1][0]) + (A.Arr2D[0][2] * B.Arr2D[2][0]) + (A.Arr2D[0][3] * B.Arr2D[3][0]);
    Result.Arr2D[0][1] = (A.Arr2D[0][0] * B.Arr2D[0][1]) + (A.Arr2D[0][1] * B.Arr2D[1][1]) + (A.Arr2D[0][2] * B.Arr2D[2][1]) + (A.Arr2D[0][3] * B.Arr2D[3][1]);
    Result.Arr2D[0][2] = (A.Arr2D[0][0] * B.Arr2D[0][2]) + (A.Arr2D[0][1] * B.Arr2D[1][2]) + (A.Arr2D[0][2] * B.Arr2D[2][2]) + (A.Arr2D[0][3] * B.Arr2D[3][2]);
    Result.Arr2D[0][3] = (A.Arr2D[0][0] * B.Arr2D[0][3]) + (A.Arr2D[0][1] * B.Arr2D[1][3]) + (A.Arr2D[0][2] * B.Arr2D[2][3]) + (A.Arr2D[0][3] * B.Arr2D[3][3]);

    Result.Arr2D[1][0] = (A.Arr2D[1][0] * B.Arr2D[0][0]) + (A.Arr2D[1][1] * B.Arr2D[1][0]) + (A.Arr2D[1][2] * B.Arr2D[2][0]) + (A.Arr2D[1][3] * B.Arr2D[3][0]);
    Result.Arr2D[1][1] = (A.Arr2D[1][0] * B.Arr2D[0][1]) + (A.Arr2D[1][1] * B.Arr2D[1][1]) + (A.Arr2D[1][2] * B.Arr2D[2][1]) + (A.Arr2D[1][3] * B.Arr2D[3][1]);
    Result.Arr2D[1][2] = (A.Arr2D[1][0] * B.Arr2D[0][2]) + (A.Arr2D[1][1] * B.Arr2D[1][2]) + (A.Arr2D[1][2] * B.Arr2D[2][2]) + (A.Arr2D[1][3] * B.Arr2D[3][2]);
    Result.Arr2D[1][3] = (A.Arr2D[1][0] * B.Arr2D[0][3]) + (A.Arr2D[1][1] * B.Arr2D[1][3]) + (A.Arr2D[1][2] * B.Arr2D[2][3]) + (A.Arr2D[1][3] * B.Arr2D[3][3]);

    Result.Arr2D[2][0] = (A.Arr2D[2][0] * B.Arr2D[0][0]) + (A.Arr2D[2][1] * B.Arr2D[1][0]) + (A.Arr2D[2][2] * B.Arr2D[2][0]) + (A.Arr2D[2][3] * B.Arr2D[3][0]);
    Result.Arr2D[2][1] = (A.Arr2D[2][0] * B.Arr2D[0][1]) + (A.Arr2D[2][1] * B.Arr2D[1][1]) + (A.Arr2D[2][2] * B.Arr2D[2][1]) + (A.Arr2D[2][3] * B.Arr2D[3][1]);
    Result.Arr2D[2][2] = (A.Arr2D[2][0] * B.Arr2D[0][2]) + (A.Arr2D[2][1] * B.Arr2D[1][2]) + (A.Arr2D[2][2] * B.Arr2D[2][2]) + (A.Arr2D[2][3] * B.Arr2D[3][2]);
    Result.Arr2D[2][3] = (A.Arr2D[2][0] * B.Arr2D[0][3]) + (A.Arr2D[2][1] * B.Arr2D[1][3]) + (A.Arr2D[2][2] * B.Arr2D[2][3]) + (A.Arr2D[2][3] * B.Arr2D[3][3]);

    Result.Arr2D[3][0] = (A.Arr2D[3][0] * B.Arr2D[0][0]) + (A.Arr2D[3][1] * B.Arr2D[1][0]) + (A.Arr2D[3][2] * B.Arr2D[2][0]) + (A.Arr2D[3][3] * B.Arr2D[3][0]);
    Result.Arr2D[3][1] = (A.Arr2D[3][0] * B.Arr2D[0][1]) + (A.Arr2D[3][1] * B.Arr2D[1][1]) + (A.Arr2D[3][2] * B.Arr2D[2][1]) + (A.Arr2D[3][3] * B.Arr2D[3][1]);
    Result.Arr2D[3][2] = (A.Arr2D[3][0] * B.Arr2D[0][2]) + (A.Arr2D[3][1] * B.Arr2D[1][2]) + (A.Arr2D[3][2] * B.Arr2D[2][2]) + (A.Arr2D[3][3] * B.Arr2D[3][2]);
    Result.Arr2D[3][3] = (A.Arr2D[3][0] * B.Arr2D[0][3]) + (A.Arr2D[3][1] * B.Arr2D[1][3]) + (A.Arr2D[3][2] * B.Arr2D[2][3]) + (A.Arr2D[3][3] * B.Arr2D[3][3]);

    /*
    for (size_t y = 0; y &lt; 4; y++)
    {
        for (size_t x = 0; x &lt; 4; x++)
        {
            for (size_t i = 0; i &lt; 4; i++)
            {
                Result.Arr2D[y][x] += _Left.Arr2D[y][i] * _Right.Arr2D[i][x];
            }
        }
    }
    */

    return Result;
}</code></pre>
<h3 id="항등행렬">항등행렬</h3>
<p>: 곱했을 때 자기자신이 나오는 행렬</p>
<ul>
<li>교환법칙이 성립한다.</li>
<li>3차원 변환행렬로 사용할 경우, 항등행렬에도 공간 정보가 포함되어 있다고 볼 수 있다.</li>
</ul>
<pre><code class="language-cpp">/*
1    0    0    0
0    1    0    0
0    0    1    0
0    0    0    1

어떤행렬 * 항등행렬 = 어떤행렬
항등행렬 * 어떤행렬 = 어떤행렬
*/

void Identity()
{
    memset(Arr1D, 0, sizeof(float) * 16);
    // Arr1D의 주소값 위치부터 (sizeof(float) * 16) 크기만큼을 0으로 채우라는 뜻

    Arr2D[0][0] = 1.0f;
    Arr2D[1][1] = 1.0f;
    Arr2D[2][2] = 1.0f;
    Arr2D[3][3] = 1.0f;
}</code></pre>
<h3 id="전치행렬">전치행렬</h3>
<p>: 행과 열을 바꾼 행렬</p>
<pre><code class="language-cpp">/*
00    01    02    03    -(전치)-&gt;    00    10    20    30
10    11    12    13                01    11    21    31
20    21    22    23                02    12    22    32
30    31    32    33                03    13    23    33
*/

void Transpose()
{
    float4x4 Result = *this;
    for (size_t y = 0; y &lt; 4; y++)
    {
        for (size_t x = 0; x &lt; 4; x++)
        {
            Result.Arr2D[y][x] = Arr2D[x][y];
        }
    }

    *this = Result;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kirby's Adventure] 16일차 (24-02-16)]]></title>
            <link>https://velog.io/@yoon-park/Kirbys-Adventure-16%EC%9D%BC%EC%B0%A8-24-02-16</link>
            <guid>https://velog.io/@yoon-park/Kirbys-Adventure-16%EC%9D%BC%EC%B0%A8-24-02-16</guid>
            <pubDate>Fri, 16 Feb 2024 07:46:09 GMT</pubDate>
            <description><![CDATA[<pre><code>~ 개요 ~

■ 엔진
AnimationInfo
    - 애니메이션이 마지막까지 재생되어야 끝났다고 판정되게 수정
Actor
    - Render, Collision의 Activate와 Destroy

■ 컨텐츠
커비 상태 및 이동
    - Squeeze 상태 추가
    - 벽을 만나면 Squeeze된 이후에 벽 방향 방향키가 무력화되도록 수정</code></pre><h2 id="-엔진"># 엔진</h2>
<h3 id=""></h3>
<pre><code class="language-cpp"></code></pre>
<p>###</p>
<pre><code class="language-cpp"></code></pre>
<h2 id="-컨텐츠"># 컨텐츠</h2>
<h3 id="커비-상태-및-이동">커비 상태 및 이동</h3>
<pre><code class="language-cpp">void APlayer::Squeeze(float _DeltaTime)
{
    if (Renderer-&gt;IsCurAnimationEnd() == true)
    {
        Renderer-&gt;ChangeAnimation(GetAnimationName(&quot;Idle&quot;));
    }

    if (UEngineInput::IsPress(VK_LEFT))
    {
        AddMoveVector(FVector::Left * _DeltaTime);
    }

    if (UEngineInput::IsPress(VK_RIGHT))
    {
        AddMoveVector(FVector::Right * _DeltaTime);
    }

    if (UEngineInput::IsFree(VK_LEFT) &amp;&amp; UEngineInput::IsFree(VK_RIGHT))
    {
        StateChange(EPlayState::Idle);
        return;
    }

    if (UEngineInput::IsDown(VK_SPACE))
    {
        StateChange(EPlayState::Jump);
        return;
    }

    if (UEngineInput::IsPress(VK_DOWN))
    {
        StateChange(EPlayState::Crouch);
        return;
    }

    MoveUpdate(_DeltaTime);
}</code></pre>
<pre><code class="language-cpp">void APlayer::Idle(float _DeltaTime)
{
    if (UEngineInput::IsDown(&#39;1&#39;))
    {
        StateChange(EPlayState::FreeMove);
        return;
    }

    if (UEngineInput::IsDown(&#39;2&#39;))
    {
        StateChange(EPlayState::CameraFreeMove);
        return;
    }

    if (IsWallCheck() == false)
    {
        if (UEngineInput::IsPress(VK_LEFT) || UEngineInput::IsPress(VK_RIGHT))
        {
            StateChange(EPlayState::Run);
            return;
        }
    }
    else if (IsRightWallCheck() == true)
    {
        if (UEngineInput::IsPress(VK_LEFT))
        {
            StateChange(EPlayState::Run);
            return;
        }
    }
    else if (IsLeftWallCheck() == true)
    {
        if (UEngineInput::IsPress(VK_RIGHT))
        {
            StateChange(EPlayState::Run);
            return;
        }
    }

    if (UEngineInput::IsDown(VK_SPACE))
    {
        StateChange(EPlayState::Jump);
        return;
    }

    if (UEngineInput::IsPress(VK_DOWN))
    {
        StateChange(EPlayState::Crouch);
        return;
    }

    MoveUpdate(_DeltaTime);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kirby's Adventure] 15일차 (24-02-15)]]></title>
            <link>https://velog.io/@yoon-park/Kirbys-Adventure-15%EC%9D%BC%EC%B0%A8-24-02-15</link>
            <guid>https://velog.io/@yoon-park/Kirbys-Adventure-15%EC%9D%BC%EC%B0%A8-24-02-15</guid>
            <pubDate>Thu, 15 Feb 2024 16:00:05 GMT</pubDate>
            <description><![CDATA[<pre><code>[엔진]
- 충돌 개념
    - 동작하지 않던 부분 수정
    - 네모와 네모가 충돌할 경우 추가
    - 원과 네모가 충돌할 경우 추가
    - 몬스터 테스트케이스로 실험
- 텍스트 출력 기능
    - 구현
    - 글자가 가운데에 위치하도록 수정

[컨텐츠]
- 커비 FSM 및 이동
    - Down 상태 추가
    - Squeeze 상태 추가 (미완)
    - Run에서 Idle 상태로 바뀔 때 이동이 끝날 때까지 애니메이션이 출력되도록 수정
    - 최대속력, 이동가속도, 중력가속도 수치 조정
    - 맵의 시작부분과 끝부분에서 카메라가 맵 이미지 바깥을 비추지 않도록 수정</code></pre><hr>
<h2 id="engine">Engine</h2>
<h3 id="-충돌-개념"># 충돌 개념</h3>
<pre><code class="language-cpp"></code></pre>
<h3 id="-텍스트-출력-기능"># 텍스트 출력 기능</h3>
<pre><code class="language-cpp"></code></pre>
<h2 id="contents">Contents</h2>
<h3 id="-커비-fsm-및-이동"># 커비 FSM 및 이동</h3>
<pre><code class="language-cpp"></code></pre>
<hr>
<h2 id="-생각해볼-문제-">! 생각해볼 문제 !</h2>
<ul>
<li>ImageRenderer.cpp의 Update 함수 중 아래 부분에서, 한 장의 이미지로 이뤄진 애니메이션일 경우 시작부터 CurFrame이 Index.size()와 같아 IsEnd가 true가 되어버리는 문제점이 있다.</li>
<li>커비 리소스를 분류할 때, 어떤 기준으로 나눠서 한 장으로 묶어야 할까?</li>
<li>커비 낙하 상태를 만들게 된다면 조건을 어떻게 걸어야 할까? 게임플레이 영상에서는 발의 끝까지 버티다가 떨어지던데, 이건 어떻게 체크할 수 있을까?</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코딩스탠다드]]></title>
            <link>https://velog.io/@yoon-park/%EC%BD%94%EB%94%A9%EC%8A%A4%ED%83%A0%EB%8B%A4%EB%93%9C</link>
            <guid>https://velog.io/@yoon-park/%EC%BD%94%EB%94%A9%EC%8A%A4%ED%83%A0%EB%8B%A4%EB%93%9C</guid>
            <pubDate>Wed, 24 Jan 2024 17:18:28 GMT</pubDate>
            <description><![CDATA[<p><em>가급적 기억해두고 지키자!</em></p>
<h3 id="명명법-초기화">명명법, 초기화</h3>
<pre><code>- 프로젝트 생성 시 바탕화면은 절대 피하자.
- 경로, 함수명, 변수명에 한글은 절대 쓰지 말자.

- 전역변수와 지역변수는 이름을 구분하여 작성하자.
- 변수에는 반드시 초기값을 설정하자.

- 함수의 인자 앞에는 언더바를 붙이자.

- 클래스가 존재하는 파일명은 웬만하면 클래스명과 동일한 이름으로 설정하자.

- 포인터를 초기화할 때 절대 0을 사용하지 말고, nullptr을 사용하자.</code></pre><h3 id="함수">함수</h3>
<pre><code>- 함수의 리턴값을 꼭 변수로 받아서 확인해보자.
- 함수를 한 줄로 만들지 말자.
  ⇒ 중단점을 걸어 값을 확인하기 어렵기 때문</code></pre><h3 id="클래스">클래스</h3>
<pre><code>- 클래스를 정의하면 일단 public, protected, private 꼭 작성해두자.
  그리고 순서를 바꾸거나, 이후에 밑에서 한번 더 접근제한지정자를 사용하지 말자.
- 한 접근제한지정자 내에 멤버가 오는 순서 : 생성자 - 변수 - 함수

- 상속을 내려주는 부모클래스라면 무조건 생성자와 소멸자를 다 만들자.
  자식클래스도 웬만하면 생성자와 소멸자를 만들어두자.
- 부모클래스의 헤더 및 cpp 파일에서 자식클래스의 자료형을 사용하는 일은 절대 없어야 한다.</code></pre><h3 id="그-외">그 외</h3>
<pre><code>[if문]
- if문을 사용할 때, 한 줄 코드일지라도 반드시 중괄호를 사용하자.

[동적할당]
- new 코드를 쳤다면 delete 코드를 반드시 치고, 제대로 호출되는지 꼭 확인하자.

[게임 엔진]
- 게임의 개념이 엔진의 개념을 받아들이거나 사용하는 것은 가능하지만,
  엔진의 개념이 게임의 개념을 받아들이는 것은 절대 안된다.
  e.g. 디버깅이 플레이어를 알아야 한다 (절대 X)
  ⇒ 상위 프로젝트의 라이브러리가 더 근본적인 개념(중력, 물리, 디버깅 등)을 제공해주기 때문</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-24]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-24-fnzw0jmq</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-24-fnzw0jmq</guid>
            <pubDate>Wed, 24 Jan 2024 01:09:26 GMT</pubDate>
            <description><![CDATA[<h2 id="자료구조">자료구조</h2>
<h3 id="순회">순회</h3>
<pre><code class="language-cpp">#include &lt;list&gt;
#include &lt;map&gt;

int main()
{
    // [iterator 순회]
    {
        std::list&lt;int&gt; NewList;

        std::list&lt;int&gt;::iterator StartIter = NewList.begin();
        std::list&lt;int&gt;::iterator EndIter = NewList.end();

        for (; StartIter != EndIter; ++StartIter)
        {

        }
    }
    {
        std::map&lt;int, int&gt; NewMap;

        std::map&lt;int, int&gt;::iterator StartIter = NewMap.begin();
        std::map&lt;int, int&gt;::iterator EndIter = NewMap.end();

        for (; StartIter != EndIter; ++StartIter)
        {

        }
    }

    // [ranged for 순회]
    {
        std::list&lt;int&gt; NewList;
        for (int&amp; Value : NewList)
        {
            /*
            ranged for의 단점

            순회하는 동안 자료구조의 메모리 구조가 변경되면 치명적인 에러를 반환한다.
            - 값 변경 (가능)
            - 노드 삭제 (치명적인 에러)

            std::list&lt;int&gt; NewList;
            for (size_t i = 0; i &lt; 100; i++)
            {
                NewList.push_back(i);
            }
            for (int&amp; Value : NewList)
            {
                NewList.erase(NewList.begin());    // ERROR
            }
            */
        }
    }
    {
        std::map&lt;int, int&gt; NewMap;
        for (std::pair&lt;const int, int&gt;&amp; Map : NewMap)
        {

        }
    }
}</code></pre>
<h2 id="게임-엔진">게임 엔진</h2>
<blockquote>
<p>💡 *<em>컨텐츠 프로그래머가 엔진 구조에서 집중해서 봐야할 것 *</em></p>
</blockquote>
<ul>
<li>오브젝트 개념
  : 화면에 존재하는 개념들<ul>
<li>만들기, 찾기, 함수 실행하기, …</li>
</ul>
</li>
<li>릴리즈 개념
  : 화면에 존재하는 개념들이 사라질 때 어떻게 정리하는지</li>
<li>렌더링 개념
  : 화면에 어떻게 그려지는지</li>
<li>콜리전 개념
  : 화면 안의 존재들이 어떻게 상호작용하는지</li>
</ul>
<h3 id="오브젝트">오브젝트</h3>
<p>화면에 보이는 존재들 뿐만 아니라, ‘장면’이라는 개념도 표현한다.
흔히 ‘스테이지’라고 부르지만 언리얼에서는 ‘Level’, 유니티에서는 ‘Scene’이라고 부른다.
<em>e.g. Level 목록 : Title, Stage1, Stage2, …, End
Stage1 Level 안에 A 몬스터, B 몬스터가 나온다.
Stage2 Level 안에 C 몬스터, D 몬스터, E 몬스터가 나온다.
…</em></p>
<p><strong>언리얼 오브젝트 명칭에 따른 분류</strong>
A가 붙은 오브젝트명 ⇒ 화면에 위치가 존재하는 오브젝트
U가 붙은 오브젝트명 ⇒ 화면에 위치가 존재하지는 않지만, 엔진에 속해있는 오브젝트
(참고) F가 붙은 명칭 ⇒ 구조체라는 의미</p>
<h2 id="kirbys-adventure-w-winapi">Kirby&#39;s Adventure w/ WinAPI</h2>
<p>~ to be written ~</p>
<p>(언리얼과 유사하게 구성하려 노력했다.)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-23]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-24</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-24</guid>
            <pubDate>Wed, 24 Jan 2024 01:08:49 GMT</pubDate>
            <description><![CDATA[<h2 id="stl">stl</h2>
<h3 id="string_view">string_view</h3>
<pre><code class="language-cpp">// 마치 문자열만 전문적으로 처리하는 포인터와 같다.

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;string&gt;
#include &lt;string_view&gt;

class MyString
{
public:
    static char SSO[25];
    /*
    Small/Short String Optimization

    25글자 이하는 동적할당이 아니라 전역을 통해 해결하기도 한다.
    하지만 그럼에도 string_view가 빠르다(?).
    */

    char* Test;

    MyString(const char* _Ptr)
    {
        Test = new char[strlen(_Ptr)];  // 동적할당
    }
};

class MyString_View
{
public:
    const char* Test;
        /*
        string_view는 포인터 역할일 뿐, 실제 데이터를 갖고 있지는 않다.
        게다가 const나 마찬가지기 때문에 내부의 내용을 변경하는 것도 불가하다.
        */

    MyString_View(const char* _Ptr)
    {
        Test = _Ptr;    // 얕은 복사
    }
};

void FunctionString(const MyString&amp; _Value)
{

}

void FunctionView(MyString_View Name)
{
    /*
        Name.data();
        .data()의 역할...?
        */
}

int main()
{
    FunctionString(&quot;AgjksflhgjsklhgsdfjkAAAA&quot;);
    /*
    MyString NewString = &quot;AgjksflhgjsklhgsdfjkAAAA&quot;;
    FunctionString(NewString);

    실제론 위와 같이 새로운 메모리에 할당이 발생한다.
    함수가 종료되면 할당된 메모리의 삭제도 필요하다.

    따라서, const std::string&amp; 으로 받는 것보다, string_view 로 받으면
    해당 값의 위치를 반환받을 수 있어 연산이 줄어들고 빠르다.
    */
}</code></pre>
<h2 id="winapi">WinAPI</h2>
<pre><code class="language-cpp">#include &quot;framework.h&quot;
#include &quot;WindowsProject1.h&quot;

#define MAX_LOADSTRING 100

// 전역 변수 : 
// [HINSTANCE = 프로그램 핸들]
HINSTANCE hInst;                        // 현재 인스턴스
WCHAR szTitle[MAX_LOADSTRING];          // 제목 표시줄 텍스트
WCHAR szWindowClass[MAX_LOADSTRING];    // 기본 창 클래스 이름

bool Live = true;

// 함수 선언 :
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

// 진입점 :
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // 사용하지 않은 인자 사용
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // 전역 문자열 초기화
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = 0; // 단축키 (사용X)

    MSG msg;

    // 기본 메시지 루프
    while (Live)
    // while (GetMessage(&amp;msg, nullptr, 0, 0))
    {
        /*
        GetMessage()
        - 메세지가 없으면 프로그램을 정지시키고, 메세지가 올 때까지 대기한다.

        PeekMesaage()
        - 메세지가 없으면 return

            PM_REMOVE
            - 메세지 버퍼 삭제
            - 한번 실행되면 누적된 메세지를 삭제하여 마지막에 전해진 메세지만 실행한다

            PM_NOREMOVE
            - 윈도우 메세지를 지속적으로 삭제하지 않기 때문에 계속 쌓인다
            - 너무 많이 쌓이게 되면 윈도우가 정지한 것처럼 보일 수 있다
        */
        if (PeekMessage(&amp;msg, nullptr, 0, 0, PM_REMOVE))
        // if (!TranslateAccelerator(msg.hwnd, hAccelTable, &amp;msg))
        // 게임에서는 단축키를 사용하지 않기 때문에 단축키를 처리하는 구문은 필요 없다.
        {
            TranslateMessage(&amp;msg);
            DispatchMessage(&amp;msg);
        }

        // (TODO)

        /*
        (Tip?) 만약 인자를 이해할 수 없다면, 대부분의 함수에서
        0을 넣으면 디폴트 옵션으로 실행시켜준다(...).
        */
    }

    return (int) msg.wParam;
}

// 창 클래스 등록 : 
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    /*
    앞이나 뒤에 W 또는 A가 붙어있는 함수
    - A =&gt; 멀티바이트 함수
    - W =&gt; 와이드바이트 함수

    따라서 이 경우, wcex.lpszClassName을 직접 입력해주고 싶다면 &quot;&quot; 앞에 L을 붙여야 한다.
    */
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;  // 메세지 콜백 (함수포인터, 다형성)
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = nullptr;  // 상단메뉴 (사용X)
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    // 윈도우 창 클래스 만들기
    return RegisterClassExW(&amp;wcex);
}

// 인스턴스 핸들 저장, 주 창 만들기 :
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   // 인스턴스 핸들
   hInst = hInstance;

   // 윈도우 창 생성
   // [HWND = 윈도우 핸들]
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
   /*
   szTitle =&gt; const char* 형

   만약 const std::string&amp; _Title 을 인자로 넣고 싶다면...
   const std::string&amp; 은 내부에 std::vector&lt;char&gt; 이 존재한다고 생각하면 된다.
   &amp;_Title[0]
   _Title.c_str()

    ...혹은 조금 다르지만 std::string_view _Title 을 사용할 수 있다!
    _Title.data()
   */

   if (!hWnd)
   {
      return FALSE;
   }

   // 윈도우 창 띄우기
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

// 주 창 메시지 처리 : 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    /*
    // 애플리케이션 메뉴 처리
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;

    게임에서는 상단 메뉴를 사용하지 않기 때문에 삭제해도 된다.
    */

    // 주 창 그리기
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &amp;ps);
            // [HDC = 윈도우에 그림을 그릴 수 있는 권한]

            Rectangle(hdc, 100, 100, 200, 200); // (왼쪽 위, 오른쪽 아래)

            EndPaint(hWnd, &amp;ps);
        }
        break;
    // 종료 메세지 게시하고 반환
    case WM_DESTROY:
        PostQuitMessage(0);
        /*
        GetMessage()는 평소에 1을 return한다.
        PostQuitMessage()가 호출되면 GetMessage()가 0을 호출해서 while문에서 탈출한다.
        */
        Live = false;
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        /*
        사용자가 할일을 정의하지 않은 메세지들은 윈도우가 디폴트로 처리하는 방식으로 실행시켜준다.
        WM_SETFOCUS, WM_KILLFOCUS, ... 등 여러 종류의 메세지가 존재한다.
        */
    }
    return 0;
}

// 정보 대화 상자 메시지 처리기 :
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == 2)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

/*
HINSTANCE =&gt; 프로그램 핸들
e.g. 내 프로그램이 4800번일 경우
     윈도우에게 4800번 프로그램이 창 하나 만들어달라고 전달
     윈도우에게 4800번 프로그램이 키보드 연결을 끊고 싶다고 전달

HWND =&gt; 윈도우 핸들
e.g. 윈도우에게 4800번 프로그램의 530번 창 크기를 바꾸고 싶다고 전달

HDC =&gt; 윈도우에 그림을 그릴 수 있는 권한
e.g. 윈도우에게 4800번 프로그램의 530번 창의 230번 그림권한에서 사각형을 그리고 싶다고 전달

HMENU, HCURSOR, HICON, ... 등 여러 권한이 존재한다.
*/</code></pre>
<h2 id="kirbys-adventure-w-winapi">Kirby&#39;s Adventure w/ WinAPI</h2>
<h3 id="공통-설정">공통 설정</h3>
<ul>
<li>속성<ul>
<li>모든 구성, 모든 플랫폼 → 구성 속성<ul>
<li>일반 → 출력 디렉터리 → <code>$(SolutionDir)Bin\$(Platform)\$(Configuration)\</code></li>
<li>일반 → 중간 디렉터리 → <code>$(SolutionDir)Bin\$(Platform)\$(Configuration)\$(ProjectName)\</code><blockquote>
<p><strong>Q) bin을 따로 설정해주는 이유?</strong>
   출력된 요소들을 한곳으로 모으기 위해</p>
</blockquote>
</li>
<li>일반 → 구성 형식 → 정적 라이브러리(.lib)</li>
<li>일반 → C++ 언어 표준 → ISO C++20 표준(/std:c++ 20)</li>
<li>고급 → 문자 집합 → 멀티바이트 문자 집합 사용</li>
<li>VC++ 디렉터리 → 포함 디렉터리 → <code>..\;</code> 추가</li>
<li>C/C++ → 일반 → 경고를 오류로 처리 → 예(/WX)</li>
</ul>
</li>
</ul>
</li>
<li>참조 추가<ul>
<li>상위 level 프로젝트 전부 추가</li>
</ul>
</li>
</ul>
<h3 id="구조">구조</h3>
<p><strong>Level0 : EngineBase</strong></p>
<ul>
<li>윈도우 프로그래밍 시 언제 어디서나 쓰일 코드</li>
<li>디버깅, 스트링, …</li>
</ul>
<p><strong>Level1 : EnginePlatform</strong></p>
<ul>
<li>운영체제의 영향을 크게 받는 클래스</li>
<li>window, 사운드, …</li>
</ul>
<p><strong>Level2 : EngineCore</strong></p>
<ul>
<li>게임엔진이 처리해야 할 내용</li>
<li>오브젝트 구조, 렌더링 구조, 삭제 구조, …</li>
</ul>
<p><strong>Level3 : Contents</strong></p>
<ul>
<li>구현하고자 하는 게임의 컨텐츠</li>
</ul>
<p><strong>Level4 : App</strong></p>
<ul>
<li>속성<ul>
<li>모든 구성, 모든 플랫폼 → 구성 속성<ul>
<li>일반 → 구성 형식 → 애플리케이션(.exe)</li>
<li>링커 → 시스템 → 하위 시스템 → 창(/SUBSYSTEM:WINDOWS)</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-22]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-22</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-22</guid>
            <pubDate>Mon, 22 Jan 2024 14:56:34 GMT</pubDate>
            <description><![CDATA[<h2 id="클래스">클래스</h2>
<h3 id="delete">delete</h3>
<pre><code class="language-cpp">/*
자동으로 생성된 복사생성자, 대입연산자 등을 통한
복사가 발생하는 것을 막고 싶다면...

1. 복사생성자와 대입연산자를 private에 두기
- 좋은 방법은 아니다
- 해당 함수 내에서 복사를 시도하기라도 하면 완전히 막을 수도 없다

2. delete
- 함수를 명시적으로 사용하지 않겠다는 뜻
- 복사가 필요한 상황이 되면 그것을 내가 인지하고 원하는 방식으로 복사하기 위함
*/

class Test
{
public:
    void operator=(const Test&amp; _Other) = delete;    // (2)

/*
private:
    void operator=(const Test&amp; _Other)    // (1)
    {

    }
*/
};

int main()
{
    Test NewTest0;
    Test NewTest1;
    // NewTest0 = NewTest1;
}</code></pre>
<h3 id="rvalue-복사생성자-대입연산자">RValue 복사생성자 대입연산자</h3>
<pre><code class="language-cpp">/*
RValue를 사용해 복사생성자 또는 대입연산자를 호출하는 경우를 대비하여,
따로 RValue용 복사생성자와 대입연산자를 명시하여 복사 방식을 지정해줄 수 있다.
컴파일 시, 복사생성자 또는 대입연산자에 인자로 들어가는 요소가 RValue인지 여부를
판단하여 적절한 함수를 호출하도록 되어 있다.

&amp;&amp; =&gt; RValue를 나타내는 기호 (딱히 레퍼런스의 레퍼런스 같은 문법적 요소는 아니다)
*/

#include &lt;iostream&gt;

class Test
{
public:
    int* NewValue = nullptr;

    Test()
    {
        NewValue = new int();
        // 동적할당이기 때문에 무조건 힙영역에 생성된다
    }

    ~Test()
    {
        if (nullptr != NewValue)
        {
            delete NewValue;
            NewValue = nullptr;
        }
    }

    // RValue 복사생성자
    Test(Test&amp;&amp; _Other) noexcept
    {
        if (nullptr != NewValue)
        {
            delete NewValue;
            NewValue = nullptr;
        }

        NewValue = _Other.NewValue;
        _Other.NewValue = nullptr;
    }

    // 일반 복사생성자
    Test(const Test&amp; _Other)    // (2)
    {
        if (nullptr != NewValue)
        {
            delete NewValue;
            NewValue = nullptr;
        }

        NewValue = new int[100];

        for (size_t i = 0; i &lt; 100; i++)
        {
            // 깊은 복사
        }
    }

    // RValue 대입연산자
    void operator=(Test&amp;&amp; _Other) noexcept    // (1), (2)
    {
        if (nullptr != NewValue)
        {
            delete NewValue;
            NewValue = nullptr;
        }

        NewValue = _Other.NewValue;
        _Other.NewValue = nullptr;
    }

    // 일반 대입연산자
    void operator=(const Test&amp; _Other)
    {
        if (nullptr != NewValue)
        {
            delete NewValue;
        }

        NewValue = new int[100];

        for (size_t i = 0; i &lt; 100; i++)
        {
            // 깊은 복사
        }
    }
};

/*
(1) 객체를 함수 내에 생성하는 경우
함수가 return되면 객체는 사라진다.

RValue -&gt; RValue 대입연산자 호출
*/
Test CreateTest()
{
    Test NewTest;

    return NewTest;
}

/*
(2) 객체를 전역에 생성하는 경우
함수가 return되어도 객체가 사라지지 않는다.

일반 복사생성자 호출 -&gt; RValue 만들기(???) -&gt; RValue 대입연산자 호출

Test NewTestG;

Test CreateTest()
{
    return NewTestG;    // return Test(NewTestG);
}
*/

int main()
{
    Test NewTest = Test();

    NewTest = CreateTest();
    /*
    Test mainTest = CreateTest();
    NewTest = mainTest;

    이렇게 두 줄의 과정을 거치는 것과 같다.
    여기서 mainTest는 이름은 없지만 분명히 존재하는, RValue이다.
    이 경우, CreateTest()의 return값은 함수가 종료되면 사라지지만,
    RValue의 생성자에서 동적할당된 NewValue는 여전히 힙영역에 남아있기 때문에
    깊은복사 방식을 채택하는 대신 해당 메모리를 NewTest의 NewValue에 연결해주는
    방식을 RValue 대입연산자에 명시하여 자원낭비를 막을 수 있다.
    */
}</code></pre>
<h2 id="예외-처리">예외 처리</h2>
<h3 id="try-catch-noexcept">try, catch, noexcept</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;

void Function2() /*throw(int)*/
// 명시해주는 것은 선택이지만, 적어두면 알아보기 편하다.
{
    int* Ptr = nullptr;

    if (Ptr != nullptr)
    {
        throw 0;    // &quot;int 예외&quot;
    }
    if (Ptr != nullptr)
    {
        throw true;    // &quot;bool 예외&quot;
    }
    if (Ptr == nullptr)
    {
        // 바깥쪽에서 catch해주지 않으면 아래의 코드가 실행된다.
        throw &quot;포인터가 nullptr 이었습니다&quot;;
    }
}

// [noexcept]
// throw와 catch가 적용되지 않고 에러가 난 장소에서 바로 프로그램이 터진다.
void Function1() throw(int, bool) /*noexcept*/
{
    Function2();
}

void Function()
{
    try
    {
        // [try] 예외처리를 해야하는 코드
        Function1();
    }
    // 예외를 throw하면 그에 맞는 catch 코드가 실행된다.
    catch (int Exp)
    {
        std::cout &lt;&lt; &quot;int 예외&quot; &lt;&lt; std::endl;
    }
    catch (bool Exp)
    {
        std::cout &lt;&lt; &quot;bool 예외&quot; &lt;&lt; std::endl;
    }
    /*
    catch (const char* Exp)
    {
        std::cout &lt;&lt; Exp &lt;&lt; std::endl;
    }
    */
}
int main()
{
    Function();
}

/*
throw와 if의 차이
- if문은 함수 내부에서만 처리 가능하다.
- 위와 달리 throw는 외부에서 해당 예외사항을 인지할 수 있게 해준다.
- 일반적으로 사용자는 라이브러리(다른 프로젝트)의 내부를 볼 수 없기 때문에,
  외부에 예외를 알리는 것은 의미가 있다.
*/</code></pre>
<h2 id="visual-studio">Visual Studio</h2>
<h3 id="템플릿-만들기">템플릿 만들기</h3>
<pre><code class="language-cpp">/*
자주 사용하는 클래스나 코드의 형태가 있다면 형태를 저장해서 한번에 불러오는 것

- 프로젝트 템플릿
    프로젝트 설정까지 저장하는 것

- 항목 템플릿 (&lt;- 요것만 다룬다)
    특정 항목만 저장해두는 것
    .h와 .cpp은 각각 내보내야 한다 (동시에 내보내는건 안되기 때문)

(Tip!) Visual Studio 자체를 원드라이브에 넣어두면
Microsoft 로그인을 통해 어디서든 같은 환경을 유지할 수 있다.

---------------------------------------------
&lt;만들기&gt;
상단 메뉴 -&gt; 프로젝트 -&gt; 템플릿 내보내기(.h)
-&gt; .\Visual Studio 2022\My Exported Templates
-&gt; 압축파일에서 .h 파일, MyTemplate.vstemplate, __Templateicon.ico 빼내기
-&gt; $rootnamespace$를 전부 $safaitemname$으로 바꾸고 저장

-&gt; 상단 메뉴 -&gt; 프로젝트 -&gt; 템플릿 내보내기(.cpp)
-&gt; .\Visual Studio 2022\My Exported Templates
-&gt; 압축파일에서 .cpp 파일 빼내기
-&gt; $rootnamespace$를 전부 $safeitemname$으로 바꾸고 저장

-&gt; MyTemplate.vstemplate 수정
    &lt;TemplateData&gt;
        &lt;DefaultName&gt;DefaultClass.h&lt;/DefaultName&gt;
        &lt;Name&gt;(VS에서 실제로 표기될 이름 작성하기, 가급적 A로 시작하면 상단에 뜨기 때문에 좋다.)&lt;/Name&gt;
    &lt;/TemplateData&gt;
    &lt;TemplateContent&gt;
        &lt;References /&gt;
        &lt;ProjectItem SubType =&quot;&quot; TargetFileName=&quot;$fileinputname$.h&quot; ReplaceParameters=&quot;true&quot;&gt;DefaultClass.h&lt;/ProjectItem&gt;
        &lt;ProjectItem SubType =&quot;&quot; TargetFileName=&quot;$fileinputname$.cpp&quot; ReplaceParameters=&quot;true&quot;&gt;DefaultClass.cpp&lt;/ProjectItem&gt;
    &lt;/TemplateContent&gt;

-&gt; .\Visual Studio 2022\Templates\itemTemplates
-&gt; 새로운 폴더 생성
-&gt; .h 파일, .cpp 파일, MyTemplate.vstemplate, __Templateicon.ico 가져다 넣기
-&gt; Visual Studio 재시작

---------------------------------------------
&lt;불러오기&gt;
새 항목 추가 -&gt; 템플릿 선택 (항목 템플릿)
*/</code></pre>
<h2 id="winapi">WinAPI</h2>
<p>윈도우도 게임엔진과 유사하다고 생각하자</p>
<pre><code class="language-cpp">#include &quot;framework.h&quot;
#include &quot;WindowsProject1.h&quot;

#define MAX_LOADSTRING 100

// 전역 변수 : 
HINSTANCE hInst;                        // 현재 인스턴스
/*
[HINSTANCE]
윈도우는 사용자에게 포인터를 주지 않는다.
대신 관리용 표식을 정수로 제공하고, 이는 주소값 또는 동적할당 포인터나 다름없는 역할을 한다.
프로그램을 켤 때, xx번 프로그램이라고 배정된 번호가 전역으로 저장되어 있다.
*/
WCHAR szTitle[MAX_LOADSTRING];          // 제목 표시줄 텍스트
WCHAR szWindowClass[MAX_LOADSTRING];    // 기본 창 클래스 이름

// 함수 선언 :
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

// 진입점 :
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    // 사용하지 않은 인자 사용
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // (TODO)

    // 전역 문자열 초기화
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = 0;
    // HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
    // 게임에서 윈도우 시스템 단축키는 사용하지 않기 때문에 로드하지 않아도 상관 없다.

    MSG msg;

    // 기본 메시지 루프
    while (GetMessage(&amp;msg, nullptr, 0, 0))
    /*
    이 while문은 GetMessage()로 인해 변화? 없이는 진행되지 않기 때문에 중단점이 적용되지 않는다.
    하지만 게임은 Alt+Tab으로 창을 전환해도 계속 메모리를 점유하고 동작하고 있어야 한다.

    GetMessage()
    - 윈도우에 사용자가 변화를 주면 작동하는 동기함수

    일반적인 게임
    - 윈도우에 사용자가 변화를 주는 여부와 상관없이 계속 돌아간다

    따라서 윈도우가 변화를 마냥 기다리는 것이 아니라, 계속 while문을 돌리며 상황에 따라
    적절한 할일을 하도록 해주어야 하는데, 이를 메세지 콜백방식으로 해결할 수 있다. (WndProc())
    */
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &amp;msg))
        {
            TranslateMessage(&amp;msg);
            DispatchMessage(&amp;msg);
        }
    }

    return (int) msg.wParam;
}

// 창 클래스 등록 : 
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    /*
    typedef struct tagWNDCLASSEXW {
    WNDPROC     lpfnWndProc;
    } WNDCLASSEXW
    typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

    정의로 이동하다 보면, 함수포인터의 형태임을 알 수 있다.
    윈도우는 사용자가 무엇을 하고싶은지 처음부터 알 수 없기 때문에
    사용자가 함수포인터를 통해 실행시키고 싶은 내용을 넣어주면
    윈도우는 특정 조건 하에 그것을 실행시켜준다.
    이는 가상함수테이블을 기반한 다형성과 유사한 원리이다.

    복잡하게 생겨서 그렇지, 아래와 같이 해당 함수 형식에 맞게 작성하기만 하면 작동한다.
    __int64 __stdcall Test(HWND _Hwnd, unsigned int, unsigned __int64, __int64)
    {
        return 0;
    }
    wcex.lpfnWndProc    = Test;
    */
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = nullptr;
    // wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
    // 게임에서 윈도우창 상단 메뉴는 필요 없다.
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    // 윈도우 창 클래스 만들기
    return RegisterClassExW(&amp;wcex);
}

// 인스턴스 핸들 저장, 주 창 만들기 :
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   // 인스턴스 핸들
   hInst = hInstance;

   // 윈도우 창 생성
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
   /*
   [HWND]
   새 창을 만들 때에도, xx번 프로그램에서 xxx번 윈도우를 띄운다는 일련번호를 주고
   이는 사용자가 윈도우를 제어하는 핸들로 사용할 수 있다.
   */ 

   if (!hWnd)
   {
      return FALSE;
   }

   // 윈도우 창 띄우기
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

// 주 창 메시지 처리 : 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    // 애플리케이션 메뉴 처리
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    // 주 창 그리기
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &amp;ps);
            // (TODO)
            EndPaint(hWnd, &amp;ps);
        }
        break;
    // 종료 메세지 게시하고 반환
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// 정보 대화 상자 메시지 처리기 :
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == 2)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-19]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-19</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-19</guid>
            <pubDate>Fri, 19 Jan 2024 09:47:08 GMT</pubDate>
            <description><![CDATA[<h2 id="error">ERROR</h2>
<h3 id="zero-division-null-pointer-exception">zero division, null pointer exception</h3>
<pre><code class="language-cpp">class Test
{
public:
    int Value;

    void Function()
    {
        this-&gt;Value = 20;
    }
};

Test* CreateTest()
{
    return nullptr;
}

int main()
{
    {
        // [zero division]
        int Value = 0;
        int Test = 100 / Value;
    }
    {
        // [null reference exception]
        Test* NewTest = CreateTest();
        NewTest-&gt;Function();
    }
}</code></pre>
<h3 id="조사식">조사식</h3>
<p>~ to be written ~</p>
<h2 id="자료구조">자료구조</h2>
<h3 id="string">String</h3>
<pre><code class="language-cpp">/*
문자를 표현하는 방식 = 인코딩
숫자 1개와 문자 1개를 매칭시키는 방식

[아스키] (초기형 방식)
- 7비트
- 현재는 ansi, 멀티바이트 방식이라고 불리는 것에 통합되었다

[ansi]
- 1바이트 (char =&gt; 0 ~ 255, &#39;A&#39; = 67)
- 아스키의 확장형
- 초창기에는 한글을 고려할 필요가 없었기 때문에, 255개의 숫자에
  코딩에 필요한 모든 문자가 잘 매칭되어 있었다 (+, -, *, /, [, ], ...도 포함)

[멀티바이트]
- 상황에 따라 1바이트 or 2바이트
- 65536개까지 가능...하지만 전세계의 글자를 다 넣을순 없었다
- 그래서 국가코드가 탄생했다
- 운영체제에 설정된 국가를 따르기 때문에 운영체제의 영향을 받는다
- 몇바이트를 사용할지 판단해야 하므로 좀 느리다...

[유니코드] = 와이드바이트
- 2바이트
- 여전히 국가코드간 변환을 해야한다는 불편함이 있다
- 전용 자료형인 wchar_t를 C++에서 지원한다
- 문자열 앞에 L을 붙이는 표현식을 사용한다

[UTF-8]
- 1 ~ 4바이트 안에 전세계 모든 글자를 넣었다
- 국가간 변환이 필요하지 않다(?)
- 예를 들어 U+0000부터 U+007F 범위에 있는 아스키 문자들은 1바이트만으로 표시된다
- 이모지(...)도 포함될 정도로 다양한 문자의 형태가 포함되어 있다
- UTF-16은 잘 사용하지 않는다...


일반적으로 코딩할 때에는, 내가 사용하는 문자열의 인코딩방식이 무엇인지 인지해야 한다.
함수를 사용하다보면 문자열을 인자로 받는 경우가 있는데, 이 때 만약 제대로 동작하지 않는다면
내가 넣어준 문자열의 값이 잘못된 것이 아니라, 인코딩이 잘못되었을 수 있음을 인지하고 있어야 한다.
*/

int main()
{
    char Arr0[3] = &quot;가&quot;;        // 멀티바이트 (2 + 1바이트)
    wchar_t Arr1[2] = L&quot;가&quot;;    // 유니코드 (1 + 1바이트)
    char8_t Arr2[4]= u8&quot;가&quot;;    // UTF-8 (3 + 1바이트)

    // 기본적으로 멀티바이트를 사용하고, 필요할 때 와이드바이트나 UTF-8 방식으로 변환하자!
}</code></pre>
<pre><code class="language-cpp">/*
[string]
- 어댑터 컨테이너 자료구조?
- char형 벡터라고 생각하면 편하다
*/

#include &lt;string&gt;
#include &lt;iostream&gt;

class MyString
{
public:
    int Size = 0;
    char* Arr = nullptr;

    MyString(const char* _Ptr)
    {
        // 깊은 복사
        Size = strlen(_Ptr) + 1;
        Arr = new char[Size] {0, };

        for (size_t i = 0; i &lt; Size; i++)
        {
            Arr[i] = _Ptr[i];
        }
    }

    MyString(const MyString&amp; _Other)    // 복사 생성자
    {
        // 깊은 복사
        Size = _Other.Size;
        Arr = new char[Size] {0, };

        for (size_t i = 0; i &lt; Size; i++)
        {
            Arr[i] = _Other.Arr[i];
        }
    }

    ~MyString()
    {
        if (Arr != nullptr)
        {
            delete Arr;
            Arr = nullptr;
        }
    }

    void operator =(const MyString&amp; _Other)
    {
        // 얕은 복사
        Arr = _Other.Arr;
    }
};

void TestFunction(std::string Text)
{

}

void TestMyFunction(const MyString&amp; Text)
{
    /*
    인자가...

    MyString Text 인 경우
    - 복사 생성자로 들어가 깊은 복사가 발생한다

    const MyString&amp; Text 인 경우
    - 레퍼런스이기 때문에 값을 새로 생성할 필요가 없어 생성자를 거치지 않고
    - 새로이 메모리를 소모하지 않는다
    */
}

int main()
{
    // 멀티바이트 인코딩
    // std::vector&lt;char&gt;
    std::string Text0;
    Text0.reserve(3);
    Text0 = &quot;가&quot;;

    std::string Text1;
    Text1.reserve(3);
    Text1 = &quot;나&quot;;

    std::string Result = Text0 + Text1;    // &quot;가나&quot;

    // 와이드바이트 인코딩 (유니코드)
    // std::vector&lt;wchar_t&gt;
    std::wstring wText0 = L&quot;가&quot;;
    std::wstring wText1 = L&quot;나&quot;;

    std::wstring wResult = wText0 + wText1;    // &quot;가나&quot;

    // 문자열을 인자로 받을 경우
    MyString String0 = &quot;aaaaaaa&quot;;
    TestMyFunction(String0);
    // 굳이 깊은 복사를 할 필요가 없다

    // 문자열을 복사할 경우
    MyString String1 = MyString(&quot;aaaaaaa&quot;);    // 생성자
    MyString String2 = String0;                // 복사 생성자
    String2 = String0;                        // 대입 연산자 -&gt; 소멸자 단계에서 터진다
    // 이 경우 깊은 복사가 필요하다
    // [TIL] 24-01-08 참고

    // 하지만 동적할당을 남발할 경우 자원을 크게 소모한다는 것을 인지하고 있어야 한다
    // 특히 게임은 프로젝트가 거대한만큼 더 심각하니 필요하지 않은 곳에서는 지양하자
}</code></pre>
<blockquote>
<p>💡 <strong>메모리 단편화</strong>
RAM의 메모리 중간에 틈새가 생기고 정리되어 있지 않은 상태</p>
</blockquote>
<p>메모리를 할당한다 ⇒ 느려진다
할당된 메모리를 삭제한다 ⇒ 느려진다.
새로이 메모리를 할당하기 위해 빈 공간을 찾는다 ⇒ 느려진다</p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/a2113eee-101f-437d-bd87-cce6d183d080/image.png" alt=""></p>
<h2 id="빌드">빌드</h2>
<h3 id="debug--release">Debug / Release</h3>
<pre><code class="language-cpp">/*
[Debug 모드]
- 코드를 최적화하는 과정을 거치지 않고 인간이 인식하는 그대로를 보여준다
- 코드 중에 불피요한 부분을 삭제 또는 치환하는 최적화를 하지 않는다
- 인간이 이해하기 쉬운 모드로 변환하고, 함수의 이름, 변수의 이름 등을 전부 기억해둔다
- int A = 0; 과 같은 변수가 있다면 그대로 A라는 이름으로 남겨두고 사용자에게 보여준다

[Release 모드]
- 최적화가 완료되어 컴퓨터가 가장 빠르다고 생각하는 모습으로 코드를 내부에서 변경시켜버린다
- 중단점이 걸리긴 하지만, 값을 제대로 확인할 수 없기 때문에 의미가 없다
- 마지막에 가서 release 모드로 빌드하면 그때까지의 코드 중 어디서 문제가 발생했는지 알 수 없다
- 따라서 release빌드를 사용할 때에는 자주 빌드해보고 커밋하는 것이 중요하다
- debug 모드에 비해 3~5배 이상 프레임이 오르기 때문에 실제 출시도 release빌드로 한다
*/</code></pre>
<h3 id="x86--x64">x86 / x64</h3>
<pre><code class="language-cpp">/*
[x64] = 64비트

[x86] = 32비트
- 4GB까지의 RAM만을 인식할 수 있다
*/

int main()
{
    int Ptr = sizeof(int*);
    // x64 =&gt; 8바이트
    // x86 =&gt; 4바이트
}</code></pre>
<h2 id="winapi">WinAPI</h2>
<pre><code class="language-cpp">/*
[인터페이스]
어떤 동작을 하는 코드를 사용하기 위한 모든 것
e.g.
소리를 재생하기 위한 함수를 제공한다.
    =&gt; 소리를 재생하기 위한 인터페이스를 제공한다.
소리를 재생하려면 xxx클래스를 사용해야 한다.
    =&gt; 소리를 재생하려면 xxx클래스를 통한 인터페이스를 제공받아야 한다.

[WinAPI]
- API = Application Programming Interface
- Windows를 사용하기 위한 모든 방법을 종합한 인터페이스
(변수, 함수, 클래스, 멤버변수, 멤버함수, ... 등등)
*/</code></pre>
<p><em>솔루션 → 추가 → 새 프로젝트 → Windows 데스크톱 애플리케이션</em>
⇒ 윈도우에서 앱을 만들기 위한 기본적인 방법을 담아둔 프로젝트
⇒ <em>프로젝트 속성 → 구성 속성 → 링커 → 시스템 → 하위 시스템</em>
윈도우 프로젝트 : 창(/SUBSYSTEM:WINDOWS)
콘솔 프로젝트 : 콘솔(/SUBSYSTEM:CONSOLE)</p>
<pre><code class="language-cpp">#include &quot;framework.h&quot;
#include &quot;WindowsProject1.h&quot;

#define MAX_LOADSTRING 100

// 전역 변수 : 
HINSTANCE hInst;                                // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.

// 이 코드 모듈에 포함된 함수의 선언 :
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);


// 진입점 = main() :
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
/*
APIENTRY = __stdcall
- static 멤버함수 등 전역함수 전용 함수호츌규약
- 사실 굳이 적지 않아도 되지만 명시해주는 것
(__cdecl - C전역함수 전용)
(__thiscall - 멤버함수 전용)

_In_
- 아마 여러 환경을 대비한 것이지만,..
- 어짜피 윈도우에서 하기 때문에 삭제해도 된다.
*/
{
    // 사용하지 않은 인자 사용 :
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    /*
    프로젝트 속성 -&gt; 구성 속성 -&gt; C/C++ -&gt; 일반 -&gt; 경고 수준 -&gt; 모든경고사용
    - ...일 경우 인자를 사용하지 않기만 해도 에러가 나기 때문에 이를 회피하기 위함
    - 하지만 경고 수준을 굳이 높일 이유가...?
    (경고를 오류로 처리하는 설정은 켤 수도 있다)
    */

    // TODO: 여기에 코드를 입력합니다.

    // 전역 문자열 초기화 :
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // 애플리케이션 초기화 :
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));

    MSG msg;

    // 기본 메시지 루프 :
    while (GetMessage(&amp;msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &amp;msg))
        {
            TranslateMessage(&amp;msg);
            DispatchMessage(&amp;msg);
        }
    }

    return (int) msg.wParam;
}

/*
함수: MyRegisterClass()
용도: 창 클래스를 등록합니다.
*/
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    // 윈도우 창 클래스 만들기 :
    return RegisterClassExW(&amp;wcex);
    /*
    프로그램이 윈도우에게 창을 띄워달라고 요청할 때, 
    WNDCLASSEW wcex에 입력한 모양으로 띄워달라고 요청하는 것
    그 모양의 정보를 담은 클래스 이름이 swWindowClass
    */
}

/*
함수: InitInstance(HINSTANCE, int)
용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.

이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
주 프로그램 창을 만든 다음 표시합니다.
*/
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   // 인스턴스 핸들 :
   hInst = hInstance;
   /*
   내 프로그램의 제어 권한이자 표식
   인스턴스 핸들을 전역 변수에 저장한다.
   */

   // 윈도우 창 생성 :
   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
   /*
   윈도우를 제어할 수 있는 권한이자 표식
   마치 포인터처럼, 이 인스턴스? 창?에 일련번호를 붙혀주고 그것을 return한다.
   윈도우가 할당되어 있는 진짜 메모리 주소를 주면 위험하므로 대신 이 번호를 받는다.
   */ 

   if (!hWnd)
   {
      return FALSE;
   }

   // 윈도우 창 띄우기 : 
   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

/*
함수: WndProc(HWND, UINT, WPARAM, LPARAM)
용도: 주 창의 메시지를 처리합니다.

WM_COMMAND  - 애플리케이션 메뉴를 처리합니다.
WM_PAINT    - 주 창을 그립니다.
WM_DESTROY  - 종료 메시지를 게시하고 반환합니다.
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석 :
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &amp;ps);
            // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
            EndPaint(hWnd, &amp;ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// 정보 대화 상자 메시지 처리기 :
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == 2)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-16 ~ 24-01-17]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-16</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-16</guid>
            <pubDate>Wed, 17 Jan 2024 01:02:34 GMT</pubDate>
            <description><![CDATA[<p><del>to be written</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-15]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-15</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-15</guid>
            <pubDate>Mon, 15 Jan 2024 19:09:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<ul>
<li>ConsoleEninge 프로젝트에 EngineCore 클래스를 추가하여 엔진의 핵심구성요소를 한곳으로 모으기<ul>
<li>Galaga.cpp에 있던 while문을 EngineCore 클래스로 이동시켜 엔진의 역할과 컨텐츠의 역할 완전 분리</li>
<li>Release 메소드에서 리스트의 삭제방법 공부</li>
<li>AllObjects를 Update용과 Render용으로 분리</li>
</ul>
</li>
<li>ConsoleObject 클래스에 Update 가상함수를 만들고, Player, Bullet, Monster와 같은 Object들에서 override하여 다형성으로 관리</li>
<li>Galaga 프로젝트에 ContentsEnum.h을 추가하여 다형성을 이용하는 Object들을 번호로 구분해주기</li>
<li>&lt;과제&gt; Monster 클래스를 수정하여 몬스터들을 움직이게 만들기</li>
</ul>
<p><del>to be written</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-12]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-12</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-12</guid>
            <pubDate>Fri, 12 Jan 2024 07:51:40 GMT</pubDate>
            <description><![CDATA[<h2 id="함수">함수</h2>
<h3 id="재귀함수-recursive-function">재귀함수 Recursive Function</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;

void Test(int&amp; _Data)
{
    // 종료되는 시점을 꼭 정하고 return시켜야 한다
    if (_Data &gt;= 100)
    {
        return;
    }

    // 함수의 실행메모리 크기는 변수의 크기에 영향을 받는다
    // 따라서, 같은 크기의 스택 메모리라고 가정했을 때,
    // Arr가 없는 경우보다 Arr가 있을 때 Stack Overflow가 발생할 때까지의
    // Test 실행 횟수가 적다
    int Arr[100];
    Arr[0] = _Data;

    std::cout &lt;&lt; _Data &lt;&lt; std::endl;
    ++_Data;

    Test(_Data);    // 재귀함수
}

int main()
{
    int Value = 0;

    Test(Value);
}</code></pre>
<h2 id="자료구조">자료구조</h2>
<blockquote>
<p><strong>💡 자료구조의 분류</strong></p>
</blockquote>
<pre><code class="language-cpp">기준        |  컨테이너    | 메모리형태(참고)
-----   |   -----   |   -----
vector  |   시퀀스   |    배열
list    |   시퀀스   |      노드
map     |    연관    |      노드(트리)
string  |   어댑터   |
stack   |   어댑터   |
queue   |   어댑터   |</code></pre>
<h3 id="map">map</h3>
<pre><code class="language-cpp">/*
[Map]
노드(트리)를 사용하는 연관 컨테이너
  - std::map은 레드블랙 바이너리 서치 트리이다.
  - root로부터 나무모양처럼 뻗어나간다고 해서 노드 형식 중 트리 구조이다.

Node &gt; Tree &gt; (이진, 삼진, ...) + (서치, ...)
  - 트리 구조 중, 자식의 갯수가 2개일 경우 이진 트리 구조이다. =&gt; binary tree
  - 트리 구조 중, 자식 끼리 크기비교를 하게 되면 서치 트리 구조이다. =&gt; search tree

(+)
탐색과 정렬에 특화되어 있는 자료구조이다.
  - 탐색 시, 최대 트리의 depth만큼의 연산만 필요하다.
  - 데이터를 추가할 시, 자동으로 정렬된다.

(-)
탐색과 정렬에 특화되어 있다곤 하지만...
  - 웬만큼 규모가 크지 않은 경우엔 속도에 특화되어 있는 벡터에게 탐색을 시키면
    좀 더 빠르긴 하다(...)
  - 그런데 심지어 배열보다 용량도 몇배다 크다.
  - 특히 언리얼은 배열형 자료구조를 선호하는 편이다.
한쪽에만 치우쳐 있는 편향트리가 생길 수 있다.
  - 레드블랙 알고리즘은 스핀이라는 알고리즘을 통해 적절한 노드에게 루트를 배정하여
    트리의 균형을 잡아준다.
*/

#include &lt;iostream&gt;
#include &lt;Windows.h&gt;
#include &lt;assert.h&gt;
#include &lt;map&gt;

typedef int KeyType;
typedef int DataType;

class MyPair
{
public:
    MyPair()
    {

    }

    MyPair(KeyType _first, DataType _second)
        : Key(_first), Value(_second)
    {

    }

    KeyType Key = KeyType();
    DataType Value = DataType();
};

// template&lt;typename KeyType, typename ValueType&gt;
class MyMap
{
private:
    class MapNode
    {
    public:
        MyPair Pair;
        MapNode* Parent = nullptr;
        MapNode* LeftChild = nullptr;
        MapNode* RightChild = nullptr;

        void insertNode(MapNode* _Node)
        {
            if ( _Node-&gt;Pair.Key &lt; this-&gt;Pair.Key)
            {
                if (this-&gt;LeftChild == nullptr)
                {
                    this-&gt;LeftChild = _Node;
                    return;
                }

                this-&gt;LeftChild-&gt;insertNode(_Node);
            }

            if (_Node-&gt;Pair.Key &gt; this-&gt;Pair.Key)
            {
                if (this-&gt;RightChild == nullptr)
                {
                    this-&gt;RightChild = _Node;
                    return;
                }

                this-&gt;RightChild-&gt;insertNode(_Node);
            }

            return;
        }

        bool containsNode(const KeyType&amp; _Key)
        {
            if (_Key == this-&gt;Pair.Key)
            {
                return true;
            }

            if (_Key &lt; this-&gt;Pair.Key)
            {
                if (this-&gt;LeftChild != nullptr)
                {
                    /*
                    [꼬리 재귀]
                    리턴과 동시에 재귀함수를 호출하는 것
                    - 컴파일러가 (가능하다면) while문 형식으로 바꿔준다.
                    - inline함수와 비슷한 경우로, 최적화에 도움이 된다.
                    */
                    return this-&gt;LeftChild-&gt;containsNode(_Key);
                }
            }

            if (_Key &gt; this-&gt;Pair.Key)
            {
                if (this-&gt;RightChild != nullptr)
                {
                    return this-&gt;RightChild-&gt;containsNode(_Key);
                }
            }

            return false;
        }

        MapNode* findNode(const KeyType&amp; _Key)
        {
            if (_Key == this-&gt;Pair.Key)
            {
                return this;
            }

            if (_Key &lt; this-&gt;Pair.Key)
            {
                if (this-&gt;LeftChild != nullptr)
                {
                    return this-&gt;LeftChild-&gt;findNode(_Key);
                }
            }

            if (_Key &gt; this-&gt;Pair.Key)
            {
                if (this-&gt;RightChild != nullptr)
                {
                    return this-&gt;RightChild-&gt;findNode(_Key);
                }
            }

            return nullptr;
        }

        // Parent 쪽에서 나와의 연결 해제
        void Release()
        {
            if (Parent != nullptr)
            {
                // 내가 LeftChild일 경우
                if (this == Parent-&gt;LeftChild)
                {
                    Parent-&gt;LeftChild = nullptr;
                }

                // 내가 RightChild일 경우
                if (this == Parent-&gt;RightChild)
                {
                    Parent-&gt;RightChild = nullptr;
                }
            }
        }

        // 전위 순회
        void firstOrderPrint()
        {
            std::cout &lt;&lt; Pair.Key &lt;&lt; std::endl;    // 할일

            if (LeftChild != nullptr)
            {
                LeftChild-&gt;firstOrderPrint();
            }

            if (RightChild != nullptr)
            {
                RightChild-&gt;firstOrderPrint();
            }

            return;
        }

        // 중위 순회
        void midOrderPrint()
        {
            if (LeftChild != nullptr)
            {
                LeftChild-&gt;midOrderPrint();
            }

            std::cout &lt;&lt; Pair.Key &lt;&lt; std::endl;    // 할일

            if (RightChild != nullptr)
            {
                RightChild-&gt;midOrderPrint();
            }

            return;
        }

        // 후위 순회
        void lastOrderPrint()
        {
            if (LeftChild != nullptr)
            {
                LeftChild-&gt;lastOrderPrint();
            }

            if (RightChild != nullptr)
            {
                RightChild-&gt;lastOrderPrint();
            }

            std::cout &lt;&lt; Pair.Key &lt;&lt; std::endl;    // 할일

            return;
        }

        // 후위 순회와 유사한 구조이다
        // this를 delete하기 전에 필요한 일을 모두 마쳐야하기 때문
        void clearNode()
        {
            if (this-&gt;LeftChild != nullptr)
            {
                LeftChild-&gt;clearNode();
            }

            if (this-&gt;RightChild != nullptr)
            {
                RightChild-&gt;clearNode();
            }

            delete this;

            return;
        }

        MapNode* minnode()
        {
            if (this-&gt;LeftChild == nullptr)
            {
                return this;
            }

            return LeftChild-&gt;minnode();
        }

        MapNode* maxnode()
        {
            if (this-&gt;RightChild == nullptr)
            {
                return this;
            }

            return RightChild-&gt;maxnode();
        }

        // 재귀함수로도 가능
        MapNode* SmallParent()
        {
            MapNode* PNode = Parent;

            while (PNode)
            {
                // 나의 Parent의 Key값이 나의 Key값보다 작다면,
                if (Pair.Key &gt; PNode-&gt;Pair.Key)
                {
                    // Parent가 반환되어 PrevNode가 된다
                    return PNode;
                }

                // 그렇지 않다면, Parent의 Parent를 탐색
                PNode = PNode-&gt;Parent;
            }

            return nullptr;
        }

        // 재귀함수로도 가능
        MapNode* OverParent()
        {
            MapNode* PNode = Parent;

            while (PNode)
            {
                // 나의 Parent의 Key값이 나의 Key값보다 크다면,
                if (Pair.Key &lt; PNode-&gt;Pair.Key)
                {
                    // Parent가 반환되어 NextNode가 된다
                    return PNode;
                }

                // 그렇지 않다면, Parent의 Parent를 탐색
                PNode = PNode-&gt;Parent;
            }

            return nullptr;
        }

        MapNode* PrevNode()
        {
            // LeftChild가 존재할 경우
            if (this-&gt;LeftChild != nullptr)
            {
                return this-&gt;LeftChild-&gt;maxnode();
            }

            // LeftChild가 존재하지 않을 경우, 부모를 탐색
            return SmallParent();
        }

        MapNode* NextNode()
        {
            // RightChild가 존재할 경우
            if (this-&gt;RightChild != nullptr)
            {
                return this-&gt;RightChild-&gt;minnode();
            }

            // RightChild가 존재하지 않을 경우, 부모를 탐색
            return OverParent();
        }

        bool IsLeaf()
        {
            return nullptr == LeftChild &amp;&amp; nullptr == RightChild;
        }
    };

public:
    class iterator
    {
        friend MyMap;

    public:
        iterator()
        {

        }

        iterator(MapNode* _CurNode)
            : CurNode(_CurNode)
        {

        }

        MyPair* operator-&gt;()
        {
            MyPair&amp; MapPair = CurNode-&gt;Pair;
            return &amp;MapPair;    // -&gt; 연산자는 주소값을 반환한다
        }

        bool operator!=(const iterator&amp; _Other)
        {
            return CurNode != _Other.CurNode;
        }

        void operator++()
        {
            CurNode = CurNode-&gt;NextNode();
            return;
        }

    private:
        MapNode* CurNode = nullptr;
    };

    ~MyMap()
    {
        clear();
    }

    /*
    Tip
    인자는 레퍼런스로 받는게 이득이다.
    어짜피 인자는 8byte씩 할당되기 때문에 bool이라도 레퍼런스가 이득이다.
    */

    /*
    map은 자료가 무작위일때, 다른 자료구조에 비해 높은 효율을 자랑한다.
    자료가 이미 특정 기준 하에 정렬된 상태로 들어간다면 대부분의 경우,
    다른 자료구조들이 map보다 빠르다.
    */
    void insert(const MyPair&amp; _Value)
    {
        MapNode* NewNode = new MapNode();
        NewNode-&gt;Pair = _Value;


        // 트리에서 최초 노드는 무조건 뿌리 노드가 된다
        if (Root == nullptr)
        {
            Root = NewNode;
            return;
        }

        Root-&gt;insertNode(/*Root, */NewNode);
    }

    bool contains(const KeyType&amp; _Key)
    {
        if (Root == nullptr)
        {
            return false;
        }

        return Root-&gt;containsNode(_Key);
    }

    iterator find(const KeyType&amp; _Key)
    {
        if (Root == nullptr)
        {
            return end();
        }

        return iterator(Root-&gt;findNode(_Key));
    }

    iterator erase(iterator&amp; _Iter)
    {
        iterator Return;

        if (_Iter.CurNode == nullptr)
        {
            MessageBoxA(nullptr, &quot;유효하지 않은 원소를 삭제하려고 했습니다.&quot;, &quot;치명적 에러&quot;, MB_OK);
            assert(false);

            return Return;
        }

        // erase()하는 노드의 NextNode를 반환
        Return.CurNode = _Iter.CurNode-&gt;NextNode();

        /*
        상황 1. 자식이 없는 리프노드일 경우
        - 대체할 노드가 필요하지 않다
        - Parent 노드에게 알려야 한다
        */

        if (_Iter.CurNode-&gt;IsLeaf() == true)
        {
            // 삭제할 노드의 부모와 연결 끊기
            _Iter.CurNode-&gt;Release();

            delete _Iter.CurNode;

            return Return;
        }

        /*
        상황 2. 자식이 있는 노드일 경우
        - LeftChild의 maxnode 또는 RightChild의 minnode가 대체한다
        - 둘 중 어느것이든 괜찮기 때문에 코드 짜는 사람이 선택 가능하다
        */

        MapNode* ChangeNode = nullptr;        // 대체할 노드
        MapNode* CurNode = _Iter.CurNode;    // 삭제할 노드

        // RightChild의 minnode를 대체할 노드로 지정
        ChangeNode = CurNode-&gt;RightChild-&gt;minnode();
        if (ChangeNode == nullptr)
        {
            // 안될 경우, LeftChild의 maxnode를 대체할 노드로 지정
            ChangeNode = CurNode-&gt;LeftChild-&gt;maxnode();
            if (ChangeNode == nullptr)
            {
                MessageBoxA(nullptr, &quot;체인지 노드 에러.&quot;, &quot;치명적 에러&quot;, MB_OK);
                assert(false);

                return Return;
            }
        }

        // 대체할 노드의 부모와 연결 끊기
        ChangeNode-&gt;Release();

        // 삭제할 노드의 자식과 연결 끊기, 대체할 노드를 새로운 자식과 연결
        MapNode* LeftChild = CurNode-&gt;LeftChild;
        MapNode* RightChild = CurNode-&gt;RightChild;
        /*
        if (LeftChild != nullptr)
        {
            LeftChild-&gt;Parent = nullptr;
        }

        if (RightChild != nullptr)
        {
            RightChild-&gt;Parent = nullptr;
        }
        */
        if (LeftChild != nullptr)
        {
            LeftChild-&gt;Parent = ChangeNode;
            if (LeftChild != ChangeNode)
            {
                ChangeNode-&gt;LeftChild = LeftChild;
            }
        }

        if (RightChild != nullptr)
        {
            RightChild-&gt;Parent = ChangeNode;
            if (RightChild != ChangeNode)
            {
                ChangeNode-&gt;RightChild = RightChild;
            }
        }

        // 삭제할 노드의 부모와 연결 끊기, 대체할 노드를 새로운 부모와 연결
        ChangeNode-&gt;Parent = CurNode-&gt;Parent;

        MapNode* Parent = ChangeNode-&gt;Parent;

        if (Parent != nullptr &amp;&amp; Parent-&gt;LeftChild == CurNode)
        {
            Parent-&gt;LeftChild = ChangeNode;
        }

        if (Parent != nullptr &amp;&amp; Parent-&gt;RightChild == CurNode)
        {
            Parent-&gt;RightChild = ChangeNode;
        }

        // 삭제할 노드가 root였을 경우
        if (Root == CurNode)
        {
            Root = ChangeNode;
        }

        delete CurNode;

        return Return;
    }

    iterator begin()
    {
        if (Root == nullptr)
        {
            return end();
        }

        return iterator(Root-&gt;minnode());
    }

    iterator end()
    {
        return iterator(nullptr);
    }

    void firstOrderPrint()
    {
        Root-&gt;firstOrderPrint();
        return;
    }

    void midOrderPrint()
    {
        Root-&gt;midOrderPrint();
        return;
    }

    void lastOrderPrint()
    {
        Root-&gt;lastOrderPrint();
        return;
    }

    void clear()
    {
        Root-&gt;clearNode();
        Root = nullptr;

        return;
    }

private:
    MapNode* Root = nullptr;
};

int main()
{
    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);

    {
        std::cout &lt;&lt; &quot;std::map&quot; &lt;&lt; std::endl;

        // [선언] key, value
        std::map&lt;int, int&gt; NewMap = std::map&lt;int, int&gt;();

        /*
        [자료 추가] 방법이 네가지나 있다
        1. pair
        NewMap.insert(std::pair&lt;int, int&gt;(1, 123456789));

        2. make_pair
        NewMap.insert(std::make_pair(1, 123456789));

        3. 배열 연산자
        NewMap[3] = 1;

        4. value_type
        NewMap.insert(std::map&lt;int, int&gt;::value_type(7, 10));
        // std::map&lt;int, int&gt;::value_type == std::pair&lt;int, int&gt;
        */
        NewMap.insert(std::pair&lt;int, int&gt;(10, 0));
        NewMap.insert(std::pair&lt;int, int&gt;(5, 0));
        NewMap.insert(std::pair&lt;int, int&gt;(15, 0));
        NewMap.insert(std::pair&lt;int, int&gt;(12, 0));
        NewMap.insert(std::pair&lt;int, int&gt;(3, 0));
        NewMap.insert(std::pair&lt;int, int&gt;(7, 0));
        // 자동으로 오름차순 정렬이 된다
        // 3, 5, 7, 10, 12, 15

        /*
        [탐색]
        1. contains() (C++20 에서만 사용 가능)
        존재하는지 여부를 bool로 반환

        NewMap.contains(15);

        if (NewMap.contains(15) == true)
        {
            // 존재한다
        }

        2. find()
        탐색한 결과를 iterator로 반환
        */
        std::map&lt;int, int&gt;::iterator FindIter = NewMap.find(15);

        if (FindIter != NewMap.end())
        {
            // 존재한다
        }

        // [자료 삭제]
        NewMap.erase(FindIter);

        // [순회]
        // map은 노드의 갯수가 많아질수록 순회를 돌리는 과정이 효율적이지 못하다
        // 무수한 반복문 또는 재귀함수를 호출해야 하기 때문
        std::map&lt;int, int&gt;::iterator StartIter = NewMap.begin();
        std::map&lt;int, int&gt;::iterator EndIter = NewMap.end();

        for (; StartIter != EndIter; ++StartIter)
        {
            std::cout &lt;&lt; &quot;Key : &quot; &lt;&lt; StartIter-&gt;first &lt;&lt; std::endl;
            // std::cout &lt;&lt; &quot;Value : &quot; &lt;&lt; StartIter-&gt;second &lt;&lt; std::endl;
        }
    }
    {
        std::cout &lt;&lt; &quot;MyMap&quot; &lt;&lt; std::endl;

        // 선언
        MyMap NewMap = MyMap();

        // 자료 추가
        NewMap.insert(MyPair(10, 0));
        NewMap.insert(MyPair(5, 0));
        NewMap.insert(MyPair(15, 0));
        NewMap.insert(MyPair(12, 0));
        NewMap.insert(MyPair(3, 0));
        NewMap.insert(MyPair(7, 0));

        // 탐색
        NewMap.contains(12);

        MyMap::iterator FindIter = NewMap.find(12);

        // 삭제
        // NewMap.erase(FindIter);

        // 순회
        MyMap::iterator StartIter = NewMap.begin();
        MyMap::iterator EndIter = NewMap.end();

        for (; StartIter != EndIter; ++StartIter)
        {
            std::cout &lt;&lt; StartIter-&gt;Key &lt;&lt; std::endl;
            //std::cout &lt;&lt; StartIter-&gt;second &lt;&lt; std::endl;
        }

        std::cout &lt;&lt; &quot;first&quot; &lt;&lt; std::endl;
        NewMap.firstOrderPrint();    // 10 5 3 7 15 12

        std::cout &lt;&lt; &quot;mid&quot; &lt;&lt; std::endl;
        NewMap.midOrderPrint();        // 3 5 7 10 12 15

        std::cout &lt;&lt; &quot;last&quot; &lt;&lt; std::endl;
        NewMap.lastOrderPrint();    // 3 7 5 12 15 10
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-11]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-11</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-11</guid>
            <pubDate>Thu, 11 Jan 2024 07:49:27 GMT</pubDate>
            <description><![CDATA[<h2 id="클래스">클래스</h2>
<h3 id="inner-class">Inner Class</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;vector&gt;

class Image
{

};

class Inventory
{
private:
    // 속해 있는 개념일 경우 바깥에 따로 두어도 상관은 없지만,
    // 이너클래스를 private 안에 둠으로써 바깥에서 접근할 수 없는 클래스로 만들 수 있다.
    class InventoryIcon    // [Inner Class]
    {
    public:
        void Test()
        {
            Icons;    // 불가능
            //InventoryIcon은 개념적으로 Inventory에 속해 있다고 생각되어
            //이너클래스로 작성되었을 뿐, 실제론 Inventory 바깥에 있는 별개의 클래스와 같다.
        }

    private:
        Image Icon;
        int Count;
    };

    std::vector&lt;InventoryIcon&gt; Icons;
};

int main()
{
    Inventory Inventory;

    // Inventory::InventoryIcon Icon;
    // 중첩된 클래스는 (public일 경우) ::으로 접근할 수 있다.
}</code></pre>
<h3 id="friend">friend</h3>
<pre><code class="language-cpp">/*
[friend]
기본적으로 서로 다른 클래스 간에 private에는 접근할 수 없다.
하지만 friend 선언을 해주면 예외를 만들 수 있다.
(사실 객체지향적으로는 불가능하지만, C++에서는 가능하다)

(+)
- 특정 경우메만 접근을 허용하고 싶을 때, 해당 멤버를 public으로 만들지 않아도 된다

(-)
- 남발할수록 좋지 못한 문법이다
- 다른 클래스의 private에 접근해야 할 경우에, 다른 구조적 방법으로 해결할 생각부터 해보자
*/

class A
{
    friend class B;                            // B 클래스 전체가 firend
    // friend void B::BFunction(A&amp; _Other);    // B 클래스의 BFunction만 friend

private:
    int Value;
};

class B
{
public:
    void Test(A&amp; _Other)
    {
        _Other.Value;    // 접근 가능
    }

private:
    void BFunction(A&amp; _Other)
    {
        _Other.Value;    // 접근 가능
    }
};

class C
{
public:
    void Test(A&amp; _Other)
    {
        _Other.Value;    // 접근 불가능
    }
};</code></pre>
<h2 id="자료구조">자료구조</h2>
<h3 id="list">list</h3>
<pre><code class="language-cpp">/*
[List]
노드를 사용하는 시퀀스 컨테이너
  - std::list는 양방향 노드를 사용하는 양방향 리스트이다.
  - 벡터와 달리 랜덤엑세스가 불가하다
    VectorValue[0]    (가능)
    ListValue[0]    (불가능)
*/
#include &lt;iostream&gt;
#include &lt;Windows.h&gt;
#include &lt;assert.h&gt;
#include &lt;list&gt;

typedef int DataType;

// MyList 구현
class MyList
{
private:
    class ListNode    // 이너클래스
    {
    public:    // 어짜피 ListNode는 MyList 안에서만 사용되므로 public이어도 된다
        DataType Data = DataType();    // 노드는 데이터를 1개 받는다
        ListNode* Next = nullptr;
        ListNode* Prev = nullptr;
    };

public:
    /*
    [iterator]
    자료구조의 순회를 담당하는 클래스
    - std의 거의 모든 자료구조는 이터레이터라는 통일된 인터페이스를 사용한다.
    - 사실 vector도 이터레이터를 활용하여 내부 전체를 순회할 수 있다.
    */
    class iterator
    {
    public:
        friend MyList;
        // MyList의 erase()에서 iterator의 private 멤버변수인 CurNode에 접근하기 위해 firend 선언

        /*
        friend MyList;            =&gt; friend 선언
        friend class MyList;    =&gt; MyList 클래스 전방선언과 동시에 friend 선언
        여기선 이미 MyList를 알고 있기 때문에 전방선언할 필요가 없다.
        */

        iterator()
        {

        }

        iterator(ListNode* _CurNode)
            : CurNode(_CurNode)
        {

        }

        bool operator!=(const iterator&amp; _Other)
        {
            return CurNode != _Other.CurNode;
        }

        void operator++()
        {
            CurNode = CurNode-&gt;Next;
        }

        DataType&amp; operator*()
        {
            return CurNode-&gt;Data;
        }

    private:
        ListNode* CurNode = nullptr;
    };

public:
    MyList()
    {
        Start-&gt;Next = End;
        End-&gt;Prev = Start;
    }

    ~MyList()
    {
        // Start, End 포함 모든 노드의 메모리 반환
        ListNode* CurNode = Start;
        while (CurNode)
        {
            ListNode* Next = CurNode-&gt;Next;
            if (CurNode != nullptr)
            {
                delete CurNode;
                CurNode = Next;
            }
        }
    }

    iterator begin()
    {
        return iterator(Start-&gt;Next);
    }

    iterator end()
    {
        return iterator(End);
    }

    // End의 Prev에 새로운 데이터 추가
    void push_back(const DataType&amp; _Data)
    {
        ListNode* NewNode = new ListNode();

        NewNode-&gt;Data = _Data;
        NewNode-&gt;Next = End;
        NewNode-&gt;Prev = End-&gt;Prev;

        ListNode* PrevNode = NewNode-&gt;Prev;
        ListNode* NextNode = NewNode-&gt;Next;

        PrevNode-&gt;Next = NewNode;
        NextNode-&gt;Prev = NewNode;
    }

    // Start의 Next에 새로운 데이터 추가
    void push_front(const DataType&amp; _Data)
    {
        ListNode* NewNode = new ListNode();

        NewNode-&gt;Data = _Data;
        NewNode-&gt;Next = Start-&gt;Next;
        NewNode-&gt;Prev = Start;

        ListNode* PrevNode = NewNode-&gt;Prev;
        ListNode* NextNode = NewNode-&gt;Next;

        PrevNode-&gt;Next = NewNode;
        NextNode-&gt;Prev = NewNode;
    }

    // 현재 노드를 지우고, 다음 노드를 return
    iterator erase(iterator&amp; _Iter)
    {
        if (_Iter.CurNode == Start)
        {
            MessageBoxA(nullptr, &quot;Start를 삭제하려고 했습니다.&quot;, &quot;치명적 에러&quot;, MB_OK);
            assert(false);
        }

        if (_Iter.CurNode == End)
        {
            MessageBoxA(nullptr, &quot;End를 삭제하려고 했습니다.&quot;, &quot;치명적 에러&quot;, MB_OK);
            assert(false);
        }

        iterator ReturnIter;

        if (_Iter.CurNode != nullptr)
        {
            ReturnIter = iterator(_Iter.CurNode-&gt;Next);

            ListNode* PrevNode = _Iter.CurNode-&gt;Prev;
            ListNode* NextNode = _Iter.CurNode-&gt;Next;

            PrevNode-&gt;Next = NextNode;
            NextNode-&gt;Prev = PrevNode;

            delete _Iter.CurNode;
            _Iter.CurNode = nullptr;
        }

        return ReturnIter;
    }

private:
    // 시작과 끝을 표현하는 더미노드
    ListNode* Start = new ListNode();
    ListNode* End = new ListNode();
};

int main()
{
    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);

    {
        std::cout &lt;&lt; &quot;std::list&quot; &lt;&lt; std::endl;
        std::list&lt;int&gt; NewList = std::list&lt;int&gt;();

        for (int i = 0; i &lt; 10; i++)
        {
            NewList.push_back(i);
            // NewList.push_front(i);
        }

        std::list&lt;int&gt;::iterator StartIter = NewList.begin();
        std::list&lt;int&gt;::iterator EndIter = NewList.end();

        StartIter = NewList.erase(StartIter);

        for (/*std::list&lt;int&gt;::iterator StartIter = NewList.begin();*/
            ; StartIter != EndIter; ++StartIter)
        {
            std::cout &lt;&lt; *StartIter &lt;&lt; std::endl;
        }
    }
    {
        std::cout &lt;&lt; &quot;MyList&quot; &lt;&lt; std::endl;
        MyList NewList = MyList();

        for (int i = 0; i &lt; 10; i++)
        {
            NewList.push_back(i);
            // NewList.push_front(i);
        }

        MyList::iterator StartIter = NewList.begin();
        MyList::iterator EndIter = NewList.end();

        StartIter = NewList.erase(StartIter);

        for (; StartIter != EndIter; ++StartIter)
        {
            std::cout &lt;&lt; *StartIter &lt;&lt; std::endl;
        }

        /*
        만약 소멸자에서 동적할당된 메모리를 반환하지 않았다면,
        Start, 1, 2, 3, 4, 5, 6, 7, 8, 9, End
        이렇게 총 11개의 노드에서 각각 24bytes씩 memory leak이 발생한다.
        */
    }
}</code></pre>
<hr>
<h2 id="과제">과제</h2>
<h3 id="240111_rlist">240111_RList</h3>
<pre><code class="language-cpp">// &lt;과제&gt; reverse_iterator를 만들자
#include &lt;iostream&gt;
#include &lt;list&gt;
#include &lt;ConsoleEngine/EngineDebug.h&gt;

typedef int DataType;

class MyList
{
private:
    class ListNode
    {
    public:
        DataType Data = DataType();
        ListNode* Next = nullptr;
        ListNode* Prev = nullptr;
    };

    // iterator와 reverse_iterator는 공통된 코드가 많기 때문에
    // 공통된 부분을 묶어 iterator_Base라는 부모 클래스를 만들어준다
    class iterator_Base
    {
    public:
        iterator_Base()
        {

        }

        iterator_Base(ListNode* _CurNode)
            : CurNode(_CurNode)
        {

        }

        DataType&amp; operator*()
        {
            return CurNode-&gt;Data;
        }

        bool operator!=(const iterator_Base&amp; _Other)
        {
            return CurNode != _Other.CurNode;
        }

        ListNode* CurNode = nullptr;
    };

public:
    class iterator : public iterator_Base
    {
        friend MyList;

    public:
        iterator()
        {

        }

        iterator(ListNode* _CurNode)
            : iterator_Base(_CurNode)
        {

        }

        void operator++()
        {
            CurNode = CurNode-&gt;Next;
        }
    };

    // 과제
    class reverse_iterator : public iterator_Base    // 상속
    {
        friend MyList;

    public:
        reverse_iterator()
        {

        }

        reverse_iterator(ListNode* _CurNode)
            : iterator_Base(_CurNode)
        {

        }

        void operator++()
        {
            CurNode = CurNode-&gt;Prev;
        }
    };

    MyList()
    {
        Start-&gt;Next = End;
        End-&gt;Prev = Start;
    }

    ~MyList()
    {
        ListNode* CurNode = Start;
        while (CurNode)
        {
            ListNode* Next = CurNode-&gt;Next;
            if (nullptr != CurNode)
            {
                delete CurNode;
                CurNode = Next;
            }
        }
    }

    iterator begin()
    {
        return iterator(Start-&gt;Next);
    }

    iterator end()
    {
        return iterator(End);
    }

    // 과제
    reverse_iterator rbegin()
    {
        return reverse_iterator(End-&gt;Prev);
    }

    // 과제
    reverse_iterator rend()
    {
        return reverse_iterator(Start);
    }

    void push_back(const DataType&amp; _Data)
    {
        ListNode* NewNode = new ListNode();
        NewNode-&gt;Data = _Data;

        NewNode-&gt;Next = End;
        NewNode-&gt;Prev = End-&gt;Prev;

        ListNode* PrevNode = NewNode-&gt;Prev;
        ListNode* NextNode = NewNode-&gt;Next;

        PrevNode-&gt;Next = NewNode;
        NextNode-&gt;Prev = NewNode;
    }

protected:

private:
    ListNode* Start = new ListNode();
    ListNode* End = new ListNode();
};

int main()
{
    LeakCheck;

    {
        std::cout &lt;&lt; &quot;std::list&quot; &lt;&lt; std::endl;
        std::list&lt;int&gt; NewList = std::list&lt;int&gt;();

        for (int i = 0; i &lt; 5; i++)
        {
            NewList.push_back(i);    // 0, 1, 2, 3, 4
        }

        std::list&lt;int&gt;::reverse_iterator rStartIter = NewList.rbegin();
        std::list&lt;int&gt;::reverse_iterator rEndIter = NewList.rend();

        for (; rStartIter != rEndIter; ++rStartIter)
        {
            std::cout &lt;&lt; *rStartIter &lt;&lt; std::endl;    // 4, 3, 2, 1, 0
        }
    }

    {
        std::cout &lt;&lt; &quot;MyList&quot; &lt;&lt; std::endl;
        MyList NewList = MyList();

        for (int i = 0; i &lt; 5; i++)
        {
            NewList.push_back(i);    // 0, 1, 2, 3, 4
        }

        MyList::reverse_iterator rStartIter = NewList.rbegin();
        MyList::reverse_iterator rEndIter = NewList.rend();

        for (; rStartIter != rEndIter; ++rStartIter)
        {
            std::cout &lt;&lt; *rStartIter &lt;&lt; std::endl;    // 4, 3, 2, 1, 0
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-10]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-10</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-10</guid>
            <pubDate>Wed, 10 Jan 2024 07:28:59 GMT</pubDate>
            <description><![CDATA[<h2 id="자료구조">자료구조</h2>
<p>자료구조를 코딩으로 공부하는 가장 좋은 방법 ⇒ 똑같이 구현해보기!</p>
<blockquote>
<p>💡 <strong>자료구조의 분류</strong></p>
</blockquote>
<pre><code class="language-cpp">기준        |  컨테이너    | 메모리형태(참고)
-----   |   -----   |   -----
vector  |   시퀀스   |    배열
list    |   시퀀스   |    노드
map     |    연관    |
string  |   어댑터   |
stack   |   어댑터   |
queue   |   어댑터   |</code></pre>
<h3 id="vector">vector</h3>
<pre><code class="language-cpp">/*
[Vector]
- 시퀸스 컨테이너의 일종
- 배열기반 메모리 구조를 가지고 있다.
    [][][][][][][][]...

(+)
웬만한 자료구조보다 훨씬 빠른 자료구조에 해당한다.

(-)
유동적으로 자료의 크기가 변하는 개념을 표현할 때에는 알맞지 않다.
자료가 한 번 추가되면 그 자료를 삭제하는 것이 어렵고 힘들기 때문이다.
한 번 push_back() 해서 확장된 공간을 줄이는 것도 어렵다.
*/

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;list&gt;
#include &lt;Windows.h&gt;
#include &lt;assert.h&gt;

template&lt;typename DataType&gt;
class CArray
{
public:
    CArray()
    {

    }

    CArray(int _Size)
    {
        ReSize(_Size);
    }

    CArray(const CArray&amp; _Other)
    {
        Copy(_Other);
    }

    ~CArray()
    {
        Release();
    }

    void operator=(const CArray&amp; _Other)
    {
        Copy(_Other);
    }

    DataType&amp; operator[](int _Count)
    {
        return ArrPtr[_Count];
    }

    int Num()
    {
        return NumValue;
    }

    void Copy(const CArray&amp; _Other)
    {
        NumValue = _Other.NumValue;

        ReSize(NumValue);
        for (int i = 0; i &lt; NumValue; i++)
        {
            ArrPtr[i] = _Other.ArrPtr[i];
        }
    }

    void ReSize(int _Size)
    {
        if (0 &gt;= _Size)
        {
            MessageBoxA(nullptr, &quot;배열의 크기가 0일수 없습니다&quot;, &quot;치명적 에러&quot;, MB_OK);
            assert(false);
        }

        DataType* Ptr = ArrPtr;
        ArrPtr = new DataType[_Size];

        int CopySize = NumValue &lt;= _Size ? NumValue : _Size;

        for (int i = 0; i &lt; CopySize; i++)
        {
            ArrPtr[i] = Ptr[i];
        }

        NumValue = _Size;

        if (nullptr != Ptr)
        {
            delete[] Ptr;
            Ptr = nullptr;
        }
    }

    void Release()
    {
        if (nullptr != ArrPtr)
        {
            delete[] ArrPtr;
            ArrPtr = nullptr;
        }
    }

private:
    int NumValue = 0;
    DataType* ArrPtr = nullptr;
};

// CArray를 이용한 MyVector 구현
template&lt;typename DataType&gt;
class MyVector
{
public:
    DataType&amp; operator[](size_t _Index)
    {
        if (size() &lt;= _Index)
        {
            MessageBoxA(nullptr, &quot;인덱스를 넘겨서 접근하려고 했습니다.&quot;, &quot;치명적 에러&quot;, MB_OK);
            assert(false);
        }

        return Array[_Index];
    }

    void push_back(const DataType&amp; _Data)
    {
        if (sizeValue + 1 &gt;= capacity())
        {
            Array.ReSize((Array.Num() + 1) * 2);
        }

        Array[sizeValue] = _Data;
        ++sizeValue;
    }

    void emplace_back()
    {
        if (sizeValue + 1 &gt;= capacity())
        {
            Array.ReSize((Array.Num() + 1) * 2);
        }

        Array[sizeValue] = DataType();
        ++sizeValue;
    }

    void resize(int _Size)
    {
        sizeValue = _Size;
        Array.ReSize(_Size);
    }

    void reserve(int _Size)
    {
        Array.ReSize(_Size);
    }

    void clear()
    {
        sizeValue = 0;
    }

    size_t size()
    {
        return sizeValue;
    }

    size_t capacity()
    {
        return Array.Num();
    }

protected:
private:
    int sizeValue = 0;
    CArray&lt;int&gt; Array;
};

int main()
{
    /*
    std::vector&lt;int&gt;();
    =&gt;  
        - 내부에서 resize()를 실행한다
        - 크기가 없는 vector를 만들 수 있다 (이후에 push_back() 등으로 추가)

    puch_back();
    =&gt; 메모리를 할당하고, 할당한 메모리에 데이터를 넣는다
        - 공간을 확장하는 순서
            확장된 공간을 새로이 만든다 -&gt; 기존의 데이터를 확장된 공간에 복사해준다
            -&gt; 기존의 공간을 delete한다 -&gt; 확장된 공간을 vector 안에 넣어준다
            -&gt; 추가할 데이터를 확장된 공간에 넣어준다
        - 확장 시 필요한 크기보다 좀 더 크게 확장한다
            - 데이터가 들어가 있는 공간의 크기 : size()
            - 실제 vector 크기 : capacity()
            - 이 때문에 데이터가 들어가 있지 않은 공간이 생긴다 (capacity - size 만큼)

    emplace_back();
    =&gt; push_back()과 동일하지만, 공간만 확장시킨다

    resize();
    =&gt; 미리 push_back()을 n번 해서 확장해준다
        - 직접적으로 resize()를 쓰지는 않는 것 같다...아마도

    reserve();
    =&gt; 미리 데이터 n개를 확실히 사용하겠다고 capacity를 정해 확장해준다
        - capacity가 변할 때 기존의 배열이 delete되고 새로운 배열이 new되는 것인데,
          C++에서 new와 delete는 가장 부하가 심한 연산에 포함된다
        - 따라서 vector 사용 전에 reserve()해주면 부하를 줄일 수 있다
        - 이 때문에 vector는 크기를 확장할 수 있는 정적배열로서 사용된다

    clear();
    =&gt; vector 내부의 데이터를 전부 지운다
        - size를 0으로 만들어준다
        - capacity는 해당되지 않기 때문에, 실제론 vector가 존재하는 것과 같다
    */

    {
        std::cout &lt;&lt; &quot;Std Vector&quot; &lt;&lt; std::endl;

        std::vector&lt;int&gt; ArrVector = std::vector&lt;int&gt;();

        for (int i = 0; i &lt; 10; i++)
        {
            ArrVector.emplace_back();   // ArrVector.push_back(i);
            std::cout &lt;&lt; &quot;capacity : &quot; &lt;&lt; ArrVector.capacity() &lt;&lt; std::endl;
            std::cout &lt;&lt; &quot;size : &quot; &lt;&lt; ArrVector.size() &lt;&lt; std::endl;
        }

        ArrVector.clear();
        std::cout &lt;&lt; &quot;Clear &quot; &lt;&lt; std::endl;
        std::cout &lt;&lt; &quot;capacity : &quot; &lt;&lt; ArrVector.capacity() &lt;&lt; std::endl;
        std::cout &lt;&lt; &quot;size : &quot; &lt;&lt; ArrVector.size() &lt;&lt; std::endl;
    }
    {
        std::cout &lt;&lt; std::endl;
        std::cout &lt;&lt; &quot;MyVector&quot; &lt;&lt; std::endl;

        MyVector&lt;int&gt; ArrVector = MyVector&lt;int&gt;();

        for (int i = 0; i &lt; 10; i++)
        {
            ArrVector.emplace_back();   // ArrVector.push_back(i);
            std::cout &lt;&lt; &quot;capacity : &quot; &lt;&lt; ArrVector.capacity() &lt;&lt; std::endl;
            std::cout &lt;&lt; &quot;size : &quot; &lt;&lt; ArrVector.size() &lt;&lt; std::endl;
        }

        ArrVector.clear();
        std::cout &lt;&lt; &quot;Clear &quot; &lt;&lt; std::endl;
        std::cout &lt;&lt; &quot;capacity : &quot; &lt;&lt; ArrVector.capacity() &lt;&lt; std::endl;
        std::cout &lt;&lt; &quot;size : &quot; &lt;&lt; ArrVector.size() &lt;&lt; std::endl;
    }
    {
        // 다른 자료구조와의 차이점

        /*
        1. 정적배열과의 차이점
        - 벡터는 공간과 데이터의 개념을 분리한다(중요!)
        - 공간이 할당된 것과 데이터가 들어가 있는 것은 별개
        */
        int Arr[10];
        Arr[0] = 20;    // [20][?][?][?][?][?][?][?][?][?]
        Arr[2];         // 배열에서는 가능, 벡터에서는 불가능

        /*
        2. list와의 차이점
        - list에는 존재하는 push_front()가 불가능하다
        - 데이터의 복사가 구성원의 수만큼 일어나야 하기 때문에 엄청난 부담이기 때문
        */
        std::list&lt;int&gt; MyList;
        MyList.push_back(0);
        MyList.push_front(0);
    }
}</code></pre>
<pre><code class="language-cpp">// 궁금해서 찾아본...
// Q) 혹시 한 인덱스의 데이터를 erase()로 삭제하게 되면 어떤일이 발생할까?
#include &lt;iostream&gt;
#include &lt;vector&gt;

int main()
{
    std::vector&lt;int&gt; ArrVector = std::vector&lt;int&gt;();

    for (int i = 0; i &lt; 10; i++)
    {
        ArrVector.push_back(i); // [0][1][2][3][4][5][6][7][8][9]
    }

    std::cout &lt;&lt; &quot;capacity : &quot; &lt;&lt; ArrVector.capacity() &lt;&lt; std::endl;    // 13
    std::cout &lt;&lt; &quot;size : &quot; &lt;&lt; ArrVector.size() &lt;&lt; std::endl;    // 10

    ArrVector.erase(ArrVector.begin() + 3);
    // [0][1][2][?][4][5][6][7][8][9]   (X)
    // [0][1][2][4][5][6][7][8][9]      (O)

    for (int i = 0; i &lt; ArrVector.size(); i++)
    {
        std::cout &lt;&lt; ArrVector[i] &lt;&lt; std::endl;
        std::cout &lt;&lt; reinterpret_cast&lt;int&gt;(&amp;ArrVector[i]) &lt;&lt; std::endl; // 4씩 차이난다
    }

    std::cout &lt;&lt; &quot;capacity : &quot; &lt;&lt; ArrVector.capacity() &lt;&lt; std::endl;    // 13
    std::cout &lt;&lt; &quot;size : &quot; &lt;&lt; ArrVector.size() &lt;&lt; std::endl;    // 9
}
// size가 1 줄어든다
// capacity는 변하지 않는다
// erase된 index 3 이후의 데이터들이 앞으로 한 칸씩 이동한다
// 결과적으로 index 8 까지만 데이터가 차있는 배열이 된다</code></pre>
<blockquote>
<p>💡<strong>int라도, vector라도, class라도 얼마든지 같은 경우이다!</strong>
복잡하다고 생각하지 말고 int로 치환해서 생각해보자</p>
</blockquote>
<pre><code class="language-cpp">#include &lt;iostream&gt;
&gt;
class Test
{
public:
    void TestFunction()
    {
        *Ptr = 20;  // Ptr.push_back(20);
    }
&gt;
public:
    int* Ptr;   // std::vector&lt;int&gt;* Ptr;
};
&gt;
int main()
{
    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
&gt;
    int Value = 0;  // std::vector&lt;int&gt; Value;
    Test NewTest;
&gt;
    NewTest.Ptr = &amp;Value;
    NewTest.TestFunction();
}
// int 자리에 다른 자료형을 넣어도 같은 원리로 작동한다</code></pre>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/763d3227-8ea5-44f2-b45b-94286b554d84/image.png" alt=""></p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/be5697c1-53ee-48c0-b4f6-de34d6f36e2c/image.png" alt=""></p>
<h3 id="node">node</h3>
<pre><code class="language-cpp">/*
[Node]
- 자료구조의 두가지 메모리 형태 중 하나
- 어떠한 데이터가 자신의 참조형을 n개 가진 형태이다.

(+)
중간 index에 접근하여 데이터를 추가하거나 삭제하기 용이하다.
*/

#include &lt;iostream&gt;
#include &lt;vector&gt;

typedef int DataType;

class Node
{
public:
    DataType Value;
    Node* Next = nullptr;
    Node* Prev = nullptr;
};

// 노드 활용 e.g.
class Zone
{
public:
    std::vector&lt;Zone*&gt; LinkZone;    // 현재 지역에서 연결된 지역들
};

int main()
{
    Node Node0;
    Node Node1;
    Node Node2;
    Node Node3;
    Node Node4;
    Node Node5;
    Node Node6;

    Node0.Value = 0;
    Node1.Value = 1;
    Node2.Value = 2;
    Node3.Value = 3;
    Node4.Value = 4;
    Node5.Value = 5;
    Node6.Value = 6;

    Node0.Next = &amp;Node1;
    Node1.Next = &amp;Node2;
    Node2.Next = &amp;Node3;
    Node3.Next = &amp;Node4;
    Node4.Next = &amp;Node5;
    Node5.Next = &amp;Node6;

    Node1.Prev = &amp;Node0;
    Node2.Prev = &amp;Node1;
    Node3.Prev = &amp;Node2;
    Node4.Prev = &amp;Node3;
    Node5.Prev = &amp;Node4;
    Node6.Prev = &amp;Node5;

    // 가장 처음 노드에서 마지막 노드까지 Value 출력
    Node* CurNode = &amp;Node0;
    while (CurNode != nullptr)
    {
        std::cout &lt;&lt; CurNode-&gt;Value &lt;&lt; std::endl;
        CurNode = CurNode-&gt;Next;
    }

    // 가장 마지막 노드에서 처음 노드까지 Value 출력
    CurNode = &amp;Node6;
    while (nullptr != CurNode)
    {
        std::cout &lt;&lt; CurNode-&gt;Value &lt;&lt; std::endl;
        CurNode = CurNode-&gt;Prev;
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/271e43b6-cace-4d0c-8b5d-062dcf7a1357/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Visual Studio 단축키]]></title>
            <link>https://velog.io/@yoon-park/Visual-Studio-%EB%8B%A8%EC%B6%95%ED%82%A4</link>
            <guid>https://velog.io/@yoon-park/Visual-Studio-%EB%8B%A8%EC%B6%95%ED%82%A4</guid>
            <pubDate>Tue, 09 Jan 2024 17:01:16 GMT</pubDate>
            <description><![CDATA[<h3 id="shift">Shift</h3>
<pre><code>Shift + Del        ⇒ 한 줄 삭제
Shift + 위/아래    ⇒ 한 줄 드래그
Shift + Home    ⇒ 한 줄 안에서 커서 앞쪽으로 드래그
Shift + End        ⇒ 한 줄 안에서 커서 뒤쪽으로 드래그</code></pre><h3 id="alt">Alt</h3>
<pre><code>Alt + 드래그                ⇒ 지정 사각형만큼 드래그
Alt + 위/아래            ⇒ 한 줄 통째로 위 또는 아래로 이동
Alt + Shift + 위/아래    ⇒ 여러 줄에 커서 두기</code></pre><h3 id="ctrl">Ctrl</h3>
<pre><code>Ctrl + 좌/우            ⇒ 단어 단위로 이동
Ctrl + Shift + 좌/우    ⇒ 단어 단위로 드래그
---------------------------------------------------------------------------
Ctrl + A            ⇒ 문서 코드 전체 선택
Ctrl + K + C        ⇒ 주석 (범위 지정 필요)
Ctrl + K + U        ⇒ 주석 해제 (범위 지정 필요)
Ctrl + K + F        ⇒ 자동 줄맞춤
Ctrl + K + O        ⇒ .h파일과 .cpp파일간의 전환 (Ctrl + 왼쪽창 으로 변경해두자)
---------------------------------------------------------------------------
변수명 위에 커서 + Ctrl + R + R        ⇒ 동일한 개념을 가진 변수명을 동시에 변경
                                      - 항상 제대로 작동하는 것은 아니다
                                      - 변수가 많을수록 엄청 오래 걸린다
프로젝트 선택 + Ctrl + Shift + A        ⇒ 새 항목 추가
                                      - 프로젝트 생성 후에 소스, 헤더, 리소스 파일 
                                        필터는 삭제해도 된다
                                      - 외부 종속성은 삭제할 수 없다
파일명 위에 커서 + Ctrl + Shift + G    ⇒ 파일 열기</code></pre><h3 id="f1--f12">F1 ~ F12</h3>
<pre><code>F8    ⇒ error 위치로 순차적으로 이동 (= error 내역 더블클릭)
F12    ⇒ 정의로 이동 (이름 위에 커서를 두고 실행)</code></pre><h3 id="디버깅">디버깅</h3>
<pre><code>F9            ⇒ 중단점 설정
Shift + F9    ⇒ 조사식으로 확인하기

F5            ⇒ 디버깅 시작
Shift + F5    ⇒ 디버깅 중지

F10            ⇒ 디버깅 줄 단위로 실행 (Step Over)
              - 함수 호출부를 만나더라도 해당 함수 안까지 들어가지 않고 
                다음 라인으로 넘어가 계속해서 실행한다
F11            ⇒ 디버깅 줄 단위로 실행 (Step Into)
              - 함수 호출부를 만나면 해당 함수 안까지 들어가서 디버깅한다
Shift + F11    ⇒ 현재 함수 빠져나오기 (Step Out)
              - 현재 디버깅하고 있는 함수를 바로 끝내고 넘어간다</code></pre><h3 id="q-단축키를-변경하고-싶을땐">Q) 단축키를 변경하고 싶을땐?</h3>
<pre><code>상단 메뉴 → Git → 설정 → 환경 → 키보드
→ ‘다음 문자열을 포함하는 명령 표시’에 원하는 키워드 검색 → 원하는 기능 선택
→ ‘바로 가기 키 누르기’에 사용하고 싶은 단축키 입력 → 할당

- 원하는 기능을 정확히 알고 있어야 한다
- 해당 기능의 단축키를 초기화하고 싶다면 ‘제거’</code></pre><h3 id="q-스크롤바-옆에-코드가-보이게-하고-싶다면">Q) 스크롤바 옆에 코드가 보이게 하고 싶다면?</h3>
<pre><code>옵션 → 텍스트 편집기 → C/C++ → 스크롤 막대 → 세로 스크롤 막대에 지도 모드 사용</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-09]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-09</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-09</guid>
            <pubDate>Tue, 09 Jan 2024 07:53:59 GMT</pubDate>
            <description><![CDATA[<h2 id="템플릿-template">템플릿 Template</h2>
<blockquote>
<p>💡 <strong>C++은 멀티 패러다임 언어이다</strong>
전역 ⇒ <em>절자지향</em>
클래스와 객체 ⇒ <em>객체지향</em>
템플릿 ⇒ <em>제네릭 프로그래밍, 템플릿 메타 프로그래밍</em></p>
</blockquote>
<h3 id="템플릿-함수">템플릿 함수</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;

/*
[템플릿 함수]
템플릿 : 자료형을 마치 변수처럼 사용하는 문법
- 컴파일러는 템플릿함수를 삭제하고, 사용되는 자료형을 넣은 함수를 만들어낸다
- 자료형만 다르고 같은 내용을 가진 함수를 여러개 만들어 오버로딩할 때 유용하다
- 사용할 자료형으로 치환했을 때 혹시 오류가 없는지 확인해주는게 좋다
*/

/*
void Print(int _Value)
{
    std::cout &lt;&lt; _Value &lt;&lt; std::endl;
}

void Print(const char* _Value)
{
    std::cout &lt;&lt; _Value &lt;&lt; std::endl;
}

void Print(int* _Value)
{
    std::cout &lt;&lt; *_Value &lt;&lt; std::endl;
}

void Print(bool* _Value)
{
    std::cout &lt;&lt; *_Value &lt;&lt; std::endl;
}

...etc.
*/

template&lt;typename Type&gt;    // typename : 변수처럼 사용하고 싶은 자료형 이름
void Print(Type _Value)
{
    std::cout &lt;&lt; *_Value &lt;&lt; std::endl;
}

/*
템플릿 특수화
- 같은 이름의 템플릿 함수가 존재할 때, 예외를 만들어주는 것
- 오버로딩이나 마찬가지이다
*/
template&lt;&gt;
void Print(const char* _Value)
{
    std::cout &lt;&lt; _Value &lt;&lt; std::endl;
}

class MyPrintObject
{
public:
    template&lt;typename Type&gt;
    void Print(Type _Value)
    {
        std::cout &lt;&lt; *_Value &lt;&lt; std::endl;
    }
};

int main()
{
    {
        std::cout &lt;&lt; 1;                // int
        std::cout &lt;&lt; &quot;dfkadfdf&quot;;    // const char*
        std::cout.operator&lt;&lt;(true);    // bool
    }
    {
        int Value = 100;
        int* Ptr = &amp;Value;

        bool bValue = true;
        bool* bPtr = &amp;bValue;

        const char* Text = &quot;aaaa&quot;;

        std::cout &lt;&lt; Text &lt;&lt; std::endl;        // const char*
        std::cout &lt;&lt; *Ptr &lt;&lt; std::endl;        // int
        std::cout &lt;&lt; *bPtr &lt;&lt; std::endl;    // bool

        /*
        템플릿 인자추론
        - 정식 호출방식은 자료형과 함께 써주어야 한다
        - 하지만 그냥 써도... 알아서 추론해준다
        */
        Print(Text);    // Print&lt;const char*&gt;(&quot;aaaa&quot;);
        Print(Ptr);        // Print&lt;int*&gt;(Ptr);
        Print(bPtr);    // Print&lt;bool*&gt;(bPtr);

        MyPrintObject Object;

        Object.Print(Text);
        Object.Print(Ptr);
        Object.Print(bPtr);
    }
}</code></pre>
<h3 id="템플릿-클래스">템플릿 클래스</h3>
<pre><code class="language-cpp">// [Main.cpp]
#include &lt;iostream&gt;
#include &quot;MyTemplateClass.h&quot;

// [템플릿 클래스]
template&lt;typename MemberType&gt;
class TemplateClass
{
public:
    MemberType Value;
};

class TemplateClassint
{};

class TemplateClassbool
{};

int main()
{
    {
        /*
        1. 인자추론이 불가능하다
        - 값형인 것은 스택에 위치하기 때문
        - main()이 실행될 때 이미 클래스의 크기가 정해져 있어야 한다
        - 클래스의 크기는 내부의 멤버변수의 크기에 의해 결정된다
        */

        TemplateClass NewType;        // 불가능
        TemplateClass&lt;int&gt; NewType;    // 가능
    }
    {
        /*
        2. .h 파일과 .cpp 파일로 분할이 불가능하다
        - 전부 헤더파일에 구현해야 한다
        - 1번 요인으로 인해 2번도 불가능한 것
        */

        MyTemplateClass&lt;int&gt; MyClass;
        MyClass.Test();

        MyTemplateClass&lt;bool&gt; MyboolClass;
        MyboolClass.Test();
        // 만약 구현부가 헤더파일에 존재하지 않았다면,
        // MyTemplateClass&lt;bool&gt;은 Test()를 사용할 수 없다
    }
    {
        /*
        3. 템플릿 클래스간의 호환은 완전히 동일한 템플릿 클래스끼리만 가능하다
        - 애초에 동일한 이름의 템플릿 클래스라고 해서 전부 같은 자료형은 아니다
        */

        MyTemplateClass&lt;int&gt; MyClass;
        MyTemplateClass&lt;bool&gt; MyboolClass;

        MyClass = MyboolClass;    // 불가능

        // 아래와 같이 전혀 다른 클래스를 같은 자료형이라고 착각하는 것과 같다
        TemplateClassint NewInt;
        TemplateClassbool NewBool;

        NewInt = NewBool;    // 불가능
    }
}</code></pre>
<pre><code class="language-cpp">// [MyTemplateClass.h]
#pragma once
#include &lt;iostream&gt;

template&lt;typename ValueType&gt;
class MyTemplateClass
{
public:
    // 헤더파일에 모든 구현부가 들어가 있어야 한다
    void Test()
    {
        std::cout &lt;&lt; Value &lt;&lt; std::endl;
    }

    ValueType Value;
};</code></pre>
<pre><code class="language-cpp">// [MyTemplateClass.cpp]
#include &quot;MyTemplateClass.h&quot;

void MyTemplateClass::Test()    // 불가능
{
    std::cout &lt;&lt; Value &lt;&lt; std::endl;
}

// 사실 인자를 명시해주면 구현할 수 있다
// 하지만 필요한 자료형마다 따로 구현해줘야 해서 템플릿을 쓰는 의미가 없어진다
void MyTemplateClass&lt;int&gt;::Test()    // 인자가 int인 경우의 Test()
{
    std::cout &lt;&lt; Value &lt;&lt; std::endl;
}</code></pre>
<h3 id="myarray-구현">MyArray 구현</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;ConsoleEngine/EngineDebug.h&gt;

// 어떤 자료형으로도 사용 가능한 MyArray 구현
template&lt;typename DataType&gt;
class CArray
{
public:
    CArray()
    {

    }

    CArray(int _Size)
    {
        ReSize(_Size);
    }

    CArray(const CArray&amp; _Other)
    {
        Copy(_Other);
    }

    ~CArray()
    {
        Release();
    }

    void operator=(const CArray&amp; _Other)
    {
        Copy(_Other);
    }

    DataType&amp; operator[](int _Count)
    {
        return ArrPtr[_Count];
    }

    int Num()
    {
        return NumValue;
    }

    void Copy(const CArray&amp; _Other)
    {
        NumValue = _Other.NumValue;

        ReSize(NumValue);
        for (int i = 0; i &lt; NumValue; i++)
        {
            ArrPtr[i] = _Other.ArrPtr[i];
        }
    }

    void ReSize(int _Size)
    {
        if (_Size &lt;= 0)
        {
            MsgBoxAssert(&quot;배열의 크기가 0일수 없습니다&quot;);
        }

        DataType* Ptr = ArrPtr;
        ArrPtr = new DataType[_Size];

        int CopySize = NumValue &lt;= _Size ? NumValue : _Size;

        for (int i = 0; i &lt; CopySize; i++)
        {
            ArrPtr[i] = Ptr[i];
        }

        NumValue = _Size;

        if (Ptr != nullptr)
        {
            delete[] Ptr;
            Ptr = nullptr;
        }
    }

    void Release()
    {
        if (nullptr != ArrPtr)
        {
            delete[] ArrPtr;
            ArrPtr = nullptr;
        }
    }

private:
    int NumValue = 0;
    DataType* ArrPtr = nullptr;
};

class Monster
{
public:
    void StatusRender()
    {
        printf_s(&quot;몬스터 스테이터스 렌더&quot;);
    }

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

int main()
{
    LeakCheck;

    {
        CArray&lt;int&gt; TestArray = CArray&lt;int&gt;(10);

        for (int i = 0; i &lt; TestArray.Num(); i++)
        {
            TestArray[i] = i;
        }
        // [0][1][2][3][4][5][6][7][8][9]

        for (int i = 0; i &lt; TestArray.Num(); i++)
        {
            std::cout &lt;&lt; TestArray[i] &lt;&lt; std::endl;
        }
    }
    {
        CArray&lt;const char*&gt; TestArray = CArray&lt;const char*&gt;(10);

        TestArray[0] = &quot;aaa&quot;;
        TestArray[1] = &quot;bbb&quot;;
        TestArray[2] = &quot;ccc&quot;;
        TestArray[3] = &quot;ddd&quot;;
        TestArray[4] = &quot;eee&quot;;
        TestArray[5] = &quot;fff&quot;;
        TestArray[6] = &quot;ggg&quot;;
        TestArray[7] = &quot;hhh&quot;;
        TestArray[8] = &quot;iii&quot;;
        TestArray[9] = &quot;jjj&quot;;
        // [aaa][bbb][ccc][ddd][eee][fff][ggg][hhh][iii][jjj]

        for (int i = 0; i &lt; TestArray.Num(); i++)
        {
            std::cout &lt;&lt; TestArray[i] &lt;&lt; std::endl;
        }
    }
    {
        // 인자로 클래스도 얼마든지 들어올 수 있다
        CArray&lt;Monster&gt; TestArray = CArray&lt;Monster&gt;(10);

        for (int i = 0; i &lt; TestArray.Num(); i++)
        {
            TestArray[i].StatusRender();
        }
    }
    {   
        // 템플릿 클래스 안에 템플릿 클래스를 넣을 수 있다
        CArray&lt;CArray&lt;int&gt;&gt; TestArray = CArray&lt;CArray&lt;int&gt;&gt;(10);    // 2차원 배열

        for (int i = 0; i &lt; TestArray.Num(); i++)
        {
            TestArray[i].ReSize(10);
        }

        for (int y = 0; y &lt; 10; y++)
        {
            for (int x = 0; x &lt; 10; x++)
            {
                TestArray[y][x] = x;
            }
        }

        for (int y = 0; y &lt; 10; y++)
        {
            for (int x = 0; x &lt; 10; x++)
            {
                std::cout &lt;&lt; TestArray[y][x];
            }

            std::cout &lt;&lt; std::endl;
        }

        /*
        이중 포인터로 구현한 2차원 배열이나 마찬가지이다
        char** Ptr;
        Ptr = new char* [10];

        for (int i = 0; i &lt; 10; i++)
        {
            Ptr[i] = new char[10];
        }

        혹은 템플릿 클래스에 동적 할당을 하는 것과 같다(???)
        CArray&lt;int&gt;* Array = new CArray&lt;int&gt;[10];
        */
    }
}</code></pre>
<h2 id="자료구조">자료구조</h2>
<p>: 특정 객체의 메모리 할당, 탐색, 삭제 등의 방법을 모두 아우르는 개념</p>
<h3 id="std">std</h3>
<pre><code class="language-cpp">/*
[std]
C++ 스탠다드 라이브러리
- C++ 프로그래머들이 편하게 프로그래밍할 수 있도록 미리 함수나 클래스를 만들어둔 것
- 거의 모든 종류의 사용해야하거나 편리한 클래스 등을 OS 및 C++에서 제공해준다

- ...만약 std가 지원하지 않으면 github에 public으로 올라와 있는 프로젝트들에서 가져와서 사용할 수 있다
- 검색능력과 C++ 실력을 갖추면 얼마든지 편리한 것을 찾아 사용할 수 있다

[stl]
C++ 스탠다드 템플릿 라이브러리
std &gt; stl &gt; 자료구조
=&gt; std가 가장 포괄적인 개념이고, 그 안에 stl이, 또 그 안에 자료구조가 속해 있다
*/

#include &lt;iostream&gt;
#include &lt;vector&gt;

int main()
{
    // std를 사용할 때에는 std:: 를 앞에 붙여 사용한다
    std::vector&lt;int&gt; ArrVector = std::vector&lt;int&gt;(5);

    /*
    인터페이스
    - 클래스와 함수의 사용방식
    - 통용되는 익숙한 클래스 사용방법, 멤버변수, ... 등이 있다
    */
    ArrVector.resize(10);

    for (int i = 0; i &lt; static_cast&lt;int&gt;(ArrVector.size()); i++)
    {
        ArrVector[i] = i;
    }

    for (int i = 0; i &lt; static_cast&lt;int&gt;(ArrVector.size()); i++)
    {
        std::cout &lt;&lt; ArrVector[i] &lt;&lt; std::endl;
    }
}</code></pre>
<h3 id="stl-container">stl container</h3>
<p>: 클래스 템플릿으로 만들어진, 같은 타입의 여러 객체를 저장해두는 곳</p>
<ol>
<li>시퀀스 컨테이너 (vector, list, …)
⇒ 자료를 추가할 때, 순서가 변경되지 않는다</li>
<li>연관 컨테이너 (map, …)
⇒ 자료를 추가할 때, 특정 규칙에 따라 순서가 변경된다
⇒ 자료를 정렬해준다는 뜻은 아니다</li>
<li>어댑터 컨테이너 (string, stack, queue, …)</li>
</ol>
<blockquote>
<p>📢 <strong>자료구조 면접의 단골손님</strong></p>
</blockquote>
<ol>
<li>vector</li>
<li>map</li>
<li>list<blockquote>
</blockquote>
string, queue, stack, …</li>
</ol>
<hr>
<h2 id="과제">과제</h2>
<h3 id="240109_arrayresize">240109_ArrayResize</h3>
<pre><code class="language-cpp">// &lt;과제&gt; Resize()로 배열의 크기를 수정할 수 있도록 하자
#include &lt;iostream&gt;
#include &lt;ConsoleEngine/EngineDebug.h&gt;

class IntArray
{
public:
    IntArray(int _Size)
    {
        ReSize(_Size);
    }

    IntArray(const IntArray&amp; _Other)
    {
        Copy(_Other);
    }

    ~IntArray()
    {
        Release();
    }

    void operator=(const IntArray&amp; _Other)
    {
        Copy(_Other);
    }

    int&amp; operator[](int _Count)
    {
        return ArrPtr[_Count];
    }

    int Num()
    {
        return NumValue;
    }

    void Copy(const IntArray&amp; _Other)
    {
        NumValue = _Other.NumValue;

        ReSize(NumValue);
        for (int i = 0; i &lt; NumValue; i++)
        {
            ArrPtr[i] = _Other.ArrPtr[i];
        }
    }

    // 과제
    void ReSize(int _Size)
    {
        if (_Size &lt;= 0)
        {
            MsgBoxAssert(&quot;배열의 크기가 0일수 없습니다&quot;);
        }

        // 깊은 복사 ver. =&gt; 이 경우, 굉장히 비효율적이다
        /*
        // 기존 ArrPtr 내용을 ArrCopy에 복사하여 보존
        int* ArrCopy = new int[NumValue];
        for (int i = 0; i &lt; NumValue; i++)
        {
            ArrCopy[i] = ArrPtr[i];
        }

        // ArrPtr을 delete 후 다시 동적 할당
        if (ArrPtr != nullptr)
        {
            Release();
        }
        ArrPtr = new int[_Size];
        */

        // 얕은 복사 ver.
        int* ArrCopy = ArrPtr;  // 기존 ArrPtr이 가리키고 있던 메모리를 ArrCopy가 가리키게 만듬
        ArrPtr = new int[_Size];    // ArrPtr은 새로운 메모리를 동적 할당

        if (_Size &gt; NumValue)   // 새로 받은 size가 기존의 size보다 클 경우
        {
            for (int i = 0; i &lt; NumValue; i++)
            {
                ArrPtr[i] = ArrCopy[i];
            }
        }
        else   // 새로 받은 size가 기존의 size보다 작은 경우
        {
            for (int i = 0; i &lt; _Size; i++)
            {
                ArrPtr[i] = ArrCopy[i];
            }
        }

        NumValue = _Size;

        if (ArrCopy != nullptr) // safe delete 코드
        {
            delete[] ArrCopy;
            ArrCopy = nullptr;
        }
    }

    void Release()
    {
        if (nullptr != ArrPtr)
        {
            delete[] ArrPtr;
            ArrPtr = nullptr;
        }
    }

private:
    int NumValue = 0;
    int* ArrPtr = nullptr;
};

int main()
{
    LeakCheck;

    IntArray NewArray = IntArray(5);    // [?][?][?][?][?]

    for (int i = 0; i &lt; NewArray.Num(); i++)
    {
        NewArray[i] = i;    // [0][1][2][3][4]
    }

    NewArray.ReSize(10);    // [0][1][2][3][4][?][?][?][?][?]

    for (int i = 0; i &lt; NewArray.Num(); i++)
    {
        std::cout &lt;&lt; NewArray[i] &lt;&lt; std::endl;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] 24-01-08]]></title>
            <link>https://velog.io/@yoon-park/TIL-24-01-08</link>
            <guid>https://velog.io/@yoon-park/TIL-24-01-08</guid>
            <pubDate>Mon, 08 Jan 2024 07:36:24 GMT</pubDate>
            <description><![CDATA[<h2 id="클래스-이어서">클래스 (이어서)</h2>
<h3 id="소멸자">소멸자</h3>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;assert.h&gt;

class Weapon
{

};

class FightUnit
{
public:
    FightUnit()
    {

    }

    FightUnit(int _Att, int _Hp)
        : Att(_Att), Hp(_Hp)
    {

    }

    virtual ~FightUnit()
    {

    }

private:
    int Att;
    int Hp;
};

class Player : public FightUnit
{
public:
    // [생성자]
    // 없어도 자동 생성되지만, 적어주는 것이 좋다
    // 초기화를 위해 생성
    Player()
        : FightUnit(10, 100), Sword(nullptr)
        // 멤버 이니셜라이저도 있다는 것을 잊지 말자
    {
        CreateSword();
    }

    /*
    [소멸자]
    - 생성자와 공통점
        - 특수한 함수라, 이름이 &#39;~클래스명&#39;으로 정해져 있다
        - 직접 만들지 않으면 디폴트 소멸자가 자동으로 생성된다
    - 생성자와 차이점
        - 멤버변수처럼 직접 호출할 수 있지만, 소멸할 때 자동으로 호출되기 때문에
        아무도 그렇게 사용하지 않는다
        - 자식의 소멸자가 먼저 호출되고 그 다음 부모의 소멸자가 호출된다 (생성자는 반대)
        - 인자를 넣을수 없다 (코드가 완전히 끝나고 나서 호출되는 형태이기 때문)
    - 부모의 포인터로 자식클래스를 관리할 경우 (다형성)
        - 소멸자가 호출될 때 부모의 소멸자만 호출된다
        - 이를 방지하기 위해 최상위 부모의 소멸자에 virtual을 붙여준다
        - 마찬가지로 자식의 소멸자에는 override를 붙여준다
    */
    ~Player() override
    {
        DeleteSword();    // 보통 소멸자에서 동적 할당을 정리한다
    }

    void CreateSword()
    {
        // 1. 이미 할당된 곳에 다시 동적 할당하지 말자
        // 마지막에 할당한 주소 이외엔 전부 잃어버리게 된다 (memory leak)
        if (Sword != nullptr)    
        {
            // assert(false)    // &#39;한번 무기를 들면, 절대로 놓지 않는다&#39;
            DeleteSword();    // &#39;다른 무기를 들기 전에, 기존의 무기를 놓는다&#39;
        }

        Sword = new Weapon();    // 동적 할당
    }

    void DeleteSword()
    {
        // 2. new를 했으면 delete가 반드시 따라와야 한다
        // 사실 이런 함수는 특정 상황이 아닌 이상 명시적으로 호출하지 않는다
        // 소멸자가 존재하기 때문
        delete Sword;
    }

private:
    Weapon* Sword = nullptr;
};

class Orc : public FightUnit    // 100마리
{};

class Dragon : public FightUnit    // 20마리
{};

class Kobolt : public FightUnit    // 20마리
{};

int main()
{
    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);

    {
        // 정적배열을 이용할 경우
        Orc ArrOrc[100];
        Dragon ArrDragon[20];
        Kobolt ArrKobolt[20];
        /*
        보통은 위와 같이 정적배열로 생성하지 않는다
        몬스터가 몇 종류일지, 몇 마리일지 알 수 없기 때문
        따라서 아래와 같이 다형성을 적극적으로 이용해 생성한다
        */

        // 다형성을 이용할 경우
        FightUnit** AllMonster = new FightUnit * [140];

        for (int i = 0; i &lt; 100; i++)
        {
            AllMonster[i] = new Orc();
        }

        for (int i = 100; i &lt; 120; i++)
        {
            AllMonster[i] = new Dragon();
        }

        for (int i = 120; i &lt; 140; i++)
        {
            AllMonster[i] = new Kobolt();
        }
    }
    {
        // 값형일 경우
        Player NewPlayer;
        /*
        - 스택영역에 생성된다
        - 생성자에 이어 자연스레 소멸자까지 호출된다
            =&gt; FightUnit() -&gt; Player() -&gt; ~Player() -&gt; ~FightUnit()
        */

        // 다형성을 이용할 경우
        FightUnit* NewFightUnit = new Player();
        /*
        - 부모클래스의 포인터를 이용해 관리한다
        - 하지만 이 경우, 자식의 소멸자가 부모의 소멸자를 override하지 않으면
          자식의 소멸자 대신 부모의 소멸자가 호출되어버리고, memory leak을 야기한다
        */
    }
}</code></pre>
<h3 id="복사-생성자-깊은-복사와-얕은-복사">복사 생성자, 깊은 복사와 얕은 복사</h3>
<blockquote>
<p>💡 <strong>자료구조란?</strong></p>
</blockquote>
<ul>
<li>메모리에 새로운 자료를 어떻게 추가하여 배치할 것인가?</li>
<li>어느 자료를 어떻게 삭제할 것인가?</li>
<li>내가 원하는 자료를 어떻게 찾을 것인가?</li>
</ul>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;Windows.h&gt;
#include &lt;assert.h&gt;

class IntArray
{
/*
클래스 안에서 아무것도 만들지 않아도, 아래 다섯가지는 디폴트로 생긴다

private:    // 디폴트 접근제한 지정자 (1)
    IntArray()    // 디폴트 생성자 (2)
    {}

    IntArray(const IntArray&amp; _Other)    // 디폴트 복사 생성자 (3)
    {}

    ~IntArray()    // 디폴트 소멸자 (4)
    {}

    void operator =(const IntArray&amp; _Other)    // 디폴트 대입 연산자 (5)
    {}


따라서 아무것도 구현하지 않아도, 클래스 디폴트 기능을 사용할 수 있다
IntArray NewArray0 = IntArray();
IntArray NewArray1 = IntArray();
IntArray NewArray2 = IntArray(NewArray1);
NewArray0 = NewArray1;
*/

public:
    IntArray(int _Size)    // 생성자
    {
        Resize(_Size);
    }

    IntArray(const IntArray&amp; _Other)    // 복사 생성자
    {
        NumValue = _Other.NumValue;
        ArrPtr = _Other.ArrPtr;    // 얕은 복사

        // Copy(_Other);
        // 얕은 복사 대신 깊은 복사를 사용하면 소멸자 호출시 발생하는 문제가 사라진다

    }

    ~IntArray()    // 소멸자
    {
        Release();
    }

    // 멤버함수와 전역함수의 차이점 =&gt; 컴파일러가 첫번째 인자로 this를 넣어준다는 것 뿐!

    void operator =(const IntArray&amp; _Other)    // 대입 연산자
    {

        NumValue = _Other.NumValue;
        ArrPtr = _Other.ArrPtr;    // 얕은 복사

        // Copy(_Other);
        // 얕은 복사 대신 깊은 복사를 사용하면 소멸자 호출시 발생하는 문제가 사라진다
    }

    int&amp; operator[](int _Count)
    {
        return ArrPtr[_Count];
    }
    /*
    Q) 왜 int&amp;형일까?
    직접적으로 ArrPtr이 가리키고 있는 값에 접근하기 위함이다.
    int형으로 return값을 받을 경우 복사본이 생성되는 것으로,
    이 경우 return값을 이용해 연산한 값은 ArrPtr이 가리키는 값에 영향을 주지 못한다.
    */

    int Num()
    {
        return NumValue;
    }

    void Copy(const IntArray&amp; _Other)
    {
        NumValue = _Other.NumValue;

        Resize(NumValue);
        for (int i = 0; i &lt; NumValue; i++)
        {
            ArrPtr[i] = _Other.ArrPtr[i];    // 깊은 복사
        }
    }

    void Resize(int _Size)
    {
        if (_Size &lt;= 0)
        {
            MessageBoxA(nullptr, &quot;배열의 크기가 0일수 없습니다&quot;, &quot;치명적 에러&quot;, MB_OK);
            assert(false);
        }

        if (ArrPtr != nullptr)
        {
            Release();
        }

        NumValue = _Size;
        ArrPtr = new int[_Size];
    }

    void Release()
    {
        if (ArrPtr != nullptr)
        {
            delete[] ArrPtr;
            ArrPtr = nullptr;
        }
    }

private:
    int NumValue = 0;
    int* ArrPtr = nullptr;
};

int main()
{
    _CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);

    // 기본문법 배열
    {
        /*
        불편한 점
        1. 정적할당이라 크기를 동적으로 바꿀 수 없다
        2. 직접적인 대입이 안된다
        3. 크기를 알아내는 방법도 불편하다

        객체지향에서는 내가 만든 객체로 표현하지 못할 것이 없으므로,
        원하는 기능이 있다면 직접 나만의 배열을 만들어 얼마든지 기능을 추가할 수 있다.

        ...사실 이미 개선된 배열도 제공돼서 그냥 사용하면 되지만,
        면접 단골 질문이기 때문에 한번 만들어보면 좋다.
        */

        int ArrValue0[10];
        int ArrValue1[10];

        // ArrValue0[11] = 5;    // 1
        // ArrValue0 = ArrValue1;    // 2
        int Value = static_cast&lt;int&gt;(sizeof(ArrValue0) / sizeof(int));    // 3
    }

    // 직접 만든 배열
    {
        IntArray NewArray = IntArray(5);

        NewArray.Resize(3);
        NewArray[0] = 20;    // NewArray.operator[](0) = 20;

        for (int i = 0; i &lt; NewArray.Num(); i++)
        {
            NewArray[i] = i;
        }

        for (int i = 0; i &lt; NewArray.Num(); i++)
        {
            std::cout &lt;&lt; NewArray[i] &lt;&lt; std::endl;
        }
    }

    /*
    얕은 복사의 문제점
    =&gt; 값을 직접 복사하는 것이 아니라, 참조만 가져오기 때문에 발생하는 문제

    NewArray1의 ArrPtr이 NewArray0의 ArrPtr와 같은 곳을 가리키게 된다
    -&gt; ~NewArray0()이 호출되어 NewArray0의 ArrPtr이 가리키는 메모리가 delete된다
    -&gt; ~NewArray1()이 호출되었지만 NewArray1의 ArrPtr이 가리키는 곳은 이미 존재하지 않는다
    */
    {
        IntArray NewArray0 = IntArray(5);
        for (int i = 0; i &lt; NewArray0.Num(); i++)
        {
            NewArray0[i] = i;
        }

        IntArray NewArray1 = IntArray(5);
        NewArray1 = NewArray0;

        for (int i = 0; i &lt; NewArray1.Num(); i++)
        {
            std::cout &lt;&lt; NewArray0[i] &lt;&lt; std::endl;
        }

        // ~NewArray0()
        // ~NewArray1()    // 에러 발생
    }
    {
        IntArray NewArray0 = IntArray(5);
        for (int i = 0; i &lt; NewArray0.Num(); i++)
        {
            NewArray0[i] = i;
        }

        IntArray NewArray1 = IntArray(NewArray0);

        for (int i = 0; i &lt; NewArray1.Num(); i++)
        {
            std::cout &lt;&lt; NewArray0[i] &lt;&lt; std::endl;
        }

        // ~NewArray0()
        // ~NewArray1()    // 에러 발생
    }
}

/*
[깊은 복사 Deep Copy] : 값을 복사하는 것

int Value0 = 0;
int Value1 = 0;
Value0 = Value1;    // 깊은 복사


[얕은 복사 Shallow Copy] : 참조를 복사하는 것
반드시 깊은 복사보다 나쁜 것이 아니라, 상황에 따라 필요한 방식이 다르다 (유념!)

int* Ptr0;
int* Ptr1;
Ptr0 = Ptr1;    // 얕은 복사
*Ptr0 = *Ptr1;    // 깊은 복사
*/</code></pre>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/c7b346f1-7ccf-4254-8c11-45cd70ca9a40/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/6a6a6a97-45d6-4582-8ce2-68132b61930b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/b5c26424-1466-4964-ad4e-77d2d381a828/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoon-park/post/de2d36d3-ecc6-420a-83c7-6d34ab89bb7c/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>