C# 공부를 좀 재밌게 할 수 있는 방법 없나 찾아보다가 문득 머드게임이 떠올랐다.

머드게임을 즐겨 하던 세대는 아니지만, 머드게임을 구현하면서 C#을 공부하면 참 재밌을 것 같았다.

 

이름은 전사의 모험 RPG로 간단하게 정해봤다.

그냥 무난하게 턴제 형식의 전투를 만들 생각이다.

 

using C_Study;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

class MainClass
{
    public static void Main()
    {
        GameCore gameCore = new GameCore();
        
        gameCore.Start();
        gameCore.Update();
        gameCore.End();
    }
}

GameCore라는 클래스 내부에서 게임을 전체적으로 관리할 것이다.

 

Main함수에선 인스턴스의 Start, Update, End를 호출하도록 하였다.

Start에선 게임이 시작되기 전에 설정해야 할 것들을 세팅하는 함수이고 Update는 게임이 진행되는 함수이다.

End는 게임이 종료될 때 해야 할 일을 하는 함수이다.

 

아래는 GameCore 클래스 내부 코드이다.

using C_Study;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace C_Study
{
    public enum LevelType
    {
        Start,
        Play,
    }

    public class GameCore
    {
        public void Start()
        {
            _player = new Player();
            _levelType = LevelType.Start;
        }

        public void Update()
        {
            while(_player != null)
            {
                switch (_levelType)
                {
                    case LevelType.Start:
                        StartLevelUpdate();
                        break;
                    case LevelType.Play:
                        PlayLevelUpdate();
                        break;
                }
            }
        }

        public void End()
        {

        }

        private void StartLevelUpdate()
        {
            while (_levelType == LevelType.Start)
            {
                Console.Clear();

                Console.WriteLine("*************************************************");
                Console.WriteLine("*************************************************");
               
                Console.Write("*****************");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("전사의 모험 RPG");
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("*****************");

                Console.Write("******************");
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.Write("제작자 오의현");
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("******************");

                Console.WriteLine("*************************************************");
                Console.WriteLine("*************************************************");
                Console.WriteLine();

                Console.WriteLine("모험을 시작하시겠습니까?");

                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine("1 : YES");
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("2 : NO");
                Console.ForegroundColor = ConsoleColor.White;

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                if(input.Length > 1)
                {
                    continue;
                }
                
                int toInt = int.Parse(input);

                switch (toInt)
                {
                    case 1:
                        _levelType = LevelType.Play;
                        break;
                    case 2:
                        Environment.Exit(0);
                        break;
                }
            }
        }
        private void PlayLevelUpdate()
        {
            while (_levelType == LevelType.Play)
            {
                Console.Clear();

                Console.WriteLine("**************************************************");
                Console.WriteLine("*****시작*****");
                Console.WriteLine("**************************************************");
                Console.WriteLine();

                string input = Console.ReadLine();

                if (DevFunctions.IsNumeric(input) == false)
                {
                    continue;
                }

                int toInt = int.Parse(input);
            }
        }

        private Player _player;
        private LevelType _levelType;
    }
}

 

Start에선 일단 플레이어 생성과 최초 레벨 설정만 해주었다.

Update에선 Level에 따라 다른 함수가 실행되도록 하였다.

 

중간에 DevFunctions.IsNumeric()이라는 함수가 보이는데 이는 입력받은 문자열이 정수로만 이루어져있는지 판단하는 함수이다. 

 

내부 코드는 아래와 같다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public class DevFunctions
    {
        public static bool IsNumeric(string str)
        {
            if (string.IsNullOrEmpty(str) == true)
            {
                return false;
            }

            foreach (char curChar in str)
            {
                if (char.IsDigit(curChar) == false)
                {
                    return false;
                }
            }

            return true;
        }
    }
}

 

StartLevel은 게임시작 화면이다.

이런 식으로 화면에 출력된다. 시작 창을 화려하게 꾸며보고 싶었는데, 그런 쪽에는 재능이 없는지라 심플하게 갔다.

1번을 누르면 게임이 시작되고 2번을 누르면 콘솔이 종료된다.

 

PlayLevel을 구현하기 전에 무기 클래스를 먼저 구현해놓았다. 무기 클래스 코드는 아래와 같다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;

namespace C_Study
{
    public enum WeaponType
    {
        None,
        Sword,
        Spear,
        Hammer,
    }

    public class Weapon
    {
        protected Weapon()
        {

        }

        protected WeaponType WPType
        {
            get { return _wpType;  }
            set { _wpType = value; }
        }

        protected string WPName
        {
            get { return _wpName; }
            set { _wpName = value; }
        }
        protected int AttPower
        {
            get { return _attPower; }
            set { _attPower = value; }
        }
        protected float BonusAttackProb
        {
            get { return _bonusAttackprob; }
            set { _bonusAttackprob = value; }
        }

        private int _attPower;
        private float _bonusAttackprob;

        private string _wpName;
        private WeaponType _wpType;
    }

    public class Sword : Weapon
    {
        protected Sword()
        {
            WPType = WeaponType.Sword;
            BonusAttackProb = 25.0f;
        }
    }

    public class Spear : Weapon
    {
        protected Spear()
        {
            WPType = WeaponType.Spear;
            BonusAttackProb = 15.0f;
        }
    }

    public class Hammer : Weapon
    {
        protected Hammer()
        {
            WPType = WeaponType.Hammer;
            BonusAttackProb = 5.0f;
        }
    }

    public class OldSword : Sword
    {
        public OldSword()
        {
            WPName = "오래된 검";
            AttPower = 10;
        }
    }

    public class OldSpear : Spear
    {
        public OldSpear()
        {
            WPType = WeaponType.Spear;
            WPName = "오래된 창";
            AttPower = 15;
        }
    }

    public class OldHammer : Hammer
    {
        public OldHammer()
        {
            WPType = WeaponType.Hammer;
            WPName = "오래된 망치";
            AttPower = 30;
        }
    }
}

 

Weapon 클래스를 Spear, Sword, Hammer 클래스가 상속받도록 하였고 실제로 플레이어가 장착할 무기들은 종류에 맞는 클래스를 상속받도록 하였다.

 

코드의 아래쪽에 보면 OldSword, OldSpear, OldHammer가 있는데 이게 플레이어가 실제 장착할 아이템이다.

처음에 장착할 기본템이며 일단은 이 3개만 만들어두었다.

 

무기는 2개의 스탯을 가지고 있다. 공격력과 추가공격확률이다.

 

공격력은 말 그대로 공격력이고, 추가공격확률은 공격시에 추가적으로 공격이 발생할 확률이다.

검은 공격력이 낮은 대신 추가공격확률이 높고, 망치는 공격력이 높은 대신 추가공격확률이 낮다.

창은 그 중간쯤이다.

 

일단은 이정도만 구현해보았다. 

다음엔 게임 플레이 내용을 본격적으로 구현할 것이다.

