파란색 : 풀면서 어려웠던 부분 or 고려해야 될 부분

빨간색 : 해결방법

초록색 : 느낀 점

문제

k값만큼 알파벳 순서인 뒤로 이동.

 

1. 대소문자 구별

2. 알파벳 외에 다른 문자는 변경하지 않는다.

3.'z' , 'Z'를 넘어가면 다시 'a', 'A' 순서로

풀이 : 내가 작성한 코드

            public static string caesarCipher(string s, int k)
            {
                string strAnswer = string.Empty;
                for (int i = 0; i < s.Length; i++)
                {
                    if (s[i] >= 'a' && s[i] <= 'z' )
                    {
                        int a = s[i] - (97 - k);
                        strAnswer += ((char)((a % 26) + 97));
    
                    }
                    else if(s[i] >= 'A' && s[i] <= 'Z')
                    {
                        int a = s[i] - (65 - k);
                        strAnswer += ((char)((a % 26) + 65));
                    }
                    else
                    {
                        strAnswer += s[i];
                    }
                }
                return strAnswer;
            }

고려할 부분은 대소문자 체크와 그 외 문자 체크 정도였다.  z이면 다시 a로 이동하도록 하는 부분을 어떻게 할지는 고민했었다. 알파벳 개수인 26을 % 연산을 통해서 체크했다.  

 

Caesar Cipher | HackerRank

Encrypt a string by rotating the alphabets by a fixed value in the string.

www.hackerrank.com

알고리즘 문제는 풀면 풀수록 자주 등장하거나 디테일한 값을 구하는 패턴이 있는 거 같다. 많이 풀어봐야겠다. 

반응형

Cache

자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 원래의 데이터를 접근하는 시간이 오래 걸리거나 반복적으로 동일한 결과를 돌려주는 경우 데이터를 직접적인 접근으로 인한 병목현상을 막기 위해 사용되는 저장소이다. 

 


캐시 철학

 

Temporal locality

시간적으로 보면, 최근에 사용했던 기억 장소들이 집중적으로 액세스 되는 경향이 있다. 접근되었던 적이 있는 곳에는 다 시 접근할 가능성이 높다는 판단

 

Spacial locality

프로그램 실행 시 접근하는 메모리의 영역은 이미 접근이 이루어진 영역의 근처일 확률이 높다.

 

테스트 코드

            int[,] arr = new int[10000, 10000];

            {
                long now = DateTime.Now.Ticks;
                for (int i = 0; i < 10000; i++)
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        arr[i, j] = 1;
                    }
                }
                long end = DateTime.Now.Ticks;
                Console.WriteLine($"(i,j) 순서 걸린 시간{end - now}");
            }

            {
                long now = DateTime.Now.Ticks;

                for (int i = 0; i < 10000; i++)
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        arr[j, i] = 1;
                    }
                }
                long end = DateTime.Now.Ticks;
                Console.WriteLine($"(j,i) 순서 걸린 시간{end - now}");
            }

 테스트 결과로 봤을 때 같은 동작이지만 두 개의 결과가 많은 시간이 차이가 보이는 걸 알 수 있다. Spacial locality관점으로 근접한 공간적 메모리 영역을 접근했을 때 좀 더 짧게 수행되기 때문이다. 

 


https://talkingaboutme.tistory.com/entry/Study-Memory-Hierarchy-1

 

반응형

'STUDY > 운영체제' 카테고리의 다른 글

운영체제) 교착상태 (deadlock)  (0) 2019.08.16

멀티스레드

둘 이상의 스레드가 하나의 프로세스에서 동시에 작업하는 것을 의미, 하나의 프로세스 내의 여러 스레드들은 프로세스에게 할당받은 자원을 공유한다. (스택 영역은 공유 X)

 

스레드 스케줄링

스레드를 어떤 순서로 동시성을 실행할 것인지 결정하는 것, (우선순위, 순환 할당 방식이 있다.)

스레드가 많다고 좋은 건 아니다.

컴퓨터가 동시에 작업 수는 CPU의 코어 수와 같다. CPU의 코어 수보다 스레드를 많이 사용하게 되면 문맥 교환이 발생하는데 오히려 효율은 저하된다. 코어 수만큼 스레드를 실행시키는 것이 좋다.

 

 


https://tcpschool.com/java/java_thread_multi

반응형

'STUDY > 네트워크' 카테고리의 다른 글

[네트워크] 유니티 네트워크 공부#1  (0) 2021.08.09
네트워크) TPC/IP 프로토콜  (0) 2019.11.17
네트워크) 네트워크 구성  (0) 2019.11.13

파란색 : 문제점

빨간색 : 해결방법

초록색 : 느낀 점

문제

Google sheet to Json이라는 에셋을 추가했을 때 발생 Newtonsoft.Json.dll이 중복으로 있어 문제인 거 같았다.

오류 해결방법

 

추가한 GSpreadSheets 폴더

 

 

Library\PackageCache에 있는

Library\PackageCache\com.unity.nuget.newtonsoft-json@2.0.0\Runtime에 있는 Newtonsoft.Json.dll를 제거해준다.

느낀 점

내가 사용하는 유니티 프로젝트 버전은 2021 버전으로 최신을 낮은 버전으로 Google sheet to Json에셋을 추가했을 때는 아무 문제가 없었다. 검색한 본 걸로는 2020? 버전부터 패키지에 com.unity.nuget.newtonsoft-이 추가되어있는 것 같다.  최신 버전의 유니티에 에셋들은 아직 버전 대응이 되지 않았던 것 같다. 유니티 버전을 바꾸면서  고려야 해야 할 사항을 체크가 필요할 것 같다.

 

 

참고 

https://www.reddit.com/r/Unity3D/comments/o18fet/multiple_precompiled_assemblies_with_the_same_name/

반응형

네트워크 프로그래밍 관련 공부를 하면서 간단히 정리해보았다. TCP

 

ServerCore(서버) /  Client(클라이언트) 두 개의 스크립트를 작성한다.

 

ServerCore

string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

host : ip주소가 아닌 도메인주소로 사용하도록 한다. 

AddressList : IP주소 리턴

IPEndPoint : IP주소와 포트번호를 통해 생성

 

Listener.class

매개변수 OnAcceptHandler : Accept이 성공했을 때 불리는 콜백

_listenSocket.Bind(endPoint); : 넘겨 받아온 IPEndPoint의 바인드 처리

_listenSocket.Listen(10); : Listen 시작 클라이언트의 연결 요청을 대기상태

SocketAsyncEventArgs : 비동기 소켓의 관련한 패턴을 사용할 때? 쓰임

args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted); : 작업이 완료되면 OnAcceptCompleted을 호출

RegisterAccept(args); : 최조 한번 등록

 

args.AcceptSocket = null; : 전에 사용했던 args이기 때문에 null 초기화 작업

bool pending = _listenSocket.AcceptAsync(args); : 완료되면 false를 리턴

즉시 완료되면 OnAcceptCompleted 실행

 

_OnAcceptHandler.Invoke(args.AcceptSocket); : 연결 성공시 송수신 처리

RegisterAccept(args); : 다시 Accept 처리.

 

Init()에서 최초 Accept 처리 -> Accept 성공 완료 콜백 OnAcceptCompleted 에서 Accept 처리 -> Accept처리 ...

로 계속 반복해서 Accept을 시도한다.


 

인프런 유니티로 만드는 MMORPG 게임 개발 시리즈를 학습 예제

반응형

'STUDY > 네트워크' 카테고리의 다른 글

