midas+son의 크리에이티브(creative) 이야기

연산자 오버로딩 중 ==(같다), !=(다르다) 체크는 쌍으로 이루어 져야 한다.

둘 중에 하나만 존재하지 못한다.

아래의 예제 샘플은

새로운 클래스의 key라는 string변수 값이 같은지 체크하여 

bool을 리턴하도록 ==, !=연산자를 오버로딩 하였다.

//새로운 클래스

    public class EquipmentActor 

    {

        public string key;    //키값변수


        //단순하게 아래처럼 비교하면 null 과 비교 할 수 없어서 위험하다.

        //public static bool operator ==(EquipmentActor x, EquipmentActor y)

        //{

        //    if (x.key.Equals(y.key))

        //        return true;

        //    else return false;

        //}

        //public static bool operator !=(EquipmentActor x, EquipmentActor y)

        //{

        //    if (x.key.Equals(y.key))

        //        return false;

        //    else return true;

        //}


  //object로 바꾸어 null비교를 하고 진행한다.

        public static bool operator ==(EquipmentActor x, EquipmentActor y)

        {

            if ((object)x == null && (object)y == null) return true;

            else if (((object)x != null && (object)y != null) && x.key == y.key) return true;

            else return false;

        }


        public static bool operator !=(EquipmentActor x, EquipmentActor y)

        {

            return !(x == y);

        }


//근데 가장 심플한건 if비교 할때 if((object)변수 == nullptr)하는게 제일 깔끔한거 같다. 그러면 위의 object바꾸는건 다 없어질텐데...

}



C#에도 #define은 존재 한다.


하지만 c++ 에서의 용도와는 다르다.


그중 하나가 상수처럼 사용하고자 할때

c++ 에서는 

#define PI 3.141592f

와 같이

3.141592f를 계속 사용하지 않기 위해서

define을 사용할 수 있었는데

이게 C#에서는 안된다.


C#의 정의는 역시 MSDN을 찾아보는게 최고이므로

url을 남기겠다.

https://msdn.microsoft.com/ko-kr/library/bb397677.aspx


static class Constants { public const double Pi = 3.141592f; }


C#에서는 static으로 별도의 클래스를 만들고

const로 변경 할 수 없는 값을 지정해 

사용하는 것을 권장함을 알아두자.

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

연산자 오버로딩(==, !=)  (0) 2017.05.12
C# 배열 비우기 - Array.Clear, Array.Copy, Array.Resize 이용  (0) 2016.06.13
C# 람다 식  (0) 2016.06.08
C# Windows Forms - 오목  (0) 2016.05.31
소켓 통신, 웹 통신(간략 설명, 메모)  (0) 2016.05.28

우리들은 배열을 비우고자 한다.


byte[] recvData = new byte[10];


위와 같이 배열을 구성하고

뒤에 사이즈를 넣어주게 된다.

"이 배열은 이만큼만 쓸 것입니다"라는 의미이다.


거기에 0부터 9까지 byte를 넣었다고 생각해보자.

(int[]로 해도 됨)


그리고 01234 숫자를 없애고 싶다면 어떻게 할 것인가?


Array.Clear(recvData, 0, 5);    //(배열, 시작 인덱스, 시작 인덱스 부터 지울 길이)

위와 같은 C#의 Array 클래스에 있는 Clear 함수를 사용 할 수 있을 것이다.


하지만 recvData 를 내부를 보면

0000056789 의 byte 가 들어있다.


그게 아니라 앞에 지운것은 다 없애고

56789 만 남게 하고 싶다면 어떻게 해야 하겠는가?


Array.Clear(recvData, 0, 5);


int count = 0;

for (int i = 0; i < recvData.Length; ++i)

{

    if (recvData[i] == '\0')

    {

        ++count;

    }

    else break;

}

byte[] recvData2 = new byte[recvData.Length - count];

Array.Copy(recvData, count, recvData2, 0, recvData2.Length);    // (원본배열, 옮길 인덱스 시작 위치, 복사본 배열, 복사될 시작위치, 복사될 길이)


편리한 함수가 따로 있지 않은 것 같다.

(내가 못찾은 것일 수도 있다.)


위와 같이 '\0'이 들어간 부분을 카운트 하여 앞에서부터 길이를 재고

Array.Copy 로 다른 배열로 옮기는 것이다.

그러면 뒤에서 부터 잘라 올 수 있다.


그럼 반대로 뒤에를 날리고 앞에꺼만 보존 하여