C#은 클래스를 어떻게 정의하는지 알아보자.

C++의 클래스와 거의 동일하다. 다만 조금 다른 부분이 있다.

 

C++은 아래와 같이 클래스를 정의한다.

class A
{
public:
    void func(){}
}

 

C#에선 아래와 같이 정의한다.

class A
{
    public func() {}
}

 

C++에선 접근 제한 지정자를 작성하면 그 아래에 있는 코드들은 해당 접근 제한 지정자의 영향을 받는다.

반면, C#은 접근 제한 지정자를 변수, 메소드 별로 앞에 붙여주어야 한다.

 

접근 제한 지정자는 C++에는 public, protected, private 3가지가 있고, C#에도 동일하게 있다. 역할 또한 똑같다. 

다만, C#에는 internal과 protected internal 이라는 2개의 접근 제한 지정자가 추가로 있다. 

 

internal은 같은 어셈블리 내에서만 접근이 가능하도록 제한하는 키워드이다. 그렇다면 어셈블리란 무엇일까?

컴파일을 통해 나온 결과물을 어셈블리라고 한다. exe파일도 하나의 어셈블리고, dll파일도 하나의 어셈블리이다.

 

어셈블리 내에서만 접근할 수 있도록 제한한다는 의미는 해당 프로젝트가 라이브러리로 컴파일되어 다른 프로젝트에서 사용되었을 때, 외부에서는 접근을 하지 못하도록 막는다는 의미인 듯 하다.

 

표로 정리해보면 아래와 같다.

접근 제한 지정자 C++ C#
public 어디에서나 참조 가능 어디에서나 참조 가능
protected 상속 관계에서만 참조 가능 상속 관계에서만 참조 가능
private 클래스 내부에서만 참조 가능 클래스 내부에서만 참조 가능
internal 없음 어셈블리 내에서만 참조 가능
protected internal 없음 어셈블리 내에 있는 상속 관계의 클래스에서만 참조 가능

 

C#에는 C++과 달리 프로퍼티라는 기능도 지원해준다.

프로퍼티란 쉽게 말하면 getter, setter이다.

 

C++에선 멤버 변수를 외부에서 안전하게 참조할 수 있도록 getter와 setter을 아래와 같이 만든다.

class Test
{
public:
    int GetA()
    {
        return A;
    }
    
    void SetA(int _A)
    {
        A = _A;
    }
    
private:
    int A = 0;
}

 

물론 C#도 위와 같이 getter, setter을 선언하여 사용할 수도 있다.

하지만, C#에서 지원해주는 프로퍼티란 기능을 사용할 수도 있다.

 

아래 코드를 보자.

class Class1
{
    public int AProperty
    {
        get 
        { 
            return A; 
        }

        set 
        {
            A = value; 
        }
    }

    private int A = 0;

}

 위와 같이, get set 키워드를 통해 getter과 setter을 간단하게 선언할 수 있다.

class Class1
{
    public int AProperty
    {
        get 
        { 
            return A; 
        }

        set 
        {
            if(A > 0)
            {
                A = value; 
            }
        }
    }

    private int A = 0;

}

이렇게 내부에 조건문을 추가할 수도 있고 선행작업이나 후행작업이 필요하다면 그 것도 추가할 수 있다.

 

class Class1
{
    public int AProperty
    {
        get { return A; }
        set {if(A > 0) A = value;}
    }

    private int A = 0;

}

이렇게 짧게 쓸 수도 있고, get이나 set중에 하나만 넣어줄 수도 있다.

 

일반적인 getter, setter이랑 사실 크게 다른 건 없어보인다. 그래도 문법에서 지원해주니까 프로퍼티를 사용하는게 아무래도 코드의 통일성을 유지하기 좋을 것 같다. 아니면 그냥 본인 쓰기 편한대로 써도 될 것 같다.

'C# > C#' 카테고리의 다른 글

C# - const, readonly  (0) 2024.08.10
C# - is, as 연산자  (0) 2024.08.07
C# - C#의 자료형  (0) 2024.07.24
C# - 클래스와 콘솔 출력  (2) 2024.07.24
C# - C#과 .NET 프레임워크  (2) 2024.07.23
스터디를 진행하며 실시한 모의 면접을 정리한 내용입니다.
겹치는 내용을 고려하지 않고 면접을 실시하기 때문에 일자 별로 겹치는 내용이 많을 수 있습니다.

1. 포인터와 참조자의 차이를 설명하라.

더보기

포인터의 경우 nullptr을 가리킬 수 있지만 참조자는 반드시 존재하는 대상을 가리켜야 합니다. 하지만, 포인터는 가리키고 있는 대상을 자유롭게 바꿀 수 있는 반면 참조자는 가리키고 있는 대상을 변경할 수 없습니다. 이러한 차이로 인해 참조자는 포인터에 비해 안전하게 사용할 수 있지만, 포인터만큼 유연하게 사용할 수는 없습니다.

2. C++의 캐스팅은 무엇이 있는가?

더보기

컴파일 타임에 이루어지는 static_cast, 런타임에 작동하는 dynamic_cast, 포인터의 형변환이 가능한 reinterpret_cast, 포인터 변수의 상수성을 제거해주는 const_cast가 있습니다.

3. 해시테이블이란?

더보기

해시 값을 키로 사용하는 자료구조입니다.

4. 해시테이블의 장단점은?

더보기

해싱된 키 값이 배열의 인덱스로 사용되기 때문에 데이터를 탐색하고 접근하는 것이 매우 빠르다는 장점이 있습니다. 다만, 원소의 수가 매우 적을 경우 메모리가 낭비될 수 있다는 단점이 있으며 동일한 해시 값을 가지는 키가 여럿 존재할 경우 성능이 떨어질 수 있다는 단점이 있습니다.

5. 운영체제란?

더보기

사용자가 하드웨어 상호작용, 메모리 관리 등 저수준의 영역을 대신 관리하여 유저가 신경쓰지 않도록 도와주는 프로그램입니다. PC가 부팅될 때 가장 먼저 실행되어 유저가 편하고 안전하게 PC를 사용할 수 있도록 다양한 인터페이스를 제공해줍니다.

6. 메모리 풀링이란?

더보기

여러 개의 오브젝트가 생성될 필요가 있을 때, 필요할 때마다 메모리를 할당하는 것이 아니라 미리 할당해두고 필요한 만큼 사용하는 기법입니다. 런타임에 발생하는 동적 할당을 줄일 수 있으며, 오브젝트를 메모리에 순차적으로 위치시켜 캐시 적중률을 높일 수 있습니다. 이로 인해 성능의 향상을 기대할 수 있지만 불필요하게 많은 메모리를 할당한 경우 내부 단편화가 발생할 수 있습니다.

7. placement new란?

더보기