[네트워크] 멀티스레드 #1  (0) 2021.09.23
네트워크) TPC/IP 프로토콜  (0) 2019.11.17
네트워크) 네트워크 구성  (0) 2019.11.13

유니티 커스텀 에디터로 맵툴 만들기 두 번째 포스팅

오늘 공부할 에디터 뷰


            GUILayout.BeginVertical("box");

            GUILayout.Label("Map Name");
            _strMapName = EditorGUILayout.TextField(_strMapName/*, GUILayout.ExpandWidth(true)*/);
            GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;
            if (GUILayout.Button("Create New Map", GUILayout.ExpandWidth(true)))
            {
                Debug.LogError("create " + _strMapName);
                CreateMap();
                _bEditOn = true;
            }

            GUILayout.Space(12);

1. GUILayout.BeginVertical("box"); 박스로 묶는 범위(진한 회색으로)를 지정하게 된다.

2. Create New Map 버튼 : EditorGUILayout.TextField()에 string값이 존재 시 활성화.

3. CreateMap()함수 : 새로운 맵을 생성함

 

 


            GUI.enabled = true;
            GUILayout.Label("Load MapData");
            _txtAssetMapContent = (TextAsset)EditorGUILayout.ObjectField(_txtAssetMapContent, typeof(TextAsset), GUILayout.ExpandWidth(true));
            GUI.enabled = _txtAssetMapContent != null ? true : false;
            if (GUILayout.Button("Edit Map", GUILayout.ExpandWidth(true)))
            {
                LoadMap(_txtAssetMapContent.text);
                _bEditOn = true;
            }
            GUILayout.EndVertical();

1. EditorGUILayout.ObjectField() : 끌어다 붙여도 되고, 동그라미 눌러서 프로젝트 리소시스에 텍스트 파일을 선택해도 됨

2. Edit Map 버튼 : _txtAssetMapContent에 값이 들어와야 활성화.

3. LoadMap() 함수 : 선택한 텍스트 파일을 가지고 하이어라키에 맵 파일을 로드시킨다.

반응형

커스텀 에디터를 이용한 맵툴 제작

 

작업 결과물

 

GUILayout.Label(string text );

 GUILayout.Label("Map Name");

=> 윈도 류창에 텍스트 라벨에 원하는 string을 표시할 수 있다.

 

EditorGUILayout.TextField( )

_strMapName = EditorGUILayout.TextField(_strMapName);
 GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;

=> 텍스트 입력필드, GUI.enabled을 통해 입력값이 있어야 

 

GUI.enabled

GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;

=> 해당 코드 아래에 버튼이나 입력 칸을 활성화 처리 여부를 정한다.

 

GUILayout.Button( )

if (GUILayout.Button("Create New Map", GUILayout.ExpandWidth(true)))
{
    CreateMap();
    _bEditOn = true;
}

=> 버튼 , 클릭하면 if안에 함수들이 실행된다.

 

GUILayout.Space()

GUILayout.Space(12);

=> 여백을 준다.

 

EditorGUILayout.ObjectField()

_txtAssetMapContent = (TextAsset)EditorGUILayout.ObjectField(_txtAssetMapContent, typeof(TextAsset), GUILayout.ExpandWidth(true));

=> 설정한 타입으로 입력받을수있는 링크 필드가 생긴다. 나는 TextAsset으로 텍스트 관련 오브젝트만 입력받을 수 있도록 했지만 해당 타입을 그냥 object라고 하면 어떠한 오브젝트도 받을 수 있는 상태가 된다.

반응형

Cpaacity를 먼저 설정했을 때와 그냥 사용했을 때에 차이점을 테스트해봤다.

첫 번째 테스트

리스트에 같은 수의 데이터를 넣고 지우 고를 반복했을 때 걸리는 시간 체크하기. 여기 포인트는 미리 정해놓은 Cpaacity의 크기를 넘지 않을때이다.

 

테스트 코드

	long lPre = System.GC.GetTotalMemory(false);
        int tick1 = Environment.TickCount;
        List<int> list = null;
        if (bCapacity)
            list = new List<int>(10000000);
        else
            list = new List<int>();

        for (int k = 0; k < 10; k++)
        {
            for (int i = 0; i < nCount; i++)
            {
                for (int j = 0; j < nCount; j++)
                {
                    list.Add(i + j);
                }
            }
        }
        int tick2 = Environment.TickCount;
        long lAfter = System.GC.GetTotalMemory(false);

        Debug.LogError(string.Format("Capacity Use : {0} | list Count : {1} / Tick : {2}/ Memory : {3}", 
        			bCapacity, list.Count, tick2 - tick1, lAfter - lPre));

1. Capacity을 사용 안 했을 때.

list Count : 10000000 / Tick : 172/ Memory : 134279168

2. Capacity을 사용했을 때.

 list Count : 10000000 / Tick : 109/ Memory : 40001536


테스트 결과

Capacity를 크기를 미리 설정한 케이스가 시간이나 메모리가 더 적게 발생하는 걸 알 수 있다. 

 


 

두 번째 테스트

재 할당될 때 List의 Capacity변화를 테스트해봤다.

 

테스트 코드

        List<int> list = new List<int>();
        Debug.LogError(list.Capacity);
        list.AddRange(new int[4] { 1, 2, 3, 4 });
        Debug.LogError(list.Capacity);
        list.Add(1);
        Debug.LogError(list.Capacity);

처음에 4개의 데이터를 넣고 그 범위를 초과할 때 Capacity를 로그 찍어봤다.

 

테스트 결과

데이터의 수는 4개에서 5개로 1개 증가했지만 현재 사이즈의 2배만큼 Capacity를 할당된 것을 알 수 있다.

 


오늘 테스트 최종 결과

List에 사용하는 최대 크기를 안다면 Capacity를 미리 설정해두는 것이 좋다. List는 현재 가지고 있는 Count수가 초과될 때 기존의 데이터를 복사한 후 다시 2배 사이즈로 재할당하게 된다(기존의 있던 데이터는 가비지 해제 대상으로 잡히게 된다.) 만약 할당된 Capacity를 현재 데이터의 수만큼 변경하고 싶으면 TrimExcess 을 이용한다.

 

 

반응형

'프로그래밍언어 > C#' 카테고리의 다른 글

C#) Async, Await  (0) 2022.08.15
C#) Enum.Parse  (0) 2020.04.17
C#) String null 검사,체크 (널처리)  (0) 2020.04.16
C#) 전처리기 지시자  (0) 2020.04.12
C#) 동적바인딩 / 정적바인딩  (0) 2020.04.08

#1에 이어 #2이다. 이번에는 Client코드를 살펴보자.

 

먼저 소켓에 접속하는 함수

 

ConnectToServer()

    public void ConnectToServer()
    {
        // 이미 연결되었다면 함수 무시
        if (_bSoketReady) return;

        // 기본 호스트/ 포트번호
        string ip = ins_inputIPNumber.text == "" ? "127.0.0.1" : ins_inputIPNumber.text;
        int port = ins_inputPort.text == "" ? 9999 : int.Parse(ins_inputPort.text);

        // 소켓 생성
        try
        {
            _tcpSoket = new TcpClient(ip, port);
            _NetworkStream = _tcpSoket.GetStream();
            _writer = new StreamWriter(_NetworkStream);
            _reader = new StreamReader(_NetworkStream);
            _bSoketReady = true;
        }
        catch (Exception e)
        {
            Chat.instance.ShowMessage($"소켓에러 : {e.Message}");
        }
    }

 

 