배열 사이즈를 맞춘다면 어떻게 할까?


Array.Resize(ref recvData, recvData.Length - eraseLength);    // (원본 배열 ref 꼭 붙여야 함(참조의 의미), 재조정할 사이즈)


위와 같은 Resize함수를 사용해주면 

사이즈를 재조정해주면서 

뒤를 없앨 수 있다.


위의 함수 들을 복합적으로 사용하면

중간 중간에 비어있는 값들도 뺄 수 있게 되니

잘 처리해보도록 하자

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

연산자 오버로딩(==, !=)  (0) 2017.05.12
C#에서의 상수 사용 - c++의 #define 같은 활용 방안  (0) 2016.06.16
C# 람다 식  (0) 2016.06.08
C# Windows Forms - 오목  (0) 2016.05.31
소켓 통신, 웹 통신(간략 설명, 메모)  (0) 2016.05.28

C# 람다 식

공부/C#2016. 6. 8. 13:10
//람다식에 관한 MSDN 의 정식 설명

=> 

위의 기호로 사용 되는 익명함수 기법.

"람다 식" 이라고 불린다. 

c++ 11버전 부터도 추가된 기법이다.

(주의 // C++11 에서는 사용법이 다르다.)


정확한 것은 위의 url에 접근하여

한번씩 글을 읽어 보기 바란다.


간략하게 얘기 해보면

=> 왼쪽에 입력될 매개 변수가 지정되고

=> 오른쪽에서는 왼쪽의 매개변수를 사용하여 식을 적용하면 

결과로서 값이 다시 매개변수로 반환되는 유용한 식이다.


제약 사항이나 다양한 사용 방법을 다 설명하긴 너무 길어지니 

역시 위의 url을 클릭해서 읽어보자.

C#에서 WinForm 을 처음 다루어 보았다.

오목을 만들었다.

오목 게임 자체는 c++ Console로도 만들어봤으니 

과거를 추억해가며 금방 만들었다.

직관성 있게 디자인 한 것을 

바로바로 확인할 수 있으니 편했다.


[그림 1 - 결과 오목]



[그림 2 - 결과 타임 아웃]



C# 윈폼을 다룰 때 속성 창이나 이벤트 처리등은

예전 회사 다닐때 써봤던 국산 UI 툴 중

투비소프트 사의 마이플랫폼, 엑스플랫폼 을 사용 했을 때나

그 외 다른 툴 들 과도 사용 방법이 거의 비슷해서

과거가 새록새록 기억나며 재미있었다.

■ socket (소켓) 통신


보통 네트워크 게임에서 사용

요청이 없어도 상대에서 알려줄 수 있음


장점

-데이터를 다루는데 있어서 자유로움

-주고 받는게 맘대로 가능


단점

-소켓은 빨대라고 생각. 통신기가 바뀌면 끊김

-빨대의 최대 갯수가 정해져 있음

-웹통신 보다 안정적이지 않음


난이도 웹 통신보다 높음.

메모리에 데이터를 가지고 있을 수 있음.


상호작용 많으면 어쩔 수 없이 소켓 네트워킹을 해야 함.


============================

■ web (웹) 통신


요청을 보내야 반응이 있음


- 비연결 지향. 빨대가 없음. (내부적인 빨대는 있음. 잠깐 연결 후 짜름)

- 최대 연결 갯수가 비교적 여유

- 안정적


메모리에 데이터를 가지기 힘들다.

데이터베이스(DB)에 데이터를 가지고 처리하기에

데이터베이스를 좀 할 줄 알아야 함.


랭킹 서버만 다루면 웹 통신만으로 처리 가능.

모바일 게임에서 많이 다룸.


============================


보통 게임들은 위의 통신 기술들을 복합적으로 사용함.

RPG게임을 예로 들면

아이템을 먹었는데 나중에 보니 없어졌다고 하면

유저들의 신뢰도를 떨어뜨리게 되고 잃게 되는 경우가 큼.

이렇게 신뢰도가 중요한 데이터라고 판단되는 데이터들은 웹 통신으로 처리.

캐릭들간의 실시간 위치 정보는 

조금 차이나도 괜찮다고 유저들은 생각하거나 혹은 크게 티가 안나므로

실시간 소켓 통신을 한다고 생각하면 됨.

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

C# 람다 식  (0) 2016.06.08
C# Windows Forms - 오목  (0) 2016.05.31
yield return, yield break  (0) 2016.05.24
Console 키입력 - Console.ReadKey, ConsoleKeyInfo  (0) 2016.05.12
예외처리 - try, catch, finally, throw new Exception  (0) 2016.05.12

C#에 들어와서 첨 보는 구문이다.

지금껏 뭔가 만들 때는 쓸 일이 없었는데

유니티의 코루틴 관련되어서는 쓸일이 많다는 것 같다.


유니티의 코루틴은 나중에 다뤄서 보고 

오늘은 C#의 yield가 붙은 구문에 대해서 살펴 보자.


C#에 대한 자세한 사항은 역시 MSDN이다.

아래의 url에 들어가서 한번 씩 살펴 보자

https://msdn.microsoft.com/ko-kr/library/9k7k7cf0.aspx


본인이 머리속으로 정리한 내용은 이러하다.


어떠한 함수 내에서 return 값을 받는 것이 있다고 해보자.

그 return은 반복문 안에 있다.


예를 들면 아래와 같다.

int 함수명()

{

int count = 0;

while(true)

{

count += 1;

return count;

}

}


위의 함수를 호출 해봤자 계속 return값은 1 밖에 안된다.


그렇다면 이제 yield return 을 사용해 보자.


yield 를 쓸때에는 반복기 인터페이스 형식이 필요하다고 나온다.

반복기 인터페이스 형식이란 것은 IEnumerableIEnumerator 같은 인터페이스를 가르킨다.


        private IEnumerable<int> Count()

        {

            int count = 0;

            while (true)

            {

                count += 1;

                yield return count;

            }

        }


        public void Print()

        {

            foreach (int i in Count())

            {

                Console.WriteLine(i);

            }

        }


위와 같은 소스가 정~~~말 간략한 예제이다.

(찾아보면 정말 어렵게 잘쓴 코드들이 있다. 전문적인....)


Print()함수 호출하게 되면 1부터 계속 1씩 증가된 값들이 출력된다.

while (true) 이므로 1,2,3,4,5,6,~,~,~,,,,계속 출력 된다.


어떻게 1이 아니라 값이 증가가 되는 것일까?

yield return 으로 빠져 나온 구문은 현재 그 상태를 기억하고 있다.

말이 좀 애매한데

foreach  에서 계속 반복으로  Count()를 호출하고 변수 i에 넣고

Console.WriteLine(i); 를 끝나고 다시 체크 할때

Count() 의 처음 부터 다시 동작 하는 것이 아니라

yield return 을 했던 자리에서 다시 시작 되는 것이다.


즉 함수가 아예 끝... } 부분까지 도달하지 않는 이상 계속 돌리는 것이다.