new 연산자의 경우, 메모리를 할당하고 생성자를 호출하여 값을 초기화한 뒤 할당 받은 메모리의 주소를 타입 캐스팅하여 반환하는 과정으로 진행됩니다. 이 과정 중에서 메모리 영역에 생성자를 호출하여 값을 초기화하는 역할을 하는 것이 placement new입니다. 이미 할당되어 있는 영역에 생성자를 호출하여 값을 초기화할 수 있으며, new 연산자 외부에서도 필요에 따라 사용할 수 있습니다. 주로 메모리 풀링에 사용됩니다. 

8. 캐시 적중률이란?

더보기

CPU가 캐시에서 데이터를 찾을 확률을 의미합니다. 캐시 적중률이 낮을 경우 CPU가 메인 메모리에 접근하는 횟수가 많아지기 때문에 성능의 저하가 발생할 수 있습니다.

9. 고정 소수점과 부동소수점에 대해 설명하라.

더보기

고정 소수점은 2진수로 변환된 실수를 그대로 메모리에 저장하는 방식입니다. 부동 소수점 방식에 비해 정밀도가 높지만 동일한 메모리 크기에서 표현할 수 있는 실수의 범위가 매우 좁다는 단점이 있습니다.

 

부동 소수점 방식은 2진수로 변환된 실수의 정수부가 1이 되도록 소숫점을 앞으로 옮긴 값을 가수부에 저장하고, 소숫점을 옮긴 정도를 지수부에 저장하는 방식입니다. 고정 소수점에 비해 정밀도가 다소 낮지만, 메모리 크기 대비 표현할 수 있는 실수 범위가 고정 소수점에 비해 매우 넓다는 장점이 있습니다.

10. 메모리 구조에 대해 설명하라.

더보기

메모리는 코드 영역, 데이터 영역, 힙 영역, 스택 영역으로 구성되어 있습니다. 코드 영역은 기계어로 번역된 바이너리 코드가 저장되며, 데이터 영역엔 전역 변수, static 변수가 저장됩니다. 힙 영역은 필요에 따라 메모리를 동적으로 할당하여 사용할 수 있는 영역이며 스택 영역은 호출 된 함수의 로컬 데이터를 저장하는 영역입니다. 

11. 변환 색인 버퍼란?

더보기

변환 색인 버퍼란 페이징 테이블의 데이터를 캐싱하는 하드웨어입니다. 페이징 기법으로 가상 메모리를 구현한 환경에선 CPU가 물리 주소에 접근하기 위해 페이징 테이블을 한 번 거쳐야 합니다. 페이징 테이블은 메인 메모리에 위치하기 때문에 CPU는 메인 메모리에 2번 접근해야 하는데, 이로 인해 병목 현상이 발생하게 됩니다. 변환 색인 버퍼는 CPU가 메인 메모리에 직접 접근하는 횟수를 최소화하여 병목현상을 완화하는 역할을 합니다.

12. quicksort란?

더보기

하나의 피봇을 설정한 뒤, 피봇보다 작은 값은 피봇의 왼쪽으로 옮기고 피봇보다 큰 값은 피봇의 오른쪽으로 이동시키는 작업을 피봇에 의해 분할되는 좌우에 재귀적으로 반복하는 방식으로 구현하는 정렬 알고리즘입니다. 평균적으로는 NlogN의 시간 복잡도를 보이지만 피봇에 따라 정렬 속도가 크게 차이나기 때문에 적절한 피봇을 정할 수 없는 상태라면 다른 정렬 알고리즘을 사용하는 것이 유리할 수 있습니다. 

13. 클래스와 객체란?

더보기

객체는 구현하고자 하는 대상이며, 클래스는 객체의 속성을 코드로 작성하여 구체화한 것입니다. 

14. 명령어파이프라인이란?

더보기

명령어 파이프라인이란 CPU의 작업 성능을 극대화하기 위해 명령어를 여러 단계로 분할하여 여러 명령어를 병렬적으로 처리하는 기법입니다.

15. 닷넷 프레임워크란?

더보기

마이크로 소프트에서 제작한 프레임워크로 운영체제에 의존적이지 않은 개발, 실행 환경을 제공해줍니다. 닷넷 프레임워크를 사용할 수 있는 다양한 언어들이 동일하게 작동할 수 있도록 작성된 코드를 IL이라는 중간 언어로 컴파일 한 뒤 런타임에 운영체제에서 이해할 수 있는 기계어로 번역하는 방식으로 작동합니다.

C#과 C/C++ 은 사용하는 자료형이 다소 다르다.

비슷한 부분이 많지만, 약간의 다른 부분이 있기 때문에 알아둬야 한다.

 

먼저, C#에서 사용하는 기본 자료형의 종류를를 알아보자.

 

정수형

자료형 형식 크기, 저장 방식 범위
sbyte System.Sbyte 8bit (1byte) 
부호 있는 정수
-128 ~ 127
byte System.Byte 8bit (1byte)
부호 없는 정수
0 ~ 255
short System.Int16 16bit (2byte)
부호 있는 정수
-32,768 ~ 32,767
ushort System.UInt16 16bit (2byte)
부호 없는 정수
0 ~ 65,535
int System.Int32 32bit (4byte)
부호 있는 정수
-2,147,483,648 ~ 2,147,483,647 
uint System.UInt32 32bit (4byte)
부호 없는 정수
0 ~ 4,294,967,295
long System.Int64 64bit (8byte)
부호 있는 정수
-9,223,372,036,854,775,808 ~ 
 9,223,372,036,854,775,808 
ulong System.UInt64 64bit (8byte)
부호 없는 정수
0 ~ 18,446,744,073,709,551,615

실수형

자료형 형식 크기, 저장 방식 범위
float System.Single 32bit (4byte)
부호 있는 실수
(부동 소수점)
±1.5e-45 ~ ±3.4e38
double  System.Double 64bit (8byte)
부호 있는 실수
(부동 소수점)
±5.0e-324 ~ ±1.7e308
decimal System.Decimal 126bit (16byte)
부호 있는 실수
(고정 소수점)
±1.0 × 1028  ±7.9 × 1028

문자형

자료형 형식 크기, 저장 방식 범위
char System.Char 16bit (2byte)
유니코드 문자
U+0000 ~ U+FFFF
string System.String 가변적인 크기
유니코드 문자열
 

논리형

자료형 형식 크기, 저장 방식 범위
bool System.Boolean 8bit (1byte) true, false

 

자료형과 형식

위의 표를 보면, 자료형과 형식을 나눠서 설명하고 있다.

코드를 작성하면 아래와 같이 자료형대로 선언할 수도 있고, 형식대로 선언할 수도 있다.

아래 그림과 같이 자료형에 마우스를 대보면, uint도 System.UInt32라는 구조체로 나오고 System.UInt32도 동일하게 나온다. 둘은 실제로 동일한 기능을 하는 것이다.  System.UInt32위에 마우스를 대면 이름을 단순화 할 수 있다는 경고 표시가 뜬다. uint로 쓰나, System.UInt32로 쓰나 기능은 어차피 똑같으니까 더 짧은 uint를 쓰라는 의미인 듯 하다.