OnIncomingData

    void OnIncomingData(string data)
    {
        if (data == "%NAME")
        {
            _strClientName = ins_inputNickName.text == "" ? "Guest" + UnityEngine.Random.Range(1000, 10000) : ins_inputNickName.text;
            Send($"&NAME|{_strClientName}");
            return;
        }

        Chat.instance.ShowMessage(data);
    }

if(data = "%NAME") : 으로 들어오는 %,&인 특수문자로 체크하는데 영상 댓글에 보니 들어오기 전 이름, 나갈 이름을 구분하는 용도라 한다. 여기서는 % 들어오기 전에 이름인거같다.

사용자가 입력한 string이 없으면 Guest + 숫자로 임의로 만들어준다.

send : &NAME으로 서버에 보낸다.

 

연결되는 부분이 Server클래스에 OnIncomingData()에서

//Server.cs.

void OnIncomingData(ServerClient c, string data)
    {
        if (data.Contains("&NAME"))
        {
            c.clientName = data.Split('|')[1];
            Broadcast($"{c.clientName}이 연결되었습니다", _listClients);
            return;
        }

        Broadcast($"{c.clientName} : {data}", _listClients);
    }

연결된 클라이언트의 이름을 접속한 클라이언트들에게 보내는 내용이다.

 

OnSendButton

    public void OnSendButton(InputField SendInput)
    {
#if (UNITY_EDITOR || UNITY_STANDALONE)
        if (!Input.GetButtonDown("Submit")) return;
        SendInput.ActivateInputField();
#endif
        if (SendInput.text.Trim() == "") return;

        string message = SendInput.text;
        SendInput.text = "";
        Send(message);
    }

메시지 보내기 버튼으로 입력된 내용을 send 해주는 과정이다.

 

 

OnApplicationQuit

    void OnApplicationQuit()
    {
        CloseSocket();
    }
    
    void CloseSocket()
    {
        if (!_bSoketReady) return;

        _writer.Close();
        _reader.Close();
        _tcpSoket.Close();
        _bSoketReady = false;
    }

앱 종료하면 연결된 소켓들의 정보를 끊어준다.

 

tcp에 대한 간단한 개념잡기에는 괜찮은거 같다.

 

참고:

https://www.youtube.com/watch?v=y3FU6d_BpjI

반응형

유튜브에 c# tcp소켓통신 채팅 관련 영상이 있었다 그 영상의 코드를 공부해봤다.

 

먼저 ServerClinet 

public class ServerClient
{
    public TcpClient tcp;
    public string clientName;

    public ServerClient(TcpClient clientSocket)
    {
        clientName = "Guest";
        tcp = clientSocket;
    }
}

TcpClient : TCP 네트워크 서비스에 대한 클라이언트 연결을 제공한다.

 

ServerCreate

    public void ServerCreate()
    {
        _listClients = new List<ServerClient>();
        _listDisconnect = new List<ServerClient>();

        try
        {
            int port = ins_PortInput.text == "" ? 9999 : int.Parse(ins_PortInput.text);
            _server = new TcpListener(IPAddress.Any, port);
            _server.Start(); //바인드 처리.

            StartListening();
            _bserverStarted = true;
            Chat.instance.ShowMessage($"서버가 {port}에서 시작되었습니다.");
        }
        catch (Exception e)
        {
            Chat.instance.ShowMessage($"Socket error: {e.Message}");
        }
    }

port로 포트번호를 만들어준다. (현재 사용하지 않는 포트번호로 지정)

IPAddress.Any : 모든 클라이언트에서 오는 요청을 받겠다는 의미

TcpListener.Start : 들어오는 연결 요청의 수신을 시작. (바인드)

 

StartListening

    private void StartListening()
    {
        _server.BeginAcceptTcpClient(AcceptTcpClient, _server);
    }

BeginAcceptTcpClient(AsyncCallback? callback, object? state) : 들어오는 연결 시도를 받아들이는 비동기 작업을 시작한다.

AsyncCallback은 작업이 완료됐을때 호출한다.

state는 연결을 받아들이는 작업자에 대한 정보가 들어 있는 정의 개체이다. 작업이 완료되면 callback대리자에게 전달한다.

 

AcceptTcpClient

    private void AcceptTcpClient(IAsyncResult ar)
    {
        TcpListener listener = (TcpListener)ar.AsyncState;
        _listClients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
        StartListening();

        // 메시지를 연결된 모두에게 보냄
        Broadcast("%NAME", new List<ServerClient>() { _listClients[_listClients.Count - 1] });
    }

매개변수

IAsyncResult : 비동기 작업의 상태를 나타낸다.

IAsyncResult 

TcpListener.EndAcceptTcpClient() : 들어오는 연결 시도를 비동기적으로 받아들이고 원격 호스트 통신을 처리할 새로운 TcpClient을 만든다. (리턴 TcpListener)

 

AcceptTcpClient이 실행 된후에 다시 StartListening()를 실행시켜 새로운 Client를 받을 수 있도록 처리한다.

 

Update()

	priavate void Update()
    {
        if (!_bserverStarted)
        { 
            return;
        }

        foreach (ServerClient c in _listClients)
        {
            // 클라이언트가 여전히 연결되있나?
            if (!IsConnected(c.tcp))
            {
                c.tcp.Close();
                _listDisconnect.Add(c);
                continue;
            }
            // 클라이언트로부터 체크 메시지를 받는다
            else
            {
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    string data = new StreamReader(s, true).ReadLine();
                    if (data != null)
                        OnIncomingData(c, data);
                }
            }
        }

        for (int i = 0; i < _listDisconnect.Count - 1; i++)
        {
            Broadcast($"{_listDisconnect[i].clientName} 연결이 끊어졌습니다", _listClients);

            _listClients.Remove(_listDisconnect[i]);
            _listDisconnect.RemoveAt(i);
        }
    }

Update에서 클라이언트들의 접속여부와 채팅 내용을 체크하도록 한다.

 

IsConnected

   bool IsConnected(TcpClient c)
    {
        try
        {
            if (c != null && c.Client != null && c.Client.Connected)
            {
                if (c.Client.Poll(0, SelectMode.SelectRead))
                    return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);

                return true;
            }
            else
                return false;
        }
        catch
        {
            return false;
        }
    }

 

if(c.Client.Poll(0, SelectMode.SelectRead))

TcpClient.Poll(int microseconds, SelectMode mode); : 메소드는 간단히 말하자면 하려는 행동이 완료할 수 있는 상태면 true를 리턴한다. 

매개변수

microseconds : 응답을 기다리는 시간

SelectMode : 오류상태모드, 읽기 상태 모드, 쓰기 상태 모드를 선택한다.

=> 데이터를 읽을 수 있다면 true를 반환한다.

 

!(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);

socket.Receive(Byte [], Int32, Int32, SocketFlags, SocketError)

매개변수

byte[] 수신된 데이터에 대한 스토리지 위치인 Byte형식의 배열

SoketFlags.Peek : 소켓 전송 및 수신 동작을 지정(Peek:들어오는 메시지를 미리 본다)

=> 1바이트를 보내고 실제 수신된 바이트를 확인하여 연결여부를 확인한다.

 

다시 위쪽에 else부분을 살펴보면,

            else
            {
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    string data = new StreamReader(s, true).ReadLine();
                    if (data != null)
                        OnIncomingData(c, data);
                }
            }

TcpClient.GetStream() : 데이터를 보내고 받는 데 사용되는 NetworkStream을 반환한다.