위와 같은 예제 소스에서는 While( 조건문 )에 반복을 나올 수 있는 조건을 주거나

while(true){

 ....

 if (count == 8) yield break

....}

와 같이 yield break; 을 해주면 해당 반복을 모두 마칠 수 있다.


yield return 을 한 문장으로 정리하자면

리턴되는 위치가 저장되어 다시 호출 됐을 때 저장된 위치로부터 재시작이 될수 있다.

라고 생각 해도 되겠다.

그 것을 끝낼 수 있는 것은 yield return 할수 없이 함수 자체가 끝나거나 

강제로  yield break; 해주면 된다.


yield 자체로는 위의 정의로 끝나겠지만

이게 간단치 않다.

위의 글중에 나온 인터페이스 들...IEnumerableIEnumerator

반복기라고 하는데 이들의 차이와 정확한 사용 예...


사용 조건을 정확히 이해 하기는 아직은 힘든 것 같다.


저 인터페이스들을 class가 implement하여 구현해서 사용 할 수도 있고

IEnumerableIEnumerator마다 구현해야 하는 함수는 

각각 다르고 foreach 말고 다른 구문에서 사용하려면 어떻게 해야 하는지 등등

계속 MSDN을 읽어 봐서 이해가 되면 다음 포스팅으로 적도록 하겠다.


//Console 키입력

                ConsoleKeyInfo cKey = Console.ReadKey(true); //bool 값은 입력된 값 표시 여부로 true 일 때 표시 안함이다.

//Console.ReadKey() 는 기본 false이다. 콘솔창에 표시된다.

                switch (cKey.Key)    //변수.Key로 어떤 키를 입력 했는지 알 수 있다.

                {

//대략 아래처럼 ConsoleKey 를 가져와 처리한다.

                    case ConsoleKey.LeftArrow:    //방향키 왼쪽

                        break;

                    case ConsoleKey.RightArrow:  //방향키 오른쪽

                        break;

                    case ConsoleKey.UpArrow:  //방향키 위쪽

                        break;

                    case ConsoleKey.DownArrow:  //방향키 아래쪽

                        break;

                }