굳이 누가 진짜인지를 따지자면, System.UInt32가 본래 이름이고, uint가 별칭이다.

이렇게 기본 자료형에도 다양한 멤버함수가 포함되어 있어서 편하게 프로그래밍을 할 수 있다.

부동 소수점, 고정 소수점

위의 표를 보면, 실수를 표현하는 자료형은 float, double, decimal 총 3가지가 있다.

보면, float과 double는 부동 소수점 방식이지만, decimal은 고정 소수점 방식이다.

 

부동 소수점과 고정 소수점을 이 게시글에서 자세하게 설명하진 않을 것이다.

다만, 간단한 차이는 알고 가자.

 

고정 소수점 방식은 부동 소수점 방식에 비해 정밀하게 소수를 표현할 수 있지만, 부동 소수점 방식에 비해 표현할 수 있는 범위가 적다. 

 

반면 부동 소수점 방식은 고정 소수점 방식에 비래 넓은 범위를 표현할 수 있지만, 고정 소수점 방식 보다 정밀하지 않다.

 

고정 소수점 방식의 표현 범위를 넓히기 위해선, 메모리 공간 자체를 크게 할당해야 한다. 그렇기 때문에 C#에서 사용하는 고정 소수점 방식의 실수 자료형인 decimal은 16byte라는 어마어마한 크기를 차지하고 있는 것이다. decimal은 정밀한 연산에 사용할 수 있지만, 메모리를 많이 차지하고 연산 속력 또한 float, double에 비해 느리다.

 

금융, 시뮬레이션 등의 정밀한 소수 계산이 필요한 상황이 아니라면 float, double로도 충분하기 때문에 가능하다면 float과 double를 사용하는 것이 성능상 좋을 것이다.

 

문자형

C++에선 아스키코드로 문자를 저장하는 char와 unsigned char가 있다. 반면, C#은 아스키코드로만 처리하는 자료형은 따로 없고, 모든 문자를 유니코드로 처리한다.

 

char은 'A' 와 같이 문자 하나를 저장할 때 사용한다. 유니코드 방식이기 때문에 '김', '최' 등의 한글도 저장할 수 있다.

string은 문자열을 저장하는 타입이다. std::wstring과 비슷하다고 보면 된다.

'C# > C#' 카테고리의 다른 글

C# - const, readonly  (0) 2024.08.10
C# - is, as 연산자  (0) 2024.08.07
C# - 클래스, 접근 제한 지정자, 프로퍼티  (0) 2024.07.27
C# - 클래스와 콘솔 출력  (2) 2024.07.24
C# - C#과 .NET 프레임워크  (2) 2024.07.23

C++은 main함수를 작성하고 출력을 하기 위해선 iostream 헤더파일을 추가한 뒤 아래와 같이 코드를 작성해야 한다.

#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;
    return 0;
}

하지만, C#은 다르다. 헤더파일을 추가할 필요도 없고, main 함수도 클래스 내부에 속해야 한다.

일단 코드를 먼저 보자. C#은 아래와 같이 작성된다.

using Sysyem;

public class MainClass
{
    static int Main()
    {
        Console.WriteLine("Hello World!");
        return 0;
    }
}

먼저,  눈에 띄는 것은 System이라는 네임스페이스이다.

 

해당 네임스페이스 안에는 Write, WriteLine와 같은 콘솔출력 함수도 있고 그 외에도 닷넷 프레임워크 시스템 단위에서 지원해주는 많은 기능들이 있다. C++의 std와 유사하다고 보면 될 것 같다.

 

C#은 C++과 달리 헤더파일이라는 개념이 없기 때문에 특별한 전처리기 구문 없이도 기능을 사용할 수 있다. System 네임스페이스에 있는 기능을 그냥 가져다 쓰면 된다.

 

그 다음으로 볼 것은 진입점 함수(Main 함수)가 클래스 내부에 있다는 것이다. C#은 C++과 달리 완전한 객체지향 언어이기 때문에 클래스 외부에 함수가 존재할 수 없다. 그렇기 때문에 진입점 함수도 클래스 내부에 선언해야 한다.

 

하지만, 클래스 내부에 진입점 함수를 선언하게 되면 한 가지 문제가 있다. 일반적인 멤버함수는 인스턴스가 생성되지 않으면 호출할 수 없다는 것이다. 즉, 프로그램이 실행되었는데도 Main함수에 접근할 수 없다는 의미이다.

(객체를 생성하려면 객체 생성 코드를 실행해야 하는데, 가장 처음으로 실행되는 코드가 Main 함수 호출 코드이기 때문에 Main 함수를 호출할 수 없게 된다.)

 

이러한 문제를 해결하기 위해 Main 함수는 반드시 static 함수로 선언해야 한다.

(위에 코드를 보면 static으로 선언되어 있다.)

 

마지막으로 살펴볼 것은 WriteLine함수이다. WriteLine는 C의 printf나 C++의 std::cout 과 유사한 기능을 가지고 있다. 입력받은 문자열을 콘솔에 출력하는 기능이다. 다만, WriteLine 함수는 자동으로 개행(줄바꿈)문자를 문자열의 끝에 삽입해준다.

 

WriteLine("안녕");

WriteLine("잘가");

 

라고 작성한다면, 콘솔에는 아래와 같이 출력된다.

 

C#에는 WriteLine말고 Write라는 함수도 있다.

Write함수는 개행문자를 삽입하지 않는다. printf나 cout과 동일한 결과를 보인다는 것이다.

 

Write("안녕");

Write("잘가");

 

와 같이 코드를 작성한다면 콘솔에는 아래와 같이 출력된다.

 

또한, C++과 차이를 하나 더 보자면, C++은 대부분의 함수가 소문자로만 작명되어있다.반면, C#은 파스칼 케이싱(단어의 앞문자를 모두 대문자로 표기)을 사용하는 듯 하다. 필자처럼 파스칼 케이싱을 자주 사용하는 사람이라면 C#이 꽤나 잘 맞을 수도 있다.

'C# > C#' 카테고리의 다른 글

C# - const, readonly  (0) 2024.08.10
C# - is, as 연산자  (0) 2024.08.07
C# - 클래스, 접근 제한 지정자, 프로퍼티  (0) 2024.07.27
C# - C#의 자료형  (0) 2024.07.24
C# - C#과 .NET 프레임워크  (2) 2024.07.23

 

비주얼 스튜디오에서 빈 프로젝트를 만들려고 보니, C#은 저렇게 (.NET Framework)가 뒤에 붙어있는 것이 보인다. C++에는 .NET Framework가 붙어있지 않은 것을 보면 C#은 C++과 달리 .NET Framework 라는 것과 긴밀한 연관이 있다는 것을 알 수 있다.

 

그렇다면, .NET Framework는 무엇이고, C#은 .NET Framework 와 무슨 관련이 있을까?

 

.NET Framework (닷넷 프레임워크)