NetworkStream.DataAvailable() : 데이터를 읽을 수 있는지 여부를 나타내는 값을 가져온다.( 읽을 수 있으면 true, 그렇지 않으면 false)

 

이제 NetworkStream.DataAvailable() 까지 성공했다면 밑에 함수를 통해

    void OnIncomingData(ServerClient c, string data)
    {
        if (data.Contains("&NAME"))
        {
            c.clientName = data.Split('|')[1];
            Broadcast($"{c.clientName}이 연결되었습니다", _listClients);
            return;
        }

        Broadcast($"{c.clientName} : {data}", _listClients);
    }

 

Broadcast()

    void Broadcast(string data, List<ServerClient> cl)
    {
        foreach (var c in cl)
        {
            try
            {
                StreamWriter writer = new StreamWriter(c.tcp.GetStream());
                writer.WriteLine(data);
                writer.Flush();
            }
            catch (Exception e)
            {
                Chat.instance.ShowMessage($"쓰기 에러 : {e.Message}를 클라이언트에게 {c.clientName}");
            }
        }
    }

리스트로 받은 모두 ServerClient에 메세지를 전달하는 과정이다.

StreamWriter.Flush : 현재writer의 모든 버퍼를 지우면 버퍼링된 모든 데이터가 내부 스트림에 쓰여진다.

 

참고

https://www.youtube.com/watch?v=y3FU6d_BpjI 

https://docs.microsoft.com/ko-kr/dotnet/api/system.net.sockets

반응형

파란색 : 문제점

빨간색 : 해결방법

초록색 : 느낀점

문제

빌드된 프로젝트를 돌렸을때 데이터와 이미지들이 빠진 상태로 진행되지 않는 현상이 있었다. 번들이 다운로드가 완료되지 않은 상태에서 이미지를 가져오려고 하는 경우였다. 유니티 에디터에서는 번들을 다운로드 하는 과정이 없이 진행되기에 오류가 발생되지 않아서 놓치고 있던 부분이었다. 

오류 해결방법

번들을 모두 완료됐다고 됐을 경우에 게임이 진행될수 있도록 셋팅한다. 

수정 전

우선 문제가 되는 부분은 LoadAssetAsyncGameObject이라는 위 함수안에 Addressables.LoadAssetsAsync()가 타입별로 한번만 실행되는줄 알았다. 하지만 EmAssetType타입의 해당하는 모든 객체가 로드될때마다 동작된다. 

 

 

수정 후

AsyncOperationHandle<IList<Object>> handle의 IsDone을 현재 에셋이 모두 불러와졌는지 체크할수 있었다.

+) 어드레서블에는 유니티 에디터에서 번들을 불러오는 것처럼 테스트할수 있는 기능이 있다

 

느낀점

느낀점은 어드레서블의 기능을 완벽히 숙지 못하고 코드를 설계했다. 코드 한줄 한줄 모두 이해하고 적용해야한다는것을 다시 깨닫게 되었다. 

 

 

 

참고 

docs.unity3d.com/Packages/com.unity.addressables@1.13/manual/LoadingAddressableAssets.html

반응형

번들관련 공부를하다가 동적으로 생성되는 파일이나 저장되는 데이터들이 어디로 저장되는지 알아보았다.

 

1. PlayerPrefs 저장위치

단말기에 저장되는 PlayerPrefs 저장경로

 

[레지스트리 편집기] -> [HKEY_CURRENT_USER] -> [SOFTWARE] -> [Unity] -> [UnityEditor] -> [DefaultCompany] ->["ProductName"] 

(DefaultCompany 와 ProductName은 ProjectSettings에서 볼수 있음)

Int형
string형

 


2. 앱 저장 경로 Application클래스

빌드된 패키지에는 에디터에서 폴더에 접근할수 없다. 

 

1) Application.dataPath

[C:\Users\사용자이름\AppData\LocalLow\회사이름]

읽기 전용


2) Application.persistentDataPath

[해당 프로젝트폴더 경로\Assets]

 

3) Application.streamingAssetsPath
[해당 프로젝트 폴더]

읽기 전용이다.

 

 

테스트 결과.

1) 에디터

2) 안드로이드

 


유니티에서 지정한 폴더들의 특징 정리

 

wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder

반응형

 

오랜만에 글을 작성한다.

 

사내 스터디 프로젝트에는 어드레서블을 사용한다. 그렇기 때문에 어드레서블에 학습이 필요했다.! 알아야 쓰지

 

리소스 폴더 사용

장점은 사용하기에 편리하다가 있다. 하지만, 리소스폴더에 메모리는 최대한 줄이도록 한다. 시작 시 최소한의 에셋만 남겨두어야겠지만? 리소스 폴더를 경계해야 한다.

단점으로는

1. apk사이즈가 커진다.

2. 앱 시작 시간이 길어진다.

3. 앱이나 폴더 변경 시 재 빌드를 해야 한다. (apk빌드에 묶이기 때문에 에셋 변경 시 무조건 재 빌드해야 한다.)

4. 에셋 이름 변경이 힘들다. (에셋을 로드할 때 경로를 바꿔줘야 하기 때문)

 

이런 문제점들을 보완할 수 있는 에셋 번들이 있다.

에셋 번들

장점은 에셋을 묶음 단위로 관리할 수 있다. 빌드 사이즈 절감과, 앱 시작 시간을 단축시킨다. 있지만

단점으로는

1. 번들의 종속성 문제.

같은 이미지를 Asset A와 Asset B에 묶여있다면 같은 이미지 1개로 처리될 것 같지만 2개의 이미지로 인식된다.

 

이러한 에셋 번들(빌드와 번들의 분리)의 장점과 리소스 폴더의 장점(비교적 편리함)을 가진 어드레 서블 에셋 시스템입니다. 


어드레 서블 에셋

어드레서블 에셋이란 어드레스가 할당된 에셋이다. 어드레스를 이용하여 에셋 위치에 상관없이 참조가 가능하다.

어드레서블 에셋 시스템

어드레스를 이용하여 에셋의 관리, 로딩, 빌드가 통합된 시스템이다.

 

어드레 서블 에셋 시스템. 특징만 봐도 편리해 보인다. 장점을 자세하게 보면

1. 에셋 빌드와 배포의 단순화 : 직접 레퍼런스, 리소스 폴더, 에셋 번들 분리가 단순화되고 편리해진다.

2. 효율적인 에셋 관리 : 종속성 파악 및 메모리 로드/언로드 현황을 볼 수 있다. (중복된 에셋으로 메모리를 낭비가 되는 것을 막는다.)


 

이제 어드레 서블 기능에 대해서 알아보자!

 

1. 해당 프리 팹에 어드레스 부여 하기.

어드레 서블 체크박스에 체크하고 어드레스를 부여하면된다. 그리고 잘 되어있는지 어드레서블 현황 대시보드를 통해 확인한다.

2. 에셋 로드하기.

1) Lables 으로 로드하기.

		Addressables.LoadAssetsAsync(type.ToString(), (Object obj) =>
		{
			if (!m_dicObject.ContainsKey(type))
				m_dicObject.Add(type, new Dictionary<string, Object>());

			m_dicObject[type].Add(obj.name, obj);

		});

위 코드에 type이 어드레서블에 Lables이다. 

 

2) 레퍼런스로 로드하기.

이런 식으로 인스펙터에 레퍼런스를 지정하여 로드하는 방식이다.

 

3) 어드레 서블 이름으로 로드하기.

    public void LoadAsset(string strAddressableName)
    {
       Addressables.InstantiateAsync(strAddressableName);
    }