//ConsoleKey는 enum으로 되어있다. 꽤 많으나 주석 처리가 잘되어있으니

//Visual Studio에서 F12를 눌러 필요한 것을 찾아보자.

//예외처리 - try, catch, finally


//예전에 java랑 Jsp 다룰때 꽤 많이 썼었다.(try, catch 안쓰면 컴파일 에러나는 것도 꽤 있었다.)


//쉽게 생각하자.

//try - 하다가 (문제가 없어야 되는게 맞지만문제가 생기면

//catch - 잡는다.(문제가 없으면 잡을 것도 없다.)

//finally - 마지막으로 문제가 있든 없든 실행할 코드 - 안써도 됨.


    class ExceptionTest    //일부러 에러 내보기 위한 class생성

    {

        public int i = 10;

    }


    class Program

    {

//함수로 에러발생이 나게 만들어 보자 - 아래의 main함수 먼저 보고 돌아오자.

        static void TestDiv(int n1, int n2)

        {

            if(n2==0)

            {

                //커스텀 Exception - 내가만든 에러 메세지

                throw new Exception("n2는 0이 아니어야 한다.");

            }


            Console.WriteLine("n1 / n2 함수");

            Console.WriteLine("{0} / {1} = {2}", n1, n2, n1 / n2);

        }


//메인 함수

        static void Main(string[] args)

        {

            ExceptionTest t = null;


            //예외 처리

            try

            {

                //위험 요소가 있는 코드를 작성

                Console.WriteLine("Test : {0}", t.i);

            }

            catch (NullReferenceException e)  //특정 예외만 골라내기 - new되지 않는 class사용하려고 함

            {

                //try 부분에서 에러가 발생하면 catch 소스로 접근

                //프로그램을 죽이지 않고 처리하기 위한 방법

                Console.WriteLine("널 참조 예외가 발생");

                Console.WriteLine(e.Message);   //한글로 간략 오류 설명

                Console.WriteLine("=======================");

                Console.WriteLine(e);   //예외 메세지 전체

            }

            //catch를 여러개 사용할 수 있는데 위에서 먼저 잡히면 아래껀 무시

            catch (Exception e) //모든 예외 

            {

                //try 부분에서 에러가 발생하면 catch 소스로 접근

                //프로그램을 죽이지 않고 처리하기 위한 방법

                Console.WriteLine("예외가 발생함.");

                Console.WriteLine(e.Message);   //한글로 간략 오류 설명

                //Console.WriteLine("=======================");

                //Console.WriteLine(e);   //예외 메세지 전체

            }

            finally

            {

                //예외가 발생 하든 안하든 

                //무조건 들어와서 실행되는 영역.

                Console.WriteLine("try 마지막");

            }


//0으로 나눌때 에러를 발생해보자

            try

            {

                Console.WriteLine("10/0 = ?");

                //int n1 = 10 / 0; //에러 - 0으로 나눌 수 없어서 빨간줄

                int n1 = 10;

                int n2 = 0;

                //int n3 = n1 / n2;

                //Console.WriteLine("{0} / {1} = {2}", n1, n2, n3);

                TestDiv(n1, n2);

            }

            catch(DivideByZeroException e)    //0으로 나눌때 잡는 Exception

            {

                Console.WriteLine("0으로 나누려고 함");

                Console.WriteLine(e);

            }

            catch (Exception e)

            {

                Console.WriteLine("에러!!!!!!!");

                Console.WriteLine(e);

            }


            //catch가 개발용...테스트 할 때 포함 하면 좋지만

            //성능이 그리 좋은게 아니라

            //배포시에는 뺄수 있으면 빼는 것이 좋다.

        }

    }

//파일 로드 - FileStream, StreamReader, ReadAllText, ReadAllLines

//이전 글 - 파일 저장과 대입되는 4가지 방법 이다.


        private string loadData;    //로드한 데이터를 담을 변수