닷넷 프레임워크는 마이크로 소프트에서 제작한 개발 및 실행 환경이다. 프로그래머는 닷넷 프레임워크가 제공해주는 틀 위에서 자유롭게 프로그래밍을 할 수 있으며, 닷넷 프레임워크 위에서 작동하는 대표적인 언어가 C#인 것이다.

 

닷넷 프레임워크는 단일 언어를 지원하는 것이 아니라 비주얼 베이직, J#등 여러 언어를 지원한다. 즉, 여러 언어에 대해 동일하게 작동하기 위해서 닷넷 프레임워크는 특수한 방식으로 동작한다. 동작 원리를 먼저 간단하게 보면, 아래와 같다.

 

1. 프로그래머에 의해 작성된 코드가 컴파일러에 의해 IL(intermediate Language)로 번역된다.

2.  IL로 번역된 코드와 라이브러리와 링크되어 exe파일, dll파일로 저장된다.

3. exe파일을 실행하게 되면, CLR(common Language Runtime)에 의해 실시간으로 IL이 기계어로 번역되며 실행된다. 

 

아래 그림은 위의 과정을 그림으로 표현한 것이다. 

출처 : https://wikidocs.net/227163

 

 

닷넷 프레임워크 위에서 작성된 코드는 컴파일러에 의해 CIL(IL) 이라는 공용 언어로 번역된다.

이 공용 언어는 CLR이라는 프로그램에 의해 기계어로 번역된다.

 

그렇다면, 왜 이러한 과정을 거칠까?

 

먼저, CIL이란 닷넷 프레임워크에서 사용하는 언어이다. OS가 아닌 프로그램에서 위에서 사용되는 언어이기 때문에 OS에  의존적이지 않다. (OS가 달라도 닷넷 프레임워크가 설치되어 있다면 작동한다는 뜻)

 

하지만, 닷넷 프레임워크 위에서 동작하는 코드도 결국엔 기계어로 번역이 되어야 운영체제가 이를 이해하고 하드웨어에 연산을 요청할 수 있다. 그렇기 때문에 CIL은 CLR이라는 것에 의해 실행 환경에 맞는 기계어로 번역이 된다.

(실제로는 JIT라는 컴파일러가 번역을 수행하고, CLR은 이를 요청하는 것)

 

이 과정을 통해, 닷넷 프레임워크 위에서 작성된 여러 언어들이 통일성있게 실행될 수 있으며, 닷넷 프레임워크만 설치되어 있다면 개발, 실행 환경이 달라지더라도 문제없이 프로그램을 실행할 수 있게 된다.

'C# > C#' 카테고리의 다른 글

C# - const, readonly  (0) 2024.08.10
C# - is, as 연산자  (0) 2024.08.07
C# - 클래스, 접근 제한 지정자, 프로퍼티  (0) 2024.07.27
C# - C#의 자료형  (0) 2024.07.24
C# - 클래스와 콘솔 출력  (2) 2024.07.24
스터디를 진행하며 실시한 모의 면접을 정리한 내용입니다.
겹치는 내용을 고려하지 않고 면접을 실시하기 때문에 일자 별로 겹치는 내용이 많을 수 있습니다.

1. noexcept 키워드에 대해 설명하라.

더보기

함수가 어떠한 예외도 방출하지 않음을 명시하는 키워드입니다. 예외가 발생하지 않음을 확신할 수 있다면, 컴파일러는 그에 맞는 최적화를 진행할 수 있으므로 추가적인 성능 향상을 기대할 수 있습니다.

2. 컨텍스트 스위칭이란?

더보기

CPU를 점유중인 프로세스, 스레드가 다른 프로세스, 스레드로 교체되는 과정을 의미합니다. 잦은 컨텍스트 스위칭은 시스템에 부하를 줄 수 있으므로 이를 최소화하는 방향으로 프로그래밍하는 것이 중요합니다. 

3. 동시성과 병렬성이란?

더보기

동시성이란 물리적으로는 동시에 실행되지 않지만, 동시에 실행되는 것처럼 보이는 것입니다. 하나의 단일코어에서 여러 작업을 처리하는 것이 동시성의 대표적인 예시입니다. 반면, 병렬성은 물리적으로 동시에 실행되는 것을 의미합니다. 다중 코어에서 여러 작업을 처리하는 것이 대표적인 예시입니다. 

4. 템플릿 특수화에 대해 설명하라.

더보기

템플릿은 특정 함수, 클래스가 다양한 자료형에 대응할 수 있도록 도와주는 문법입니다. 템플릿을 사용하염 모든 자료형에 대해 동일한 구조와 기능을 지닌 함수, 클래스 등을 만들 수 있습니다. 템플릿 특수화는 템플릿으로 함수, 클래스 등을 선언한 상태에서 특정 자료형에 대해서는 다른 구조를 취하고 싶을 때 사용합니다. std::vector가 템플릿 특수화를 사용하는 대표적인 예시입니다. std::vector를 boolean타입으로 선언하게 되면 다른 자료형과는 다르게 unsigned int 자료형을 비트단위로 쪼개어 값을 저장하고 사용하게 됩니다.

5. 함수형 프로그래밍이란?

더보기

값을 변경하지 않는 함수를 조합하여 프로그래밍하는 방법론을 의미합니다. 함수 위주의 사용으로 인해 코드의 재사용성이 높습니. 또한, 변수에 쓰기작업을 하지 않기 때문에 멀티 스레드 환경에서 안전하다는 장점도 있습니다. 하지만, 설계가 복잡하고 어렵다는 단점이 있습니다.

6. const 키워드에 대해 설명하고, 위치에 따른 의미를 설명하라.

더보기

const란 대상을 상수화하여 값을 변경할 수 없도록 만드는 키워드입니다. 값형 변수에 붙으면 대상을 변경할 수 없는 상태로 만듭니다. 포인터 변수의 경우엔 앞에 붙을 경우 포인터가 가리키는 대상을 변경할 수 없게 만들며, 포인터 변수의 뒤에 붙으면 가리키는 대상을 상수화합니다.

 

멤버 함수의 경우에도 const를 사용할 수 있습니다. 멤버 함수의 뒤에 const를 붙이게 될 경우 함수 내에서 사용되는 this포인터가 가리키는 대상을 상수화하기 때문에 멤버 변수를 수정할 수 없게 됩니다.  

7. malloc과 new 의 차이를 설명하라.

더보기

malloc는 동적 할당을 할 때 사용하는 함수입니다. 할당 받을 메모리 크기를 인자로 대입하면 동일한 크기의 메모리를 할당하여 가장 앞의 주소를 void*타입으로 반환해줍니다. void* 으로 메모리를 생성하기 때문에 대상의 생성자를 호출하지 않습니다.

 

new는 동적 할당을 할 때 사용하는 연산자입니다. new는 메모리 할당, 생성자 호출, 타입캐스팅의 순서로 이루어지기 때문에 malloc과 달리 반환형이 void*이 아니며, 생성자를 호출해준다는 차이가 있습니다. 또한, 할당 받을 메모리의 크기가 malloc처럼 자유롭지 않고 자료형의 크기에 기반한다는 차이가 있습니다.