2),3) 번은 현재 프로젝트에서는 사용하지 않는 방법이지만 어떤 식으로 로드하는지 찾아봤다. 

반응형

유니티가 제공하는 인앱결제 IAP에 대한 학습.

간단하게 인앱컨트롤러를 만들어서 게임시작과 동시에 인앱초기화작업이 되도록 한다.

 

1. var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

AddProduct ~~~...

builder를 통해 스토어에 등록된 상품의 아이디와 같은 데이터를 입력한다.

 

2. UnityPurchasing.Initialize(this, builder);

등록된 builder와 Store클래스를 초기화 한다.

 

초기화

1. IStoreListener.OnInitialized(IStoreController controller, IExtensionProvider extensions)

초기화가 성공하면 IStoreController 과 IExtensionProvider 을받습니다.

 

2. IStoreController 

Unity IAP를 제어하는데 사용하며 데이터및 구매 영수증을 포함한 제품을 저장하고 있다.

3. IExtensionProvider

상점 별 확장 기능에 대한 엑세스를 제공 (extensionProvider.GetExtension<IAppleExtensions>()) 등 특정 스토어 관련한 

 

초기화 실패

1. OnInitializeFailed

초기화 실패 시InitializationFailureReason를 통해 원인을 볼 수 있음 

 

구매실패

1. Product

구매 시도한 상품의 대한 정보.

2. PurchaseFailureReason

구매 실패한 원인

 

구현결과

인앱 초기화의 성공하면 Purchase함수를 통해 각각 상품 구매 테스트 진행

 

사실 실제 상용게임에서는 여러가지 이유로 구매실패가 많이 발생한다. 게임 구매 시도중에 단말기가 종료되거나 통신이 끊기는 경우가 있기에 거기에 대응이 되는 코드와 조건이 많이 필요하다. 

반응형

타이머를 간단하게 만들어봤습니다. 

 

타이머 시작 부분

    public void StartTimer(int nRemain, Text txtTimer = null, UnityAction EndCallBack = null)
    {
        ClearTimer();
        _CorTimer = StartCoroutine(CorStartTime(nRemain, txtTimer, EndCallBack));
    }

 

nRemain : 파라미터로 타이머를 동작시킬 시간

txtTimer : 남은 시간을 표기할 Text UI

EndCallBack : 타이머가 완료 후 실행시킬 함수.

 

코 루틴으로 작동되기 때문에 Clear 하는 부분을 꼭 넣어주도록 합니다. 

    private void ClearTimer()
    {
        if (_CorTimer != null)
        {
            StopCoroutine(_CorTimer);
            _CorTimer = null;
        }
        _bPause = false;
    }

타이머 동작 부분

    private IEnumerator CorStartTime(int nRemain = 5, Text txtTimer = null, UnityAction EndCallBack = null)
    {
        while (nRemain >= 0)
        {
            txtTimer.text = nRemain + " 초 남음";

            while (_bPause)
            {
                yield return null;
            }

            yield return new WaitForSeconds(1f);
            nRemain--;
        }
        EndCallBack?.Invoke();
    }

 

이 타이머스크립트에는 코 루틴으로 작동되며, WaitForSeconds(1f)로 1초마다 남은 시간을 감소하도록 했습니다. 저는 EndCallBack으로 델리게이트로 넘겨받도록 했는데 이유는 다른 곳에서도 쓰일 수 있도록 했습니다. 

 

 

그리고 매프 레임 텍스트만 바꾸지만 매 프레임 단위로 실행시키는 함수가 필요하면 EndCallBack처럼 받고 매프 레임 실행시켜주면 되겠죠. 


일시정지

타이머를 잠깐 멈추는 일시정지를 하는 부분입니다. 버튼 부분에 달아주고 OnPointerDown과 OnPointerUp을 통해 제어하도록 해줬습니다.

public class EventButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    private TimerUI _TimerUI = null;
    public void SetData(TimerUI TimerUI)
    {
        _TimerUI = TimerUI;
    }
    //클릭 누름.
    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.LogError("퍼즈");
        _TimerUI.ins_Timer.SetPause(true);
    }
    //클릭 뗌.
    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.LogError("퍼즈 취소");
        _TimerUI.ins_Timer.SetPause(false);
    }

}

 ins_Timer는 타이머 동작부분이 구현되어있는 스크립트입니다. SetPause를 통해 코루틴을 잠시 멈추도록합니다.

 


구현결과

반응형

스칼라

벡터가 크기와 방향이 있다면 스칼라는 크기만을 갖는 물리량이라고 말한다. (길이,넓이,시간,온도,질량,속력,에너지)

스칼라 왜 필요할까?

벡터 크기를 조정하는 데 사용한다. 스칼라는 방향이 없기 때문에 벡터에 스칼라를 곱하면 벡터의 크기만 바꿀 수 있는 것이다. 좌표계로 보면 아래 그림과 같다.

게임에서는 어떻게 이용하는지 살펴보자. 우선 저번 포스팅에 벡터의 뺄셈을 통해 이동할 방향벡터를 구할 수 있었다. 이 방향벡터에 스칼라를 곱하면 이동 벡터가 나오게 된다.

방향벡터(단위 벡터) x 스칼라(이동할 거리) = 이동 벡터

2020/10/23 - [StUdY/게임 수학] - 게임 수학) 벡터 뺄셈

캐릭터를 원하는 위치로 이동시키는 방법은 캐릭터 현재 위치 벡터 + 이동 벡터

 

반응형

'STUDY > 게임수학' 카테고리의 다른 글

게임수학)벡터 뺄셈  (0) 2020.10.23
게임수학) 벡터란, 벡터의 덧셈  (0) 2020.10.19

벡터의 뺄셈

벡터의 뺄셈은 덧셈과 같다. 우리가 알고 있는 3 - 2 = 1인 것처럼.  (3,0) - (2,0) = (1,0)이 된다. 음수인 좌표에 있는 값은 (3,2) - (-2,2) = (5,0)이 된다. 좌표로 보면 아래 그림과 같다.

뺄셈을 덧셈으로 바꿔보자. (3,2) - (-2,2) = (3,2) + (2,-2) = (5,0)

(-2,0)이 (2,-2)로 바뀌면서 화살표 머리 방향이 반대로 바뀌었다. 두 벡터의 뺀 값 (5,0)도 좌표로 표시해 보자

벡터의 덧셈에서 공부했던 것처럼 꼬리에서 머리로 이으면 두 벡터의 뺄셈 결과인 (5,0)을 알게 되었다. 여기서 주의해서 봐야 할 것은 (5,0)의 방향입니다. (3,2)를 을 가리키고 있습니다.

벡터의 뺄셈 언제 쓸까?

벡터의 뺄셈은 특정 위치에서 다른 위치를 바라보는 벡터의 방향을 알 수 있다.(방향벡터)  

유니티로 몬스터 위치에서 히어로 위치로 이동하도록 해봤다.

 

반응형

'STUDY > 게임수학' 카테고리의 다른 글

게임수학) 벡터와 스칼라의 곱셈  (0) 2020.10.30
게임수학) 벡터란, 벡터의 덧셈  (0) 2020.10.19

게임프로그래밍에서 벡터는 물체의 위치, 이동 등 다양하게 이용된다. 그렇기 때문에 지식이 필요하다. 책과 구글링, 서치를 통해 정리하면서 공부를 하려고 한다.

벡터의 사전적 의미

 

벡터의 표시법

 

벡터의 기본적인 성질에 대해서 알아보자