//디렉터리 경로 지정

        private const string path = "./save/test1/";   //요즘 많이 사용하는 방법 /

        private const string path2 = ".\\save\\test2"; //옛날부터 디렉터리 구분하는 방법 \\

        private const string path3 = @".\save\test3"; //@뒤에 있는 string은 디렉터리를 표시하는 것이라고 지정 - 최신


        private void PrintLoadData()

        {

            Console.WriteLine("읽은 데이터는 다음과 같습니다.");

            Console.WriteLine("{0}\n", loadData);

        }


        //로드

        //1. FileStream

        public void LoadFromFileStream(string fileName = "S_FileString.txt")

        {

            string fullpath = path + "/" + fileName;    //그냥 쉽게 하려고 쓴것이다. 성능은 StringBuilder이 좋다. 


            try

            {

//파일 스트림 생성. cs파일 상단에 using System.IO; 추가

                FileStream fs = new FileStream(

                    fullpath,

                    FileMode.Open,

                    FileAccess.Read);


//바이트 단위로 로드

                byte[] bytes = new byte[fs.Length];

                fs.Read(bytes, 0, bytes.Length);

                loadData = Encoding.UTF8.GetString(bytes);

                fs.Close();

            }

                //파일 못찾음

            catch (FileNotFoundException)   //변수 없어도 됨

            {

                Console.WriteLine("파일을 찾지 못했습니다.");

                return;

            }

                //디렉터리 못찾음

            catch(DirectoryNotFoundException)

            {

                Console.WriteLine("폴더가 없습니다.");

                return;

            }

            catch (Exception e)

            {

                Console.WriteLine(e);

            }


            Console.WriteLine("로딩 완료 LoadFromFileStream");

            PrintLoadData();


        }


        //2. StreamReader

        public void LoadFromStreamReader(string fileName = "S_StreamWriter.txt")

        {

            string fullpath = path2 + "/" + fileName;


            try

            {

                FileStream fs = new FileStream(

                    fullpath,

                    FileMode.Open,

                    FileAccess.Read);


                StreamReader sr = new StreamReader(fs);    //스트림 생성


                string line;

                StringBuilder builder = new StringBuilder();


                while((line = sr.ReadLine()) != null)    //한줄 읽는데 null이 아니면

                {

                    builder.Append(line);    //한줄 저장

                    builder.Append("\n");    //개행

                }

                loadData = builder.ToString();


                sr.Close();

                fs.Close();

            }

            //파일 못찾음

            catch (FileNotFoundException)   //변수 없어도 됨

            {

                Console.WriteLine("파일을 찾지 못했습니다.");

                return;

            }

            //디렉터리 못찾음

            catch (DirectoryNotFoundException)

            {

                Console.WriteLine("폴더가 없습니다.");

                return;

            }

            catch (Exception e)

            {

                Console.WriteLine(e);

            }


            Console.WriteLine("로딩 완료 LoadFromStreamReader");

            PrintLoadData();


        }


        //3. ReadAllText

        public void LoadFromReadAllText(string fileName = "S_WriteAllText.txt")

        {

            string fullpath = path3 + "/" + fileName;


            try

            {

                loadData = File.ReadAllText(fullpath);    //걍 모두 저장. 한줄이라 참쉽다.

            }

            //파일 못찾음

            catch (FileNotFoundException)   //변수 없어도 됨

            {

                Console.WriteLine("파일을 찾지 못했습니다.");

                return;

            }

            //디렉터리 못찾음

            catch (DirectoryNotFoundException)

            {

                Console.WriteLine("폴더가 없습니다.");

                return;

            }

            catch (Exception e)

            {

                Console.WriteLine(e);

            }


            Console.WriteLine("로딩 완료 LoadFromReadAllText");

            PrintLoadData();


        }


        //4. ReadAllLines

        public void LoadFromReadAllLines(string fileName = "S_WriteAllLines.txt")

        {

            string fullpath = path2 + "/" + fileName;


            try

            {

                string[] lines = File.ReadAllLines(fullpath);    //라인별로 string 배열에 저장

                StringBuilder builder = new StringBuilder(lines.Length);    //스트링 변수의 크기를 알고 있으면 선언해주는게 좋다

                foreach(var line in lines)

                {

                    builder.Append(line);

                    builder.Append("\n");

                }

                loadData = builder.ToString();

            }

            //파일 못찾음

            catch (FileNotFoundException)   //변수 없어도 됨

            {

                Console.WriteLine("파일을 찾지 못했습니다.");

                return;

            }

            //디렉터리 못찾음

            catch (DirectoryNotFoundException)

            {

                Console.WriteLine("폴더가 없습니다.");

                return;

            }

            catch (Exception e)

            {

                Console.WriteLine(e);

            }


            Console.WriteLine("로딩 완료 LoadFromReadAllLines");

            PrintLoadData();


        }