8. struct에 char, int가 순서대로 선언되어 있을 때, 해당 구조체의 크기는?

더보기

8바이트의 공잔을 차이합니다. CPU는 데이터를 사용할 때 4바이트 단위로 사용하는 것이 가장 효율적이기 때문에 char타입과 int타입의 크기를 합친 5바이트의 크기로 메모리를 할당하지 않고, char타입에 4바이트를 할당하고, 그 뒤에 int 타입의 메모리를 할당하게 됩니다.

9. static 멤버변수에 대해 설명하라.

더보기

static 멤버변수는 사용 범위가 클래스 내부로 제한되어 있는 static 변수입니다. static 변수인 만큼 데이터 영역에 위치하고 있으며 인스턴스가 아무리 생성되어도 메모리 상에 1개만 존재하게 됩니다.

10. volatile 키워드에 대해 설명하라.

더보기

컴파일러의 최적화를 제한하는 키워드입니다. 컴파일러의 최적화로 인해 작업 결과가 의도와 다를 수 있는 경우에 사용하게 됩니다.

11. Map과 Unordered_map의 차이에 대해 설명하라.

더보기

map과 unordered_map 모두 키와 데이터를 쌍으로 저장하여 사용하기 위한 자료구조입니다.  map은 이진 균형 트리 기반의 자료구조이며, 데이터가 삽입, 삭제될 때마다 정렬을 실행합니다. 반면, unordered_map은 해시 테이블 기반의 지료구조 이며 데이터를 정렬하지 않습니다.

12. Port 넘버란?

더보기

IP와 MAC주소를 통해 PC에 도달하였을 때, 해당 PC에서 여러 프로세스가 실행중이라면 최종 목적지를 식별할 수 없게 됩니다.  이 때, 최종 목적지 프로세스를 식별하기 위해 사용되는 것이 포트 번호입니다. 각 프로세스에 고유의 번호를 부여하여 이를 기반으로 목적지를 식별하게 됩니다. 포트 번호는 실행중인 프로세스 간에 겹치지 않는 것이 중요합니다. 그렇기 때문에 웹 사이트 등에서 이미 사용하고 있는 포트 번호를 파악한 뒤 겹치지 않는 방향으로 설정해야 합니다.

13. 컬링과 클리핑의 차이는? 또한 컬링의 종류를 아는대로 말하라.

더보기

컬링이란 대상을 아예 렌더링에서 제외하는 것이며, 클리핑은 렌더링 된 대상의 일부를 잘라내는 것을 의미합니다.

대표적인 컬링 기법은 백페이스 컬링, 뷰 프러스텀 컬링, 오클루전 컬링, 디스턴스 컬링이 있습니다.

 

백페이스 컬링은 카메라의 시야를 기준으로 반대면을 렌더링에서 제외하는 기법입니다.

뷰 프러스텀 컬링은 카메라의 시야 절두체를 기반으로 외부에 있는 물체를 렌더링에서 제외하는 기법입니다.

오클루전 컬링은 벽 등의 물체에 가려 보이지 않는 대상을 렌더링에서 제외하는 기법입니다.

디스턴스 컬링은 카메라와의 거리가 충분히 멀리있는 대상을 렌더링에서 제외하는 기법입니다.

14. CPU의 구조에 대해 간단하게 설명하라.

더보기

CPU는 크게 ALU, CU, 레지스터로 구성되어 있습니다. CU는 명령어를 해독하여 어떤 부품이 어떤 작업을 실행해야 하는지, 어떤 연산을 실행해야 하는지 등을 결정짓는 역할을 합니다. ALU는 CU로부터 명령받은 산술, 논리 연산을 진행하게 됩니다. 레지스터는 ALU에서 실행된 연산의 결과값이나 연산에 사용될 데이터를 저장하는 역할을 합니다. 

15. 람다함수란?

더보기

람다함수란 이름이 없는 함수를 의미합니다. 다른 함수와 같이 미리 선언과 정의를 해두고 이를 재사용하는 것이 아니라 필요한 상황에 즉석으로 생성하여 사용하게 됩니다. 이러한 차이로 인해 람다함수는 정적 함수와 다르게 스택영역에 위치하게 됩니다. 

 

람다 함수를 잘 활용하면 메모리를 아낄 수 있고, 논리를 쉽게 이해할 수 있다는 장점이 있지만 호출된 함수를 추적하는 것이 어려워 디버깅이 어려워지며 무분별하게 사용하면 코드의 가독성을 저하할 수 있다는 단점이 있습니다. 

16. 벡터의 resize와 reserve의 차이에 대해 설명하라.

더보기

resize는 입력받은 인자의 값만큼 벡터의 size를 증가시키는 역할을 합니다. 반면, reserve는 벡터의 capacity를 입력받은 인자의 값만큼 증가시키는 역할을 합니다. 

17. 동기와 비동기란?

더보기

동기란, 기존의 작업이 종료되어야만 다음 작업을 실행하는 것을 의미합니다. 반면, 비동기는 기존의 작업이 종료되지 않더라도 다음 작업을 실행하는 것을 의미합니다.

18. L-Value 와 R-Value에 대해 설명하라.

더보기

L-Value란 메모리 상에 존재하여 주소를 참조할 수 있는 값을 의미합니다. 반면, R-Value는 호출식이 끝나면 사라지는 임시의 값으로 주소를 참조할 수 없는 값을 의미합니다. 

19. std::atomic이란?

더보기

멀티스레드 환경에서 데이터를 보호하기 위한 기법 중 하나입니다. 변수의 값을 변경하는 경우, 간단한 산술 연산일지라도 값을 읽은 뒤 연산을 수행하고 값을 저장하는 과정이 순차적으로 이루어져야 합니다. 하지만, 중간에 다른 스레드가 개입하여 변수의 값을 변경하게 되면 예측하지 못한 결과가 발생할 수 있습니다. 이를 막기 위해, 반드시 함께 실행되어야 하는 여러 연산을 하나로 묶어 다른 스레드가 중간에 개입할 수 없도록 만들어주는 객체가 std::atomic입니다.

20. IP 와 MAC주소의 차이는?

더보기

IP는 연결된 회선의 주소이기 때문에 접속하는 인터넷에 따라 주소값이 계속 변경되지만, MAC주소는 랜카드에 기록되어 있어 변경되지 않는다는 차이가 있습니다.

21. 함수의 호출 과정을 설명하라.

더보기

함수를 호출하기 전에 현재 스택 프레임의 주소 값을 프레임 포인터 레지스에 저장한 뒤, 함수의 주소를 참조하여 함수를 실행하고, 함수가 종료되면 프레임 포인터 레지스터의 값을 참조하여 기존의 스택 프레임으로 돌아오는 과정으로 진행됩니다.

22. 캐시 지역성이란?