동등성 :  사전적 의미로 벡터는 크기방향만을 가지고 있다. 그렇기 때문에 원점의 특정한 위치는 아무런 의미를 갖지 않는다. 이게 무슨 말이냐??

내가 그림

위 4개의 벡터는 각각 다른 위치에 있지만, 크기와 방향은 같다. 그러니까 동일한 벡터이다. 

 

벡터는 왜 필요할까?

x,y나 특 정 위치를 표시하는 방법으로 표현할 수도 있지만, 벡터의 성질을 게임에서 활용하기 위해서이다.( 더 많은 것들을 표현할 수 있음)

예를 들어 지속적으로 맵 안에서 움직이는 캐릭터가  있다. 이 캐릭터는 일정한 속도로 움직인다. 이 상황에서 크기와 방향이 같은 벡터를 통해 어디에 놓아도 동일한 계산 결과가 나오게 된다.

벡터가 크기랑 방향을 갖고 있는건 알겠는데,  이걸로 뭘 어떻게  움직이는 건데..??

 

벡터의 덧셈

위 2개의 벡터의 덧셈 결과는? 어느 위치하냐는 의미가 없다고 했으니까, 각 좌표의 x끼리, y끼리 더하면 된다. 결괏값은 벡터(7,0)이다.  몇 가지 더 풀어보자

첫 번째 그림은 (5,0)  두번째 그림은 (5,1)

마지막으로 이 두 벡터를 더해보자.

위에서 봤던 문제처럼 (5,3)이라는 답을 쉽게 찾을 수 있다. 그러면 이 (5,3)을 벡터로 표시하면 어떻게 될까?

 두 벡터 덧셈은 시작점과 끝점을 연결하면 된다는 것을 알 수 있다.

이 말은 즉, 특정 위치에서 특정 거리만큼 떨어진 위치를 찾을 수 있다. 

반응형

'STUDY > 게임수학' 카테고리의 다른 글

게임수학) 벡터와 스칼라의 곱셈  (0) 2020.10.30
게임수학)벡터 뺄셈  (0) 2020.10.23

UML을 처음 알게 되었던 것은 클라이언트 프로그래머를 준비하기 위해 학원을 다니면서였다. 하지만 그때는 깊게 공부를 하지 않았었다. UML의 사용의 이유정도 였다. 딱히 필요성도 느끼지 못했었다. 10개월 동안 업무를 진행하면서 필요성을 조금 느꼈다. 그래서 다시 사이트를 검색하며 공부/정리하게 됐다.

1. UML 

Unified Modeling Language의 약자이다. 소프트웨어 공학에서 사용되는 표준화된 범용 모델링 언어이다. 산출물을 만들때 미리 설계해 검증할 수 있으며 이것은 곧 문서화가 되게 된다. 문서화가 되면서부터 UML의 사용 이유가 생기게 된다.

  • 작성한 코드의 구조를 한눈의 볼 수 있다.
  • 추 후 유지보수의 대응이 빠르다.

그렇다고 모든 코드를 작성할 때마다 UML을 만들 필요는 없어 보인다. 실제로 작성하는 코드보다 UML의 문서화가 시간이 더 오래 걸릴 수 있기 때문이다.

 

2. 클래스 간의 관계도 표시방법

1. Generakization(일반화 관계) : 부모 클래스와 자식 클래스 간의 상속관 계를 나타낼 때.

2. Realization (실체화) :  인터페이스를 구현하는 관계를 표현한다.

3. Association (연관 관계) : 한 객체가 다른 객체를 소유하거나, 참조하여 사용할 때, 단방향과 양방향이 존재한다.

 

- Aggregation (집합) : 약한 결합, 연결된 클래스와 독립적으로 동작된다. 메인클래스가 삭제될 시 대상 클래스는 같이 삭제가 안된다.

 - Composition (합성) :  Aggregation보다 강한 결합으로 이루어져 있어 라이프 사이클이 같아(같이 사라짐) 독립적이면 의미가 없다.

4. Dependency (의존 관계) :  서로의 객체를 소유하지는 않지만, 객체의 변경이 이루어질 때 따라서 같이 변경을 해주어야 할 때 사용한다. 대상 클래스를 생성해주고 바이바이

 

 

 

반응형

모바일에서 게임 플레이 중에 홈버튼으로 나가게 된다면 애니메이션이나 프레임은 어떻게 동작하는 것이 궁금하여 큐브가 회전하는 간단한 애니메이션을 만들어 테스트해봤다.

유니티 에디터에서 모바일에서 홈버튼이 눌린 상태를 만들기 위해서는 아래와 같은 세팅이 필요하다.

Run In Background 체크를 해제한다. 그러면 유니티 에디터 창이 비활성화 상태를 만들면 게임은 멈추게 된다. 

그럼 이제 몇 가지 테스트를 해보자

1. 애니메이터 Update Mode : Normal / WaitForSecondsRealtime

    void Start()
    {
        Debug.Log("Test 시작");
        StartCoroutine(CorTest());
    }

    public IEnumerator CorTest()
    {
        yield return (CorWaitForRealSeconds());
    }
    private IEnumerator CorWaitForRealSeconds()
    {
        yield return new WaitForSecondsRealtime(5);
        Debug.Log("Test2 WaitForSecondsRealtime 5seconds");
    }
    //애니메이션 이벤트 콜.
    public void AnimEnd()
    {
        Debug.LogError("AnimEnd");
    }

 

에디터가 시작하면 CorTest를 시작하면서 RealSeconds로 5초를 기다린 후 로그를 출력하고, AnimEnd는 애니메이션에서 6초에 이벤트 키를 넣었다.

정상적으로 아무 동작을 하지 않으면 차례대로 5초 뒤에 "Test 2 WaitForSeconds 5 seconds"을 출력하고 그다음에 1초 뒤에 곧이어 "AnimEnd"을 출력하게 될 것이다.

예상 : "Test 2 WaitForSeconds 5 seconds" -> 1초 뒤  "AnimEnd"

에디터가 활성화되어있지 않을 때는 애니메이션이 멈추는 것을 볼 수 있다. 5초쯤에 유니티를 클릭하면 바로 "Test 2 WaitForSecondsRealtime 5 seconds"을 출력하게 된다. 그리고 "AnimEnd"은 1초가 아닌 약 5초 뒤에 출력하게 되는 것을 볼 수 있다.

결과 : "Test 2 WaitForSeconds 5 seconds" -> 약 5초 뒤  "AnimEnd"

테스트 결론 : 비활성화되었을 때도 WaitForSecondsRealtime은 계속해서 흐르지만 애니메이션은 활성화 되었을때 다시 카운팅을 하는 것이다. 

 

2. 애니메이터 Update Mode : Unscaled Time / WaitForSeconds

    void Start()
    {
        Debug.Log("Test 시작");
        StartCoroutine(CorTest());
    }

    public IEnumerator CorTest()
    {
        yield return StartCoroutine(CorWaitForSeconds());
        //Debug.LogError("CorTest");
    }
    private IEnumerator CorWaitForSeconds()
    {
        yield return new WaitForSeconds(5);
        Debug.Log("Test2 WaitForSeconds 5seconds");
    }
    //애니메이션 이벤트 콜.
    public void AnimEnd()
    {
        Debug.LogError("AnimEnd");
    }

애니메이션 이벤트 콜은 6초이기 때문에 WaitForSeconds 5 seconds 후 1초 뒤에 AnimEnd가 불려야 하지만 