더보기

메인 메모리에 있는 데이터를 캐시 메모리로 복사할 때, 참조한 메모리 주소를 기반으로 근처에 있는 값을 한 번에 가져오게 됩니다. 이 때, 다음 연산에 사용될 변수가 현재 사용된 변수와 메인 메모리에서 가까운 곳에 위치했다면, 캐시 메모리에도 있을 확률이 높아집니다. 이러한 경우 캐시의 공간 지역성이 좋다고 표현합니다. 동일한 데이터를 여러 번 참조하는 경우에도 기존에 참조한 대상이 캐시 메모리에 있을 확률이 높아집니다. 이러한 것을 캐시의 시간 지역성이 좋다고 표현합니다.

23. 인터페이스란?

더보기

인터페이스란, 순수 가상함수를 사용해 특정 메서드의 정의를 강제하는 추상 클래스를 활용해 외부에서 다양한 파생 클래스를 통일된 방식으로 사용할 수 있도록 하는 것입니다.

24. 운영체제란?

더보기

운영체제란, 컴퓨터가 부될 때 가장 먼저 실행되는 소프트웨어입니다. 유저가 하드웨어, 메모리 관리 등의 저수준 영역을 신경쓸 필요 없이 대신 해주는 역할을 합니다. 이로 인해, 유저는 컴퓨터를 안전하고 편리하게 사용할 수 있게 됩니다.

25. 인터럽트란?

더보기

CPU 작업 중 예외가 발생하거나 다른 작업을 우선적으로 처리해야 하는 경우 현재 작업을 잠시 중단하고 우선적으로 다른 작업을 처리하는 것을 인터럽트라고 합니다.

26. SIMD 연산이란?

더보기

SIMD연산이란, 하나의 명령어로 다중 데이터를 처리하는 CPU의 고속 연산 기법입니다. 벡터, 행렬 연산 등에 사용하게 되면 성능 향상을 기대할 수 있습니다.

27. join과 detach에 해 설명하라.

더보기

join이란, 스레드가 작업을 완료할 때까지 메인 스레드의 실행을 중지하는 함수입니다. 반면, detach는 스레드의 작업 완료 상태와 관계없이 메인 스레드는 실행을 유지하게 됩니다. 특정 작업이 모두 완료되어야만 정상적으로 프로그램을 실행할 수 있을 때는 join을 사용하며, 메인 스레드의 흐름과 관계없이 작업을 처리해야 하는 경우엔 detach를 사용하게 됩니다. 

28. 다형성이란?

더보기

하나의 객체, 메서드 등이 다양한 형태로 사용될 수 있는 것을 의미합니다. 상속관계에선 타입캐스팅을 통해 자식 클래스가 부모 클래스로 사용되는 것이나, 함수 오버로딩을 통해 동일한 이름의 함수가 다양하게 사용되는 것이 대표적인 예시입니다.

29. RAII 패턴이란?

더보기

자원의 획득과 해제를 객체의 생애주기와 결합하는 디자인 패턴입니다. 자원의 획득은 객체의 생성자에서 실행되도록 하며, 자원의 해제는 객체의 소멸자에서 실행되도록 설계합니다. 이러한 설계를 통해, 객체가 생성되어 있는 동안 항상 자원이 존재함을 보장할 수 있고 객체가 소멸하는 순간 자원이 해제됨을 보장할 수 있어 메모리 누수를 방지할 수 있으며 안전하게 자원을 사용할 수 있게 됩니다.

30. std::forward에 대해 설명하라.

더보기

인자가 L-Value라면 L-Value의 형태로 리턴하고 R-Value라면 R-Value의 형태로 반환해주는 함수입니다. 템플릿 함수에선 인자가 L-Value인지 R-Value인지 정확히 파악할 수 없어 무브 시맨틱스가 제대로 작동하지 않을 가능성이 있습니다. std::forward를 사용하면 인자의 실제 타입에 맞게 캐스팅되기 때문에 템플릿 환경에서도 무브 시맨틱스가 제대로 작동하도록 할 수 있습니다.

31. 무브 시맨틱이란?

더보기

무브 시맨틱이란, 깊은 복사로 인한 오버헤드를 방지하기 위해 복사가 아닌 이동 연산을 활용하는 기법입니다. 데이터를 얕은 복사한 뒤, 복사되는 대상의 데이터를 지워버리는 방식으로 동작하기 때문에 이동 연산 이후에 사용되지 않을 것이 확실한 객체에 대해서만 사용해야 합니다.

32. explicit 키워드에 대해 설명하라.

더보기

암시적 형변환을 막는 키워드입니다. 대입 연산에서 발생하는 암시적 형변환으로 인해 프로그래머의 의도와 다른 결과가 발생할 수 있는 경우, explicit 키워드를 활용해 명시적 형변환을 강제할 수 있습니다.

33. 뮤텍스와 세마포어의 차이에 대해 설명하라.

더보기

뮤텍스는 임계영역에 1개의 스레드만을 허용하지만, 세마포어는 여러개의 스레드를 허용할 수 있습니다. 뮤텍스의 경우 작업을 진행중인 스레드만이 뮤텍스 객체를 조작할 수 있지만, 세마포어는 외부의 스레드가 세마포어를 조작할 수 있습니다.

34. std::vector에 대해 설명하라.

더보기

std::vector는 C++의 STL에서 제공해주는 배열 기반의 자료구조입니다. 원소가 메모리에 연속적으로 위치하기 때문에 캐시적중률이 높고 임의 접근이 가능하다는 장점이 있지만, 적절한 reserve를 하지 않으면 잦은 동적 할당으로 인한 오버헤드가 발생하거나 메모리 내부 단편화가 발생할 수 있습니다.

STL의 vector 템플릿은 bool 자료형에 대해 특수화되어있다.

bool타입은 기본 1바이트를 사용하지만, 비트연산을 활용하면 1바이트에 8개의 boolean값을 저장할 수 있기 때문이다.

이로 인한 여러 문제들도 있다지만, STL을 모방하는 프로젝트인만큼 bool 타입에 특수화를 진행하였다. 

 

일단은 생성자 부분만 정의하였고, 정의된 내용은 아래 코드와 같다.

다음엔 [], * 등 연산자를 오버로딩할 것이며 이터레이터를 추가하여 begin, end 등의 함수를 정의할 것이다.

template <>
class Vector<bool>
{
public:
    //Default
    Vector() : MySize(0), MyCapacity(32)
    {
        if (MyElements == nullptr)
        {
            MyElements = new unsigned int[MyCapacity]();
            BeginPtr = MyElements;
        }
    }

    //Only Size
    Vector(const size_t _Size)
    {
        if (BeginPtr == nullptr)
        {
            MySize = _Size;
            MyCapacity = _Size + (32 - (_Size % 32));

            MyElements = new unsigned int[MyCapacity](0);
            BeginPtr = MyElements;
        }
    }
    
    //Size, Data
    Vector(const size_t _Size, const bool _Data)
    {
        if (BeginPtr == nullptr)
        {
            MySize = _Size;
            MyCapacity = _Size + (32 - (_Size % 32));

            MyElements = new unsigned int[MyCapacity](0);
            BeginPtr = MyElements;
            
            if (_Data == true)
            {
                RangedBitOn(0, 0, _Size / 32, _Size % 32);
            }
            else
            {
                RangedBitOff(0, 0, _Size / 32, _Size % 32);
            }
        }
    }

    ~Vector()
    {
        if (MyElements != nullptr)
        {
            delete[] MyElements;

            BeginPtr = nullptr;
            MyElements = nullptr;
        }
    }

private:
    void BitOff(size_t _Index, size_t _Bit)
    {
        MyElements[_Index] &= ~(1 << _Bit);
    }

    void BitOn(size_t _Index, size_t _Bit)
    {
        MyElements[_Index] |= (1 << _Bit);
    }

    void RangedBitOn(size_t _StartIndex, size_t _StartBit, size_t _EndIndex, size_t _EndBit)
    {
        size_t Start = _StartIndex * 32 + _StartBit;
        size_t End = _EndIndex * 32 + _EndBit;

        for (size_t i = Start; i < End; i++)
        {
            size_t Index = i / 32;
            size_t Bit = i % 32;

            MyElements[Index] |= (1 << Bit);
        }
    }

    void RangedBitOff(size_t _StartIndex, size_t _StartBit, size_t _EndIndex, size_t _EndBit)
    {
        size_t Start = _StartIndex * 32 + _StartBit;
        size_t End = _EndIndex * 32 + _EndBit;

        for (size_t i = Start; i < End; i++)
        {
            size_t Index = i / 32;
            size_t Bit = i % 32;

            MyElements[Index] &= ~(1 << Bit);
        }
    }

private:
    unsigned int* BeginPtr = nullptr;
    unsigned int* MyElements = nullptr;

    size_t MySize = 0;
    size_t MyCapacity = 0;
};

생성자, 소멸자를 정의하였고 reserve, resize, push_back, emplace_back 를 정의하였다.

기존에 알고 있던 STL의 작동 원리를 바탕으로 만들어보았다.

 

아래는 현재까지 작성된 코드 전문이다.

#pragma once
#include <memory>

template<typename DataType>
class Vector 
{
public:
    //Default
    Vector() : MySize(0), MyCapacity(4)
    {
        if (MyElements == nullptr)
        {
            MyElements = new DataType[MyCapacity]();
            
            BeginPtr = MyElements;
            EndPtr = MyElements;
        }
    }

    //Only Size
    Vector(const size_t _Size) : MySize(_Size), MyCapacity(_Size)
    {
        if (BeginPtr == nullptr)
        {
            MyElements = new DataType[MyCapacity]();

            BeginPtr = MyElements;
            EndPtr = BeginPtr + _Size;
        }
    }

    //Size, Data (L-Value)
    Vector(const size_t _Size, const DataType& _Data) : MySize(_Size), MyCapacity(_Size)
    {
        if (BeginPtr == nullptr)
        {
            MyElements = new DataType[MyCapacity];

            BeginPtr = MyElements;
            EndPtr = BeginPtr + _Size;

            for (size_t i = 0; i < _Size; i++)
            {
          	    MyElements[i] = _Data;
            }
        }
    }

    //Size, Data (R-Value)
    Vector(const size_t _Size, DataType&& _Data) : MySize(_Size), MyCapacity(_Size)
    {
        if (BeginPtr == nullptr)
        {
            MyElements = new DataType[MyCapacity];

            BeginPtr = MyElements;
            EndPtr = BeginPtr + _Size;

            for (size_t i = 0; i < _Size; i++)
            {
                MyElements[i] = std::forward<DataType>(_Data);
            }
        }
    }

    ~Vector()
    {
        if (MyElements != nullptr)
        {
            delete[] MyElements;

            BeginPtr = nullptr;
            EndPtr = nullptr;
            MyElements = nullptr;
        }
    }
    
public:
    void Reserve(const size_t _Capacity)
    {
        ReAllocate(_Capacity);
    }

    void Resize(const size_t _Size)
    {
        ReAllocate(_Size);
        MySize = _Size;
    }

    void Resize(const size_t _Size, const DataType& _Data)
    {
        ReAllocate(_Size);

        for (int i = MySize; i < _Size; i++)
        {
            MyElements[i] = _Data;
        }

        MySize = _Size;
    }

    void Resize(const size_t _Size, DataType&& _Data)
    {
        ReAllocate(_Size);

        for (size_t i = MySize; i < _Size; i++)
        {
            MyElements[i] = std::forward<DataType>(_Data);
        }

        MySize = _Size;
    }

public:
    template<typename... Valty>
    void Emplace_Back(Valty&&... _Value)
    {
        if (MySize == MyCapacity)
        {
            ReAllocate(MyCapacity * 2);
        }

        new (&MyElements[MySize]) DataType(std::forward<Valty>(_Value)...);

        ++MySize;
        ++EndPtr;
    }

    void Push_Back(const DataType& _Data)
    {
        if (MySize == MyCapacity)
        {
            ReAllocate(MyCapacity * 2);
        }
    
        MyElements[MySize] = _Data;
        
        ++MySize;
        ++EndPtr;
    }
    
    void Push_Back(DataType&& _Data)
    {
        if (MySize == MyCapacity)
        {
            ReAllocate(MyCapacity * 2);
        }
    
        MyElements[MySize] = std::forward<DataType>(_Data);
        
        ++MySize;
        ++EndPtr;
    }

private:
    void ReAllocate(const size_t _Capacity)
    {
        if (_Capacity <= MyCapacity)
        {
            return;
        }


        DataType* NewPtr = new DataType[_Capacity]();

        for (size_t i = 0; i < MySize; i++)
        {
            NewPtr[i] = std::move(MyElements[i]);
        }

        BeginPtr = NewPtr;
        EndPtr = BeginPtr + MySize;

        delete[] MyElements;

        MyElements = NewPtr;

        MyCapacity = _Capacity;
    }

private:
    DataType* BeginPtr = nullptr;
    DataType* EndPtr = nullptr;

    DataType* MyElements = nullptr;
    
    size_t MySize = 0;
    size_t MyCapacity = 0;
};


용과 같이 7에서 이치반이 하도 드퀘 타령하길래 궁금해서 해봤다.

아주 옛날 게임인데 생각보다 재밌었다 요즘 JRPG 시스템이 대부분 드퀘에서 왔다던데 진짜 비슷한 시스템이 많았다 이 게임을 그 당시에 했다면 더 즐겁게 했을텐데 조금 아쉽다.. 그리고 모바일로 이식하면서 글씨체나 UI가 원본에 비해 좀 조잡해진 것도 아쉽다.. 

 

플레이 타임 자체는 매우 짧은 편이었지만 나름 재밌게 즐겼다

+ Recent posts