애니메이터는 Unscale Time이기 때문에 비활성화 상태에도 시간은 흐른다. 그렇기 때문에 "AnimEnd" 후 "WaitForSeconds 5 seconds"가 출력된다. 

결론으로 짝을 매겨보자면.

Unscale Time과 WaitForSecondsRealtime과 짝꿍/  Normal과 WaitForSeconds이 짝이 될수 있겠다.

애니메이션이 종료 시점에 맞추어 코 루틴으로 제어한다면 애니메이터 업데이트 모드를 확인하고 그에 맞는 대기 코 루틴을 설정할 필요가 있다.

반응형

스크롤 셀을 만들 때 셀에 들어가는 데이터가 같은 글자 수면 참 좋겠지만 그렇지 않은 경우가 많다. 예를 들어 퀘스트 스크롤을 제작한다고 하자.

퀘스트의 설명이 어떤 퀘스트는 아주 길고 또 어떤 퀘스트의 내용은 짧고 또는 예상할수 없는 길이의 데이터가 입력될 수 있다.

그렇다고 셀들의 크기를 설명이 큰 내용의 맞추어 크게 만들 수는 없다. 또 유지보수에도 좋지않다.(이미 지정한 크기 이상의 데이터는 추가될 수도 없기 때문이다.)

이쁘지않다.

그렇다면 입력되는 데이터 텍스트의 수의 따라 파란배경 이미지의 사이즈 변경이 필요하다.

방법은 Content Size Fitter와 Layout Group컴포넌트를 이용해하는 것이다. 

사실 위 두개 컴포넌트가 텍스트 사이즈의 맞게 바로바로 변경되면 좋겠지만 그렇지 못하는 경우가 있다. LayoutRebuilder.ForceRebuildLayoutImmediate요 함수를 통해 즉시 정렬하도록 해줬다.

(요 함수는 오브젝트가 켜져있어야 정상 작동한다)

반응형

함수 오버로딩

매개변수는 다르지만 동일한 이름의 멤버 함수를 여러 개 작성할 수 있는 기능이다. 

 

1. 구현 방법

	void Add()
	{

	}	
    void Add(string s1, string s2)
	{
		...~~~
	}
	void Add(int a, int b)
	{
		...~~~
	}

함수 이름은 같지만 받는 매개변수가 다르다.

같은 이름의 함수를 호출하지만 상황에 따라 다른 결과를 얻어 낼 수 있다. 예를 들어 같은 기능이지만 매개변수 타입의 따른 다른 결과를 원할 때 사용하면 된다. 

 


연산자 오버 로딩

기존의 제공하고 있는 연산자를 재정의하여 원하는 방식으로 수정하여 사용할 수 있다. 전역 함수나 클래스로 재정의도 가능하다.

1. 구현 방법

	Student() { name = "ji"; }
	Student(string name) :name(name) {}

	Student operator + (const Student& other)
	{
		return Student(Student(name + " & " + other.name));
	}

operator를 사용하여 '+'의 연산자를 재정의 한다. 

	Student st1;
	Student st2("han");
	Student result = st1 + st2;

'+'은 기본적으로 더한다라는 정의하지만 내가 만든 Student의 객체를 '+'한다는 의미는 컴파일러는 알지 못한다. 하지만 위에서 정의한 '+'의 기능을 정의하였기에 아래와 같은 결과를 볼 수 있다. 이런 기능을 사용하므로 코드의 길이를 줄일 수 있는 장점이 있다.

결과

 

2. 규칙

연산자 오버 로딩의 규칙들이 있다.

기존의 있는 연산자만 가능하다 (새로운 연산자를 만들어 낼 수는 없음)

멤버 연산자(.), 범위 지정 연산자(::), 전처리기 연결(##)등의 몇몇 연산자는 불가능하다.

피연산자의 개수 규칙 등 기본적인 연산자의 규칙 등 기본적인 연산자의 규칙을 따라야 한다. 

오버 로딩이 된 연산자 중 하나는 사용자 정의 자료형 이어야 한다.

 

반응형

'프로그래밍언어 > C++' 카테고리의 다른 글

C++) 클래스 상속, 오버라이딩  (0) 2020.06.22
C++) 생성자와 소멸자  (0) 2020.06.10
C++)접근 제한자  (0) 2020.06.09
C) 배열/문자열과 포인터.  (0) 2020.05.02
C) 파일 입출력 (FILE,fopen,fclose)  (0) 2020.05.02

클래스 상속

부모(베이스) 클래스 안에 있던 멤버 변수와 멤버 함수를 물려받아 새로운 클래스를 작성할 수 있게 된다. 객체지향 언어의 가장 큰 특징이며 장점이라고 생각된다.


 

1. 구현방법

class Person
{
	~~~~부모
}


class Student : Person 
{
	~~~~자식
}

Person은 부모 클래스 Student는 자식 클래스라 보면 자식 클래스 옆에  : 을 사용하여 Person의 자식이라는 것을 정의한다.

2. 예시

class Person
{

private:
	string name;

public:
	Person(string name) : name(name)
	{
	}
	string GetName()
	{
		return name;
	}

};

class Student : Person 
{
private:
	int studentID;
public:
	Student(int studentID, string name) : Person(name)
	{
		this->studentID = studentID;
	}

	void Show()
	{
		cout << "학생 번호 : " << studentID << '\n';
		cout << "학생 이름 : " << GetName() << '\n';
	}

};
int main()
{
	Student student(1,"han");
	student.Show();
	system("pause");
}

Student라는 객체를 새로 생성하면서 생성자를 통해 학생 번호와 이름을 부여하고 있다. Student는 name이라는 변수는 없지만 Person(name)을 통해 부모의 변수의 값을 넣을 수 있게 된다.

실행 결과

 

3. 다중 상속

class TempClass
{
public :
	void ShowTemp()
	{
		cout << "임시 부모 \n";
	}

};

class Student : Person, public TempClass
{
private:
	int studentID;
public:

~~~...

int main()
{
	Student student(1,"han");
	student.ShowTemp();
	system("pause");
}

C#은 안되지만, C++은 다중 상속이 가능하다. 

 

4. 장점

1) 코드 중복이 없어진다. 2) 함수 재활용이 가능해진다.


 

오버라이딩

 

상속받은 자식 클래스에서 부모 클래스의 함수를 사용하지 않고 자식 클래스의 함수로 재정의 해서 사용하는 것이다. 아래 예시를 보면 쉽게 이해가 될 것이다.

class Person
{

private:
	string name;

public:
	Person(string name) : name(name)
	{
	}
	string GetName()
	{
		return name;
	}

	void ShowName()
	{
		cout << "이름 :" << GetName() << '\n';
	}
};

class Student : Person, public TempClass
{
private:
	int studentID;
public:
	Student(int studentID, string name) : Person(name)
	{
		this->studentID = studentID;
	}

	void Show()
	{
		cout << "학생 번호 : " << studentID << '\n';
		cout << "학생 이름 : " << GetName() << '\n';
	}
	void ShowName()
	{
		cout << "학생 이름 :" << GetName() << '\n';
	}
};



int main()
{
	Student student(1,"han");
	student.ShowName();
	system("pause");
}

부모인 Person클래스도 ShowNmae() 함수가 있고, 자식인 Student도 ShowName() 함수가 있다. 이 과정을 오버 라이딩이며 부모 함수를 무시하고 자식 함수를 실행하게 된다.

실행 결과


 

추가적으로

1. 자식(파생) 클래스의 객체를 생성하면, 부모(베이스) 클래스의 생성자를 호출한 뒤, 자식 클래스의 생성자가 호출된다.

2. 소멸자는 반대로 자식 클래스가 먼저 호출되고 그 후에 부모 클래스의 소멸자가 호출된다. 

반응형

'프로그래밍언어 > C++' 카테고리의 다른 글

C++) 함수 오버로딩 / 연산자 오버로딩  (0) 2020.06.23
C++) 생성자와 소멸자  (0) 2020.06.10
C++)접근 제한자  (0) 2020.06.09
C) 배열/문자열과 포인터.  (0) 2020.05.02
C) 파일 입출력 (FILE,fopen,fclose)  (0) 2020.05.02

생성자

객체를 생성함과 동시에 멤버 변수를 초기화할 수 있다. 인스턴스화될 때 자동으로 호출되며,  클래스의 멤버 변수를 적절한 기본값 또는 사용자가 정의한 값을 갖는 인스턴스가 생성되는 것이다. 

 


 

1. 구현방법

class Student
{
private:
	string name;
	int englishScore;
	int mathScore;

public:

	Student(string n ,int e, int m)
	{
		name = n;
		englishScore = e;
		mathScore = m;
	}

};

일반 메서드를 작성하는 방법과 같다. 이름은 클래스 이름과 같게 설정해주면 된다.(반환 값은 없음) Student인 새로운 객체를 만들 때 name, englishscore, mathscore의 각각 해당하는 값을 할당해주면서 이 객체는 초기화된다.

 


 

2. 사용방법

1) 클래스 이름과 같아야함 2) 반환 값이없어야함

int main(void)
{
	Student a = Student("Mr.Han",100,98); // Student 생성!

}

 이렇게 해주면 끝!

	Student* st1 = new Student("han" , 100, 98);
	st1->NumberUp();

이런식으로 동적으로 생성할때도 생성자는 동작한다.


 

3. 디폴트 생성자

public:

	//Student(string n ,int e, int m) //생성자 주석.
	//{
	//	name = n;
	//	englishScore = e;
	//	mathScore = m;
	//}
};

int main(void)
{
	Student a = Student(); //매개변수 X

}

작성한 생성자가 없을때는 매가변수가 없는 기본 생성자로 인식한다. 멤버 변수들은 '0'혹은 'NULL'인 값으로 설정된다.

 


 

4. 복사생성자

매개변수의 값을 넘겨주는 일반 생성자와 다르게 객체를 넘겨 복사를 하도록 하는 생성자이다.

int number = 0;

class Student
{


private:
	string name;
	int englishScore;
	int mathScore;
	int num;

public:

	Student(string n, int e, int m)
	{
		name = n;
		englishScore = e;
		mathScore = m;
	}
	Student(const Student &st)
	{
		name = st.name;
		englishScore = st.englishScore;
		mathScore = st.mathScore;
	}
	void NumberUp()
	{
		number++;
		num = number;
	}

	void Show()
	{
		cout << "번호 " << num << "이름 " << name << "영어 " << englishScore << "수학 " << mathScore << '\n';

	}
};

int main()
{
	Student* st1 = new Student("han" , 100, 98);
	st1->NumberUp();
	Student st2(*st1);
	st2.NumberUp();
	st1->Show();
	st2.Show();

	system("pause");
}

st1을 만들고 st2를 만들때 생성자에 *st1을 넘김으로 써 st1을 그대로 복사한다. 각 Student객체는 NumberUp()으로 int number를 한개씩 증가 시켜 각각 num의 값을 다르게 넣도록 했다. 

 

소멸자

객체의 수명이 끝났다고 판단되면 이 객체를 제거하기 위한 목적으로 사용된다. 객체의 수명이 끝났을 때 자동으로 컴파일러가 소멸자 함수를 호출한다. 클래스명 앞에 '~'기호를 사용하여 작성한다. 

 


 

1. 구현방법

	~Student()
	{
		cout << "객체 소멸 \n";
	}
int main()
{
	Student* st1 = new Student("han" , 100, 98);
	st1->NumberUp();
	Student st2(*st1);
	st2.NumberUp();
	st1->Show();
	st2.Show();
	
	delete st1;
	system("pause");
}

st1은 선언한 지역이 끝날 때 메모리를 해제하기 전에 소멸자를 먼저 수행하지만, 동적으로 생성한 st2같은 경우는 delete키워드를 이용해 메모리를 해제요청하고 소멸자를 먼저 수행하도록 해야한다.

반응형

C++ 역시 클래스 멤버 선언 시 접근 제한자를 지정할 수 있다.

접근 제한자는 왜 필요할까?

객체지향 프로 래밍은 특히 이 접근 제한자를 잘 사용할 필요가 있다. 객체지향 프로그래밍은 한 완제품을 생상하는 공장의 기계들과 같다고 생각하면 좋을 것 같다. 예를 들어 자동차 한 대를 만들어 내기 위해서는 엔진, 부품 등등 여러 가지 제작, 결합 과정을 거치게 된다. 이 공장 기계들은 서로의 어떻게 부품을 만들어 내는지, 전혀 알 필요가 없다. 공장 기계들은 설계자가 시킨 작업만 진행하면 된다. 서로의 작업들의 내용을 공유하지 않기 위해 필요한 것이 정보은닉이다.

프로그램도 프로그래머가 설계한 클래스, 함수를 통해 자기 자신의 작업만 진행하면 된다. 이 접근 제한자는 프로그램을 보통 한명이 만들 때보다 여러 프로그래머가 동시에 작업을 할 때 특히 확실히 필요하게 된다.

#include <iostream>
#include <string>

using namespace std;

class Student
{
private:
	string name;
	int englishScore;
	int mathScore;
	int getSum()
	{
		return englishScore + mathScore;
	}
public:
	Student(string n ,int e, int m)
	{
		name = n;
		englishScore = e;
		mathScore = m;
	}
	void show() { cout << name << " : [합계 " << getSum() << "점]\n"; }

};

int main(void)
{
	Student a = Student("Mr.Han",100,98);
	a.show();

	system("pause");
}

반응형

'프로그래밍언어 > C++' 카테고리의 다른 글

C++) 클래스 상속, 오버라이딩  (0) 2020.06.22
C++) 생성자와 소멸자  (0) 2020.06.10
C) 배열/문자열과 포인터.  (0) 2020.05.02
C) 파일 입출력 (FILE,fopen,fclose)  (0) 2020.05.02
C) ASCII 아스키코드  (0) 2020.05.01

반응형

반응형

 

 

모든 리스트 해제

 

일반적으로 삽입삭제가 많이 필요하다면 배열로 구현된 리스트보다 연결리스트가 더 유리하다.

반응형

배열의 사이즈 할당

리스트의 값을 맨앞 또는 맨뒤에 넣는 함수.

특정 인덱스의 값을 지우는 함수

 

결과

반응형

배열과 포인터는 동일하다고 보면된다. 배열을 선언하면 그 이름자체가 포인터 변수가 되는 것이다.

a자체가 선언한 배열의 주소를 가지고 있는 것이다. 

 

문자열의 개념

문자열은 문자들의 배열이다. 끝을 알리는 목적으로 컴퓨터 메모리 구조상 마지막에 널값을 포함한다. 

문자열 형태로 포인터를 사용하면 포인터에 특정한 문자열의 주소를 넣게된다. 

"Hello World"문자열을 읽기 전용으로 메모리 공간에 넣은 뒤 그 위치를 처리한다. 

배열은 포인터와 치환되기 때문에 이런식으로 각각 char한 문자씩 출력이 가능하다. 

반응형

+ Recent